diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index ca7c016..9376b0f 100644
--- a/.github/workflows/build-docker.yml
+++ b/.github/workflows/build-docker.yml
@@ -168,7 +168,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: server
- file: server/Dockerfile.aetherlite-dev
+ file: server/Dockerfile.aetherlite
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 6ade26a..61fdca0 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -95,9 +95,9 @@ jobs:
steps:
- uses: actions/checkout@v6
- - uses: actions/setup-go@v5
+ - uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: server/go.sum
- name: Run integration tests
@@ -113,9 +113,9 @@ jobs:
steps:
- uses: actions/checkout@v6
- - uses: actions/setup-go@v5
+ - uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: server/go.sum
# The e2e suite spawns aetherlite as a subprocess and exercises the
@@ -132,4 +132,3 @@ jobs:
# t.Parallel tunnel tests concurrently against the shared gateway causes
# tunnel-lifecycle races (PEER_RESET) under 2-core-runner load. Serialize.
run: go test -tags=e2e -count=1 -p 1 -parallel 1 -timeout 360s ./internal/proxysidecar/integration_e2e/...
-
diff --git a/.github/workflows/proto-check.yml b/.github/workflows/proto-check.yml
index 7408e52..7f7006c 100644
--- a/.github/workflows/proto-check.yml
+++ b/.github/workflows/proto-check.yml
@@ -28,7 +28,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache: false
- name: Install protoc
diff --git a/.github/workflows/publish-go.yml b/.github/workflows/publish-go.yml
index f6e60d3..5fe5d4a 100644
--- a/.github/workflows/publish-go.yml
+++ b/.github/workflows/publish-go.yml
@@ -17,18 +17,18 @@ jobs:
uses: ./.github/workflows/test-go.yml
module-tags:
- name: Verify Go module tags
+ name: Publish Go module tags
runs-on: ubuntu-latest
needs: [ tests ]
permissions:
- contents: read
+ contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- - name: Verify per-module tags
+ - name: Reconcile per-module tags
env:
MODULE_DIRS: "api sdk/go server"
run: |
@@ -42,13 +42,17 @@ jobs:
tag="$dir/$VERSION"
have="$(git rev-list -n1 "$tag" 2>/dev/null || true)"
if [ -z "$have" ]; then
- echo "::error::missing tag $tag — Go cannot resolve this module at $VERSION"
- bad=1
+ echo "::notice::creating $tag at $sha"
+ git tag "$tag" "$sha"
+ new="$new $tag"
elif [ "$have" != "$sha" ]; then
- echo "::error::$tag points at $have but the release tag is $sha"
+ echo "::error::$tag exists at $have but the release tag is $sha"
bad=1
else
- echo "$tag ok"
+ echo "$tag already correct"
fi
done
+ if [ -n "$new" ]; then
+ git push origin $new
+ fi
[ "$bad" = 0 ]
diff --git a/.github/workflows/test-go.yml b/.github/workflows/test-go.yml
index 1ad98ed..87c6bf6 100644
--- a/.github/workflows/test-go.yml
+++ b/.github/workflows/test-go.yml
@@ -33,7 +33,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: api/go.sum
- name: go vet
@@ -60,7 +60,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: api/go.sum
- name: golangci-lint
@@ -77,7 +77,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: api/go.sum
- name: Install govulncheck
@@ -130,7 +130,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: server/go.sum
- name: go vet
@@ -157,7 +157,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: server/go.sum
- name: golangci-lint
@@ -174,7 +174,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: server/go.sum
- name: Install govulncheck
@@ -227,7 +227,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: sdk/go/go.sum
- name: go vet
@@ -254,7 +254,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: sdk/go/go.sum
- name: golangci-lint
@@ -271,7 +271,7 @@ jobs:
- uses: actions/setup-go@v6
with:
- go-version: '1.25.12'
+ go-version: '1.25.14'
cache-dependency-path: sdk/go/go.sum
- name: Install govulncheck
@@ -280,11 +280,9 @@ jobs:
- name: Run govulncheck
working-directory: sdk/go
env:
- # GO-2026-4887: docker/docker <= v28.5.2; no upstream fix; see SECURITY.md
- # GO-2026-4883: docker/docker <= v28.5.2; no upstream fix; see SECURITY.md
- # GO-2026-5617: docker cp bind-mount redirection race; no upstream fix; see SECURITY.md
- # GO-2026-5668: docker cp symlink-swap empty-file race; no upstream fix; see SECURITY.md
- IGNORED_ADVISORIES: "GO-2026-4887 GO-2026-4883 GO-2026-5617 GO-2026-5668"
+ # GO-2026-4887: legacy docker client module; Engine-only advisory; see SECURITY.md
+ # GO-2026-4883: legacy docker client module; Engine-only advisory; see SECURITY.md
+ IGNORED_ADVISORIES: "GO-2026-4887 GO-2026-4883"
run: |
set -uo pipefail
report="$RUNNER_TEMP/govulncheck-aether-sdk-go.json"
diff --git a/CLAUDE.md b/CLAUDE.md
index ff99f98..0c9269f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -84,6 +84,27 @@ toolchain), plus a virtualenv with the pinned `grpcio-tools` and
`npm install` in `sdk/typescript`. If repo-tools runs outside that virtualenv,
point it at the right interpreter with `--python .venv/bin/python`.
+### Releasing
+
+A release is one action: push the root tag. Everything else is CI.
+
+```bash
+# versions.yaml already holds the version; sync-versions keeps manifests in step
+git checkout main && git pull
+git tag v0.2.3 && git push origin v0.2.3
+```
+
+That single tag triggers `publish-python`, `publish-npm`, `publish-go` and
+`build-docker`. `publish-go` creates and pushes the `api/`, `sdk/go/` and
+`server/` tags that Go resolves nested modules by — they are the artifact of a
+release, not the trigger for one, and nothing keys off them.
+
+Push **only** the root tag. GitHub creates no push event when more than three
+tags arrive at once, so pushing the module tags yourself alongside it silently
+fires no workflows at all.
+
+There is no tag-release script any more; `publish-go` replaced it.
+
### Docker Build
```bash
# Build context is the repo root
diff --git a/SECURITY.md b/SECURITY.md
index f8c96e2..e049c22 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -49,16 +49,14 @@ Out of scope:
## Known Issues
-The following vulnerabilities are tracked but unresolved at the time of the current release because no upstream fix is yet available. They are reachable from the published Go SDK (`github.com/scitrera/aether/sdk/go`) via the Docker-based orchestrator (`sdk/go/orchestrators/docker`):
+The following Docker Engine advisories are tracked for the published Go SDK (`github.com/scitrera/aether/sdk/go`) because it imports the legacy `github.com/docker/docker` client module. Aether uses the client packages, not the affected Engine plugin implementation, but the Go vulnerability records do not provide symbol-level data or a fixed version for this legacy module path, so `govulncheck` conservatively reports them as reachable:
| Advisory | Affected | Status |
|---|---|---|
-| [GO-2026-4887](https://pkg.go.dev/vuln/GO-2026-4887) | `github.com/docker/docker` ≤ v28.5.2 | No upstream fix released. Tracking. |
-| [GO-2026-4883](https://pkg.go.dev/vuln/GO-2026-4883) | `github.com/docker/docker` ≤ v28.5.2 | No upstream fix released. Tracking. |
-| [GO-2026-5617](https://pkg.go.dev/vuln/GO-2026-5617) | `github.com/docker/docker` ≤ v28.5.2 | `docker cp` bind-mount redirection race. No upstream fix released. Tracking. |
-| [GO-2026-5668](https://pkg.go.dev/vuln/GO-2026-5668) | `github.com/docker/docker` ≤ v28.5.2 | `docker cp` symlink-swap arbitrary-empty-file race. No upstream fix released. Tracking. |
+| [GO-2026-4887](https://pkg.go.dev/vuln/GO-2026-4887) | Docker Engine < 29.3.1; legacy Go module has no fixed release | Engine AuthZ-plugin bypass; Aether imports only the Docker API client. Tracking migration to `github.com/moby/moby/client`. |
+| [GO-2026-4883](https://pkg.go.dev/vuln/GO-2026-4883) | Docker Engine < 29.3.1; legacy Go module has no fixed release | Engine plugin privilege-validation issue; Aether imports only the Docker API client. Tracking migration to `github.com/moby/moby/client`. |
-Mitigation: callers that don't need the Docker orchestrator can build their applications without importing `sdk/go/orchestrators/docker`. We will bump the dependency immediately when upstream ships fixed releases.
+Mitigation: callers that don't need the Docker orchestrator can build their applications without importing `sdk/go/orchestrators/docker`. We will migrate to the separately versioned Moby client module once compatibility is validated.
## Security Best Practices
diff --git a/api/go.mod b/api/go.mod
index c5ffbf4..a7b11b0 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -1,15 +1,15 @@
module github.com/scitrera/aether/api
-go 1.25.12
+go 1.25.14
require (
- google.golang.org/grpc v1.81.1
+ google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
)
require (
- golang.org/x/net v0.54.0 // indirect
- golang.org/x/sys v0.44.0 // indirect
+ golang.org/x/net v0.55.0 // indirect
+ golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
)
diff --git a/api/go.sum b/api/go.sum
index 1f74e93..b01b282 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -22,17 +22,17 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
-golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
-golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
-golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
-golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
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/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
-google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
-google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
+google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
diff --git a/api/proto/aether.pb.go b/api/proto/aether.pb.go
index 27f7183..1b176d0 100644
--- a/api/proto/aether.pb.go
+++ b/api/proto/aether.pb.go
@@ -625,6 +625,61 @@ func (BackoffStrategy) EnumDescriptor() ([]byte, []int) {
return file_aether_proto_rawDescGZIP(), []int{9}
}
+// Controls what TARGETED task creation does when the exact target identity is
+// not connected. UNSPECIFIED deliberately preserves the released behavior:
+// validate the implementation and ask an orchestrator to start the worker.
+type TargetOfflinePolicy int32
+
+const (
+ TargetOfflinePolicy_TARGET_OFFLINE_POLICY_UNSPECIFIED TargetOfflinePolicy = 0
+ TargetOfflinePolicy_TARGET_OFFLINE_POLICY_ORCHESTRATE TargetOfflinePolicy = 1
+ TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE TargetOfflinePolicy = 2
+ TargetOfflinePolicy_TARGET_OFFLINE_POLICY_REJECT TargetOfflinePolicy = 3
+)
+
+// Enum value maps for TargetOfflinePolicy.
+var (
+ TargetOfflinePolicy_name = map[int32]string{
+ 0: "TARGET_OFFLINE_POLICY_UNSPECIFIED",
+ 1: "TARGET_OFFLINE_POLICY_ORCHESTRATE",
+ 2: "TARGET_OFFLINE_POLICY_QUEUE",
+ 3: "TARGET_OFFLINE_POLICY_REJECT",
+ }
+ TargetOfflinePolicy_value = map[string]int32{
+ "TARGET_OFFLINE_POLICY_UNSPECIFIED": 0,
+ "TARGET_OFFLINE_POLICY_ORCHESTRATE": 1,
+ "TARGET_OFFLINE_POLICY_QUEUE": 2,
+ "TARGET_OFFLINE_POLICY_REJECT": 3,
+ }
+)
+
+func (x TargetOfflinePolicy) Enum() *TargetOfflinePolicy {
+ p := new(TargetOfflinePolicy)
+ *p = x
+ return p
+}
+
+func (x TargetOfflinePolicy) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (TargetOfflinePolicy) Descriptor() protoreflect.EnumDescriptor {
+ return file_aether_proto_enumTypes[10].Descriptor()
+}
+
+func (TargetOfflinePolicy) Type() protoreflect.EnumType {
+ return &file_aether_proto_enumTypes[10]
+}
+
+func (x TargetOfflinePolicy) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use TargetOfflinePolicy.Descriptor instead.
+func (TargetOfflinePolicy) EnumDescriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{10}
+}
+
// WaitReason enumerates why a task is in a WAITING_* state. Each value pairs
// with a specific TaskStatus: WAIT_REASON_INPUT <-> TASK_STATUS_WAITING_INPUT,
// WAIT_REASON_AUTHORITY <-> TASK_STATUS_WAITING_AUTHORITY,
@@ -669,11 +724,11 @@ func (x WaitReason) String() string {
}
func (WaitReason) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[10].Descriptor()
+ return file_aether_proto_enumTypes[11].Descriptor()
}
func (WaitReason) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[10]
+ return &file_aether_proto_enumTypes[11]
}
func (x WaitReason) Number() protoreflect.EnumNumber {
@@ -682,7 +737,7 @@ func (x WaitReason) Number() protoreflect.EnumNumber {
// Deprecated: Use WaitReason.Descriptor instead.
func (WaitReason) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{10}
+ return file_aether_proto_rawDescGZIP(), []int{11}
}
// AuthorityRequestStatus tracks the lifecycle of an AuthorityRequest.
@@ -728,11 +783,11 @@ func (x AuthorityRequestStatus) String() string {
}
func (AuthorityRequestStatus) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[11].Descriptor()
+ return file_aether_proto_enumTypes[12].Descriptor()
}
func (AuthorityRequestStatus) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[11]
+ return &file_aether_proto_enumTypes[12]
}
func (x AuthorityRequestStatus) Number() protoreflect.EnumNumber {
@@ -741,7 +796,7 @@ func (x AuthorityRequestStatus) Number() protoreflect.EnumNumber {
// Deprecated: Use AuthorityRequestStatus.Descriptor instead.
func (AuthorityRequestStatus) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{11}
+ return file_aether_proto_rawDescGZIP(), []int{12}
}
// ProgressKind classifies a progress update by its intended UI surface or
@@ -793,11 +848,11 @@ func (x ProgressKind) String() string {
}
func (ProgressKind) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[12].Descriptor()
+ return file_aether_proto_enumTypes[13].Descriptor()
}
func (ProgressKind) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[12]
+ return &file_aether_proto_enumTypes[13]
}
func (x ProgressKind) Number() protoreflect.EnumNumber {
@@ -806,7 +861,106 @@ func (x ProgressKind) Number() protoreflect.EnumNumber {
// Deprecated: Use ProgressKind.Descriptor instead.
func (ProgressKind) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{12}
+ return file_aether_proto_rawDescGZIP(), []int{13}
+}
+
+type WorkflowAuthorityLifetimeMode int32
+
+const (
+ WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND WorkflowAuthorityLifetimeMode = 0
+ WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_DURABLE WorkflowAuthorityLifetimeMode = 1
+)
+
+// Enum value maps for WorkflowAuthorityLifetimeMode.
+var (
+ WorkflowAuthorityLifetimeMode_name = map[int32]string{
+ 0: "WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND",
+ 1: "WORKFLOW_AUTHORITY_LIFETIME_DURABLE",
+ }
+ WorkflowAuthorityLifetimeMode_value = map[string]int32{
+ "WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND": 0,
+ "WORKFLOW_AUTHORITY_LIFETIME_DURABLE": 1,
+ }
+)
+
+func (x WorkflowAuthorityLifetimeMode) Enum() *WorkflowAuthorityLifetimeMode {
+ p := new(WorkflowAuthorityLifetimeMode)
+ *p = x
+ return p
+}
+
+func (x WorkflowAuthorityLifetimeMode) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (WorkflowAuthorityLifetimeMode) Descriptor() protoreflect.EnumDescriptor {
+ return file_aether_proto_enumTypes[14].Descriptor()
+}
+
+func (WorkflowAuthorityLifetimeMode) Type() protoreflect.EnumType {
+ return &file_aether_proto_enumTypes[14]
+}
+
+func (x WorkflowAuthorityLifetimeMode) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use WorkflowAuthorityLifetimeMode.Descriptor instead.
+func (WorkflowAuthorityLifetimeMode) EnumDescriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{14}
+}
+
+type AuthorityContinuationRequest_ScopeMode int32
+
+const (
+ AuthorityContinuationRequest_SCOPE_MODE_UNSPECIFIED AuthorityContinuationRequest_ScopeMode = 0
+ // Service-only mode used when a trusted service must evaluate arbitrary
+ // resources within the caller's existing authority ceiling.
+ AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT AuthorityContinuationRequest_ScopeMode = 1
+ // Required for agent recipients. The requested scope is validated as a
+ // strict subset of the parent and the child is minted per invocation.
+ AuthorityContinuationRequest_SCOPE_MODE_ATTENUATE AuthorityContinuationRequest_ScopeMode = 2
+)
+
+// Enum value maps for AuthorityContinuationRequest_ScopeMode.
+var (
+ AuthorityContinuationRequest_ScopeMode_name = map[int32]string{
+ 0: "SCOPE_MODE_UNSPECIFIED",
+ 1: "SCOPE_MODE_INHERIT_PARENT",
+ 2: "SCOPE_MODE_ATTENUATE",
+ }
+ AuthorityContinuationRequest_ScopeMode_value = map[string]int32{
+ "SCOPE_MODE_UNSPECIFIED": 0,
+ "SCOPE_MODE_INHERIT_PARENT": 1,
+ "SCOPE_MODE_ATTENUATE": 2,
+ }
+)
+
+func (x AuthorityContinuationRequest_ScopeMode) Enum() *AuthorityContinuationRequest_ScopeMode {
+ p := new(AuthorityContinuationRequest_ScopeMode)
+ *p = x
+ return p
+}
+
+func (x AuthorityContinuationRequest_ScopeMode) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (AuthorityContinuationRequest_ScopeMode) Descriptor() protoreflect.EnumDescriptor {
+ return file_aether_proto_enumTypes[15].Descriptor()
+}
+
+func (AuthorityContinuationRequest_ScopeMode) Type() protoreflect.EnumType {
+ return &file_aether_proto_enumTypes[15]
+}
+
+func (x AuthorityContinuationRequest_ScopeMode) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use AuthorityContinuationRequest_ScopeMode.Descriptor instead.
+func (AuthorityContinuationRequest_ScopeMode) EnumDescriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{21, 0}
}
type KVOperation_OpType int32
@@ -894,11 +1048,11 @@ func (x KVOperation_OpType) String() string {
}
func (KVOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[13].Descriptor()
+ return file_aether_proto_enumTypes[16].Descriptor()
}
func (KVOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[13]
+ return &file_aether_proto_enumTypes[16]
}
func (x KVOperation_OpType) Number() protoreflect.EnumNumber {
@@ -907,7 +1061,7 @@ func (x KVOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use KVOperation_OpType.Descriptor instead.
func (KVOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{23, 0}
+ return file_aether_proto_rawDescGZIP(), []int{25, 0}
}
// Scope identifies the (identity x sharing) cell of the KV matrix.
@@ -975,11 +1129,11 @@ func (x KVOperation_Scope) String() string {
}
func (KVOperation_Scope) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[14].Descriptor()
+ return file_aether_proto_enumTypes[17].Descriptor()
}
func (KVOperation_Scope) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[14]
+ return &file_aether_proto_enumTypes[17]
}
func (x KVOperation_Scope) Number() protoreflect.EnumNumber {
@@ -988,7 +1142,7 @@ func (x KVOperation_Scope) Number() protoreflect.EnumNumber {
// Deprecated: Use KVOperation_Scope.Descriptor instead.
func (KVOperation_Scope) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{23, 1}
+ return file_aether_proto_rawDescGZIP(), []int{25, 1}
}
type Signal_SignalType int32
@@ -1021,11 +1175,11 @@ func (x Signal_SignalType) String() string {
}
func (Signal_SignalType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[15].Descriptor()
+ return file_aether_proto_enumTypes[18].Descriptor()
}
func (Signal_SignalType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[15]
+ return &file_aether_proto_enumTypes[18]
}
func (x Signal_SignalType) Number() protoreflect.EnumNumber {
@@ -1034,7 +1188,7 @@ func (x Signal_SignalType) Number() protoreflect.EnumNumber {
// Deprecated: Use Signal_SignalType.Descriptor instead.
func (Signal_SignalType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{27, 0}
+ return file_aether_proto_rawDescGZIP(), []int{30, 0}
}
type CheckpointOperation_OpType int32
@@ -1073,11 +1227,11 @@ func (x CheckpointOperation_OpType) String() string {
}
func (CheckpointOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[16].Descriptor()
+ return file_aether_proto_enumTypes[19].Descriptor()
}
func (CheckpointOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[16]
+ return &file_aether_proto_enumTypes[19]
}
func (x CheckpointOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1086,7 +1240,7 @@ func (x CheckpointOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use CheckpointOperation_OpType.Descriptor instead.
func (CheckpointOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{34, 0}
+ return file_aether_proto_rawDescGZIP(), []int{37, 0}
}
type AdminQuery_OpType int32
@@ -1128,11 +1282,11 @@ func (x AdminQuery_OpType) String() string {
}
func (AdminQuery_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[17].Descriptor()
+ return file_aether_proto_enumTypes[20].Descriptor()
}
func (AdminQuery_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[17]
+ return &file_aether_proto_enumTypes[20]
}
func (x AdminQuery_OpType) Number() protoreflect.EnumNumber {
@@ -1141,7 +1295,7 @@ func (x AdminQuery_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use AdminQuery_OpType.Descriptor instead.
func (AdminQuery_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{36, 0}
+ return file_aether_proto_rawDescGZIP(), []int{39, 0}
}
type SessionOperation_OpType int32
@@ -1177,11 +1331,11 @@ func (x SessionOperation_OpType) String() string {
}
func (SessionOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[18].Descriptor()
+ return file_aether_proto_enumTypes[21].Descriptor()
}
func (SessionOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[18]
+ return &file_aether_proto_enumTypes[21]
}
func (x SessionOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1190,7 +1344,7 @@ func (x SessionOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use SessionOperation_OpType.Descriptor instead.
func (SessionOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{44, 0}
+ return file_aether_proto_rawDescGZIP(), []int{47, 0}
}
type TaskQuery_OpType int32
@@ -1223,11 +1377,11 @@ func (x TaskQuery_OpType) String() string {
}
func (TaskQuery_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[19].Descriptor()
+ return file_aether_proto_enumTypes[22].Descriptor()
}
func (TaskQuery_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[19]
+ return &file_aether_proto_enumTypes[22]
}
func (x TaskQuery_OpType) Number() protoreflect.EnumNumber {
@@ -1236,7 +1390,7 @@ func (x TaskQuery_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use TaskQuery_OpType.Descriptor instead.
func (TaskQuery_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{46, 0}
+ return file_aether_proto_rawDescGZIP(), []int{49, 0}
}
type TaskOperation_OpType int32
@@ -1290,11 +1444,11 @@ func (x TaskOperation_OpType) String() string {
}
func (TaskOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[20].Descriptor()
+ return file_aether_proto_enumTypes[23].Descriptor()
}
func (TaskOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[20]
+ return &file_aether_proto_enumTypes[23]
}
func (x TaskOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1303,7 +1457,7 @@ func (x TaskOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use TaskOperation_OpType.Descriptor instead.
func (TaskOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{50, 0}
+ return file_aether_proto_rawDescGZIP(), []int{53, 0}
}
type WorkspaceOperation_OpType int32
@@ -1348,11 +1502,11 @@ func (x WorkspaceOperation_OpType) String() string {
}
func (WorkspaceOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[21].Descriptor()
+ return file_aether_proto_enumTypes[24].Descriptor()
}
func (WorkspaceOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[21]
+ return &file_aether_proto_enumTypes[24]
}
func (x WorkspaceOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1361,7 +1515,7 @@ func (x WorkspaceOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use WorkspaceOperation_OpType.Descriptor instead.
func (WorkspaceOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{54, 0}
+ return file_aether_proto_rawDescGZIP(), []int{57, 0}
}
type AgentOperation_OpType int32
@@ -1409,11 +1563,11 @@ func (x AgentOperation_OpType) String() string {
}
func (AgentOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[22].Descriptor()
+ return file_aether_proto_enumTypes[25].Descriptor()
}
func (AgentOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[22]
+ return &file_aether_proto_enumTypes[25]
}
func (x AgentOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1422,7 +1576,7 @@ func (x AgentOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use AgentOperation_OpType.Descriptor instead.
func (AgentOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{61, 0}
+ return file_aether_proto_rawDescGZIP(), []int{64, 0}
}
type ACLOperation_OpType int32
@@ -1529,11 +1683,11 @@ func (x ACLOperation_OpType) String() string {
}
func (ACLOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[23].Descriptor()
+ return file_aether_proto_enumTypes[26].Descriptor()
}
func (ACLOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[23]
+ return &file_aether_proto_enumTypes[26]
}
func (x ACLOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1542,7 +1696,7 @@ func (x ACLOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use ACLOperation_OpType.Descriptor instead.
func (ACLOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{69, 0}
+ return file_aether_proto_rawDescGZIP(), []int{72, 0}
}
type AuthorityGrantOperation_OpType int32
@@ -1596,11 +1750,11 @@ func (x AuthorityGrantOperation_OpType) String() string {
}
func (AuthorityGrantOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[24].Descriptor()
+ return file_aether_proto_enumTypes[27].Descriptor()
}
func (AuthorityGrantOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[24]
+ return &file_aether_proto_enumTypes[27]
}
func (x AuthorityGrantOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1609,7 +1763,7 @@ func (x AuthorityGrantOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use AuthorityGrantOperation_OpType.Descriptor instead.
func (AuthorityGrantOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{94, 0}
+ return file_aether_proto_rawDescGZIP(), []int{97, 0}
}
type ResolveAuthorityRequestPayload_Decision int32
@@ -1645,11 +1799,11 @@ func (x ResolveAuthorityRequestPayload_Decision) String() string {
}
func (ResolveAuthorityRequestPayload_Decision) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[25].Descriptor()
+ return file_aether_proto_enumTypes[28].Descriptor()
}
func (ResolveAuthorityRequestPayload_Decision) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[25]
+ return &file_aether_proto_enumTypes[28]
}
func (x ResolveAuthorityRequestPayload_Decision) Number() protoreflect.EnumNumber {
@@ -1658,7 +1812,7 @@ func (x ResolveAuthorityRequestPayload_Decision) Number() protoreflect.EnumNumbe
// Deprecated: Use ResolveAuthorityRequestPayload_Decision.Descriptor instead.
func (ResolveAuthorityRequestPayload_Decision) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{108, 0}
+ return file_aether_proto_rawDescGZIP(), []int{111, 0}
}
type AuthorityRequestOperation_OpType int32
@@ -1703,11 +1857,11 @@ func (x AuthorityRequestOperation_OpType) String() string {
}
func (AuthorityRequestOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[26].Descriptor()
+ return file_aether_proto_enumTypes[29].Descriptor()
}
func (AuthorityRequestOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[26]
+ return &file_aether_proto_enumTypes[29]
}
func (x AuthorityRequestOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1716,7 +1870,7 @@ func (x AuthorityRequestOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use AuthorityRequestOperation_OpType.Descriptor instead.
func (AuthorityRequestOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{110, 0}
+ return file_aether_proto_rawDescGZIP(), []int{113, 0}
}
type AuthorityRequestEvent_EventType int32
@@ -1761,11 +1915,11 @@ func (x AuthorityRequestEvent_EventType) String() string {
}
func (AuthorityRequestEvent_EventType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[27].Descriptor()
+ return file_aether_proto_enumTypes[30].Descriptor()
}
func (AuthorityRequestEvent_EventType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[27]
+ return &file_aether_proto_enumTypes[30]
}
func (x AuthorityRequestEvent_EventType) Number() protoreflect.EnumNumber {
@@ -1774,7 +1928,7 @@ func (x AuthorityRequestEvent_EventType) Number() protoreflect.EnumNumber {
// Deprecated: Use AuthorityRequestEvent_EventType.Descriptor instead.
func (AuthorityRequestEvent_EventType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{112, 0}
+ return file_aether_proto_rawDescGZIP(), []int{115, 0}
}
type TokenOperation_OpType int32
@@ -1816,11 +1970,11 @@ func (x TokenOperation_OpType) String() string {
}
func (TokenOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[28].Descriptor()
+ return file_aether_proto_enumTypes[31].Descriptor()
}
func (TokenOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[28]
+ return &file_aether_proto_enumTypes[31]
}
func (x TokenOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1829,7 +1983,7 @@ func (x TokenOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use TokenOperation_OpType.Descriptor instead.
func (TokenOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{113, 0}
+ return file_aether_proto_rawDescGZIP(), []int{116, 0}
}
type WorkflowOperation_OpType int32
@@ -1945,11 +2099,11 @@ func (x WorkflowOperation_OpType) String() string {
}
func (WorkflowOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[29].Descriptor()
+ return file_aether_proto_enumTypes[32].Descriptor()
}
func (WorkflowOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[29]
+ return &file_aether_proto_enumTypes[32]
}
func (x WorkflowOperation_OpType) Number() protoreflect.EnumNumber {
@@ -1958,7 +2112,7 @@ func (x WorkflowOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use WorkflowOperation_OpType.Descriptor instead.
func (WorkflowOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{121, 0}
+ return file_aether_proto_rawDescGZIP(), []int{126, 0}
}
type ProxyError_Kind int32
@@ -2009,11 +2163,11 @@ func (x ProxyError_Kind) String() string {
}
func (ProxyError_Kind) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[30].Descriptor()
+ return file_aether_proto_enumTypes[33].Descriptor()
}
func (ProxyError_Kind) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[30]
+ return &file_aether_proto_enumTypes[33]
}
func (x ProxyError_Kind) Number() protoreflect.EnumNumber {
@@ -2022,7 +2176,7 @@ func (x ProxyError_Kind) Number() protoreflect.EnumNumber {
// Deprecated: Use ProxyError_Kind.Descriptor instead.
func (ProxyError_Kind) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{132, 0}
+ return file_aether_proto_rawDescGZIP(), []int{137, 0}
}
type TunnelOpen_Protocol int32
@@ -2058,11 +2212,11 @@ func (x TunnelOpen_Protocol) String() string {
}
func (TunnelOpen_Protocol) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[31].Descriptor()
+ return file_aether_proto_enumTypes[34].Descriptor()
}
func (TunnelOpen_Protocol) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[31]
+ return &file_aether_proto_enumTypes[34]
}
func (x TunnelOpen_Protocol) Number() protoreflect.EnumNumber {
@@ -2071,7 +2225,7 @@ func (x TunnelOpen_Protocol) Number() protoreflect.EnumNumber {
// Deprecated: Use TunnelOpen_Protocol.Descriptor instead.
func (TunnelOpen_Protocol) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{133, 0}
+ return file_aether_proto_rawDescGZIP(), []int{138, 0}
}
type TunnelClose_Reason int32
@@ -2113,11 +2267,11 @@ func (x TunnelClose_Reason) String() string {
}
func (TunnelClose_Reason) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[32].Descriptor()
+ return file_aether_proto_enumTypes[35].Descriptor()
}
func (TunnelClose_Reason) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[32]
+ return &file_aether_proto_enumTypes[35]
}
func (x TunnelClose_Reason) Number() protoreflect.EnumNumber {
@@ -2126,7 +2280,7 @@ func (x TunnelClose_Reason) Number() protoreflect.EnumNumber {
// Deprecated: Use TunnelClose_Reason.Descriptor instead.
func (TunnelClose_Reason) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{135, 0}
+ return file_aether_proto_rawDescGZIP(), []int{140, 0}
}
type TaskSubscriptionOperation_OpType int32
@@ -2162,11 +2316,11 @@ func (x TaskSubscriptionOperation_OpType) String() string {
}
func (TaskSubscriptionOperation_OpType) Descriptor() protoreflect.EnumDescriptor {
- return file_aether_proto_enumTypes[33].Descriptor()
+ return file_aether_proto_enumTypes[36].Descriptor()
}
func (TaskSubscriptionOperation_OpType) Type() protoreflect.EnumType {
- return &file_aether_proto_enumTypes[33]
+ return &file_aether_proto_enumTypes[36]
}
func (x TaskSubscriptionOperation_OpType) Number() protoreflect.EnumNumber {
@@ -2175,7 +2329,7 @@ func (x TaskSubscriptionOperation_OpType) Number() protoreflect.EnumNumber {
// Deprecated: Use TaskSubscriptionOperation_OpType.Descriptor instead.
func (TaskSubscriptionOperation_OpType) EnumDescriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{143, 0}
+ return file_aether_proto_rawDescGZIP(), []int{148, 0}
}
type UpstreamMessage struct {
@@ -2213,6 +2367,8 @@ type UpstreamMessage struct {
// *UpstreamMessage_SubmitAuditEvent
// *UpstreamMessage_AuthorityRequestOp
// *UpstreamMessage_TaskSubscriptionOp
+ // *UpstreamMessage_AccessCheck
+ // *UpstreamMessage_BatchAccessCheck
Payload isUpstreamMessage_Payload `protobuf_oneof:"payload"`
// Phase 6: URIs of extensions active on this specific message. When the
// receiver does not support a URI listed here and the extension was
@@ -2542,6 +2698,24 @@ func (x *UpstreamMessage) GetTaskSubscriptionOp() *TaskSubscriptionOperation {
return nil
}
+func (x *UpstreamMessage) GetAccessCheck() *AccessCheckOperation {
+ if x != nil {
+ if x, ok := x.Payload.(*UpstreamMessage_AccessCheck); ok {
+ return x.AccessCheck
+ }
+ }
+ return nil
+}
+
+func (x *UpstreamMessage) GetBatchAccessCheck() *BatchAccessCheckOperation {
+ if x != nil {
+ if x, ok := x.Payload.(*UpstreamMessage_BatchAccessCheck); ok {
+ return x.BatchAccessCheck
+ }
+ }
+ return nil
+}
+
func (x *UpstreamMessage) GetActiveExtensions() []string {
if x != nil {
return x.ActiveExtensions
@@ -2677,6 +2851,14 @@ type UpstreamMessage_TaskSubscriptionOp struct {
TaskSubscriptionOp *TaskSubscriptionOperation `protobuf:"bytes,31,opt,name=task_subscription_op,json=taskSubscriptionOp,proto3,oneof"`
}
+type UpstreamMessage_AccessCheck struct {
+ AccessCheck *AccessCheckOperation `protobuf:"bytes,33,opt,name=access_check,json=accessCheck,proto3,oneof"`
+}
+
+type UpstreamMessage_BatchAccessCheck struct {
+ BatchAccessCheck *BatchAccessCheckOperation `protobuf:"bytes,34,opt,name=batch_access_check,json=batchAccessCheck,proto3,oneof"`
+}
+
func (*UpstreamMessage_Init) isUpstreamMessage_Payload() {}
func (*UpstreamMessage_Send) isUpstreamMessage_Payload() {}
@@ -2739,6 +2921,10 @@ func (*UpstreamMessage_AuthorityRequestOp) isUpstreamMessage_Payload() {}
func (*UpstreamMessage_TaskSubscriptionOp) isUpstreamMessage_Payload() {}
+func (*UpstreamMessage_AccessCheck) isUpstreamMessage_Payload() {}
+
+func (*UpstreamMessage_BatchAccessCheck) isUpstreamMessage_Payload() {}
+
type DownstreamMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Payload:
@@ -2780,6 +2966,8 @@ type DownstreamMessage struct {
// *DownstreamMessage_TaskHibernated
// *DownstreamMessage_TaskSubscriptionResponse
// *DownstreamMessage_TaskEvent
+ // *DownstreamMessage_AccessCheckResponse
+ // *DownstreamMessage_BatchAccessCheckResponse
Payload isDownstreamMessage_Payload `protobuf_oneof:"payload"`
// Phase 6: URIs of extensions active on this specific message. Same
// semantics as UpstreamMessage.active_extensions: receivers reject when
@@ -3160,6 +3348,24 @@ func (x *DownstreamMessage) GetTaskEvent() *TaskEvent {
return nil
}
+func (x *DownstreamMessage) GetAccessCheckResponse() *AccessCheckResponse {
+ if x != nil {
+ if x, ok := x.Payload.(*DownstreamMessage_AccessCheckResponse); ok {
+ return x.AccessCheckResponse
+ }
+ }
+ return nil
+}
+
+func (x *DownstreamMessage) GetBatchAccessCheckResponse() *BatchAccessCheckResponse {
+ if x != nil {
+ if x, ok := x.Payload.(*DownstreamMessage_BatchAccessCheckResponse); ok {
+ return x.BatchAccessCheckResponse
+ }
+ }
+ return nil
+}
+
func (x *DownstreamMessage) GetActiveExtensions() []string {
if x != nil {
return x.ActiveExtensions
@@ -3319,6 +3525,14 @@ type DownstreamMessage_TaskEvent struct {
TaskEvent *TaskEvent `protobuf:"bytes,37,opt,name=task_event,json=taskEvent,proto3,oneof"`
}
+type DownstreamMessage_AccessCheckResponse struct {
+ AccessCheckResponse *AccessCheckResponse `protobuf:"bytes,39,opt,name=access_check_response,json=accessCheckResponse,proto3,oneof"`
+}
+
+type DownstreamMessage_BatchAccessCheckResponse struct {
+ BatchAccessCheckResponse *BatchAccessCheckResponse `protobuf:"bytes,40,opt,name=batch_access_check_response,json=batchAccessCheckResponse,proto3,oneof"`
+}
+
func (*DownstreamMessage_Msg) isDownstreamMessage_Payload() {}
func (*DownstreamMessage_Config) isDownstreamMessage_Payload() {}
@@ -3393,6 +3607,10 @@ func (*DownstreamMessage_TaskSubscriptionResponse) isDownstreamMessage_Payload()
func (*DownstreamMessage_TaskEvent) isDownstreamMessage_Payload() {}
+func (*DownstreamMessage_AccessCheckResponse) isDownstreamMessage_Payload() {}
+
+func (*DownstreamMessage_BatchAccessCheckResponse) isDownstreamMessage_Payload() {}
+
// TaskHibernated is sent to the worker assigned to a task immediately after it
// transitions to HIBERNATED. Workers SHOULD close their gRPC stream cleanly
// after receiving this; the gateway will tolerate the disconnect and the
@@ -4750,9 +4968,23 @@ type SendMessage struct {
// meaningful for agents/tasks but empty for users today). Will become
// secondary once authproxy-issued root grants are used as the primary
// scope source.
- AppWorkspace string `protobuf:"bytes,5,opt,name=app_workspace,json=appWorkspace,proto3" json:"app_workspace,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ AppWorkspace string `protobuf:"bytes,5,opt,name=app_workspace,json=appWorkspace,proto3" json:"app_workspace,omitempty"`
+ // Optional exact logical-resource check evaluated in addition to ordinary
+ // topic-route authorization. On allow, the resulting receipt is attached to
+ // the trusted MessageEnvelope/IncomingMessage metadata; on deny, nothing is
+ // published. Existing sends without this field retain their current path.
+ CheckedAccess *ResourceAccessRequest `protobuf:"bytes,6,opt,name=checked_access,json=checkedAccess,proto3" json:"checked_access,omitempty"`
+ // Explicitly request a gateway-derived, short-lived authorization context
+ // for the resolved recipient. The gateway only honors this when the send is
+ // already operating under a validated OBO grant with delegation capacity.
+ // For sv::{implementation} targets, wildcard resolution happens first and
+ // the child grant is bound to the concrete service instance. Exact agent
+ // targets require an invocation-bound, explicitly attenuated scope. The
+ // recipient receives the result in IncomingMessage.forwarded_authorization;
+ // payload data can never populate that trusted field.
+ AuthorityContinuation *AuthorityContinuationRequest `protobuf:"bytes,7,opt,name=authority_continuation,json=authorityContinuation,proto3" json:"authority_continuation,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *SendMessage) Reset() {
@@ -4820,41 +5052,47 @@ func (x *SendMessage) GetAppWorkspace() string {
return ""
}
-// Metric is the canonical payload for SendMessage when message_type == METRIC.
-// All entries are interpreted as additive deltas; negative qty requires the
-// `capability/metric_credit` ACL permission on the sender.
-type Metric struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // Optional correlation ID (request/trace) tying these entries to upstream work.
- TraceId string `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
- // One or more counter deltas. At least one entry is required.
- Entries []*MetricEntry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"`
- // Free-form tags / hints. Documented well-known keys:
- //
- // lifecycle = "startup" | "shutdown"
- // source.version, source.region, source.host, ...
- Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // Optional client-side timestamp (ms since epoch). The server always also
- // stamps MessageEnvelope.timestamp_ms; this is for client-side ordering hints.
- ClientTimestampMs int64 `protobuf:"varint,4,opt,name=client_timestamp_ms,json=clientTimestampMs,proto3" json:"client_timestamp_ms,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *SendMessage) GetCheckedAccess() *ResourceAccessRequest {
+ if x != nil {
+ return x.CheckedAccess
+ }
+ return nil
}
-func (x *Metric) Reset() {
- *x = Metric{}
+func (x *SendMessage) GetAuthorityContinuation() *AuthorityContinuationRequest {
+ if x != nil {
+ return x.AuthorityContinuation
+ }
+ return nil
+}
+
+// Explicit scope ceiling for a derived message authority continuation. Empty
+// axes retain the AuthorityGrant meaning of unrestricted, so an attenuated
+// agent continuation requires every axis to be populated and validated.
+type AuthorityContinuationScope struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ WorkspaceScope []string `protobuf:"bytes,1,rep,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"`
+ ResourceScope []*ACLAuthorityGrantResourceScopeEntry `protobuf:"bytes,2,rep,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"`
+ OperationScope []string `protobuf:"bytes,3,rep,name=operation_scope,json=operationScope,proto3" json:"operation_scope,omitempty"`
+ MaxAccessLevel int32 `protobuf:"varint,4,opt,name=max_access_level,json=maxAccessLevel,proto3" json:"max_access_level,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AuthorityContinuationScope) Reset() {
+ *x = AuthorityContinuationScope{}
mi := &file_aether_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *Metric) String() string {
+func (x *AuthorityContinuationScope) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*Metric) ProtoMessage() {}
+func (*AuthorityContinuationScope) ProtoMessage() {}
-func (x *Metric) ProtoReflect() protoreflect.Message {
+func (x *AuthorityContinuationScope) ProtoReflect() protoreflect.Message {
mi := &file_aether_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -4866,11 +5104,153 @@ func (x *Metric) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use Metric.ProtoReflect.Descriptor instead.
-func (*Metric) Descriptor() ([]byte, []int) {
+// Deprecated: Use AuthorityContinuationScope.ProtoReflect.Descriptor instead.
+func (*AuthorityContinuationScope) Descriptor() ([]byte, []int) {
return file_aether_proto_rawDescGZIP(), []int{20}
}
+func (x *AuthorityContinuationScope) GetWorkspaceScope() []string {
+ if x != nil {
+ return x.WorkspaceScope
+ }
+ return nil
+}
+
+func (x *AuthorityContinuationScope) GetResourceScope() []*ACLAuthorityGrantResourceScopeEntry {
+ if x != nil {
+ return x.ResourceScope
+ }
+ return nil
+}
+
+func (x *AuthorityContinuationScope) GetOperationScope() []string {
+ if x != nil {
+ return x.OperationScope
+ }
+ return nil
+}
+
+func (x *AuthorityContinuationScope) GetMaxAccessLevel() int32 {
+ if x != nil {
+ return x.MaxAccessLevel
+ }
+ return 0
+}
+
+type AuthorityContinuationRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ScopeMode AuthorityContinuationRequest_ScopeMode `protobuf:"varint,1,opt,name=scope_mode,json=scopeMode,proto3,enum=aether.v1.AuthorityContinuationRequest_ScopeMode" json:"scope_mode,omitempty"`
+ // Opaque invocation identifier. Required for ATTENUATE and matched to the
+ // checked-access correlation ID so the trusted receipt, child, and payload
+ // can be validated as one call by the recipient.
+ BindingId string `protobuf:"bytes,2,opt,name=binding_id,json=bindingId,proto3" json:"binding_id,omitempty"`
+ Scope *AuthorityContinuationScope `protobuf:"bytes,3,opt,name=scope,proto3" json:"scope,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AuthorityContinuationRequest) Reset() {
+ *x = AuthorityContinuationRequest{}
+ mi := &file_aether_proto_msgTypes[21]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *AuthorityContinuationRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AuthorityContinuationRequest) ProtoMessage() {}
+
+func (x *AuthorityContinuationRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_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 AuthorityContinuationRequest.ProtoReflect.Descriptor instead.
+func (*AuthorityContinuationRequest) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{21}
+}
+
+func (x *AuthorityContinuationRequest) GetScopeMode() AuthorityContinuationRequest_ScopeMode {
+ if x != nil {
+ return x.ScopeMode
+ }
+ return AuthorityContinuationRequest_SCOPE_MODE_UNSPECIFIED
+}
+
+func (x *AuthorityContinuationRequest) GetBindingId() string {
+ if x != nil {
+ return x.BindingId
+ }
+ return ""
+}
+
+func (x *AuthorityContinuationRequest) GetScope() *AuthorityContinuationScope {
+ if x != nil {
+ return x.Scope
+ }
+ return nil
+}
+
+// Metric is the canonical payload for SendMessage when message_type == METRIC.
+// All entries are interpreted as additive deltas; negative qty requires the
+// `capability/metric_credit` ACL permission on the sender.
+type Metric struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // Optional correlation ID (request/trace) tying these entries to upstream work.
+ TraceId string `protobuf:"bytes,1,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
+ // One or more counter deltas. At least one entry is required.
+ Entries []*MetricEntry `protobuf:"bytes,2,rep,name=entries,proto3" json:"entries,omitempty"`
+ // Free-form tags / hints. Documented well-known keys:
+ //
+ // lifecycle = "startup" | "shutdown"
+ // source.version, source.region, source.host, ...
+ Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ // Optional client-side timestamp (ms since epoch). The server always also
+ // stamps MessageEnvelope.timestamp_ms; this is for client-side ordering hints.
+ ClientTimestampMs int64 `protobuf:"varint,4,opt,name=client_timestamp_ms,json=clientTimestampMs,proto3" json:"client_timestamp_ms,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Metric) Reset() {
+ *x = Metric{}
+ mi := &file_aether_proto_msgTypes[22]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Metric) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Metric) ProtoMessage() {}
+
+func (x *Metric) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_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 Metric.ProtoReflect.Descriptor instead.
+func (*Metric) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{22}
+}
+
func (x *Metric) GetTraceId() string {
if x != nil {
return x.TraceId
@@ -4911,7 +5291,7 @@ type MetricEntry struct {
func (x *MetricEntry) Reset() {
*x = MetricEntry{}
- mi := &file_aether_proto_msgTypes[21]
+ mi := &file_aether_proto_msgTypes[23]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -4923,7 +5303,7 @@ func (x *MetricEntry) String() string {
func (*MetricEntry) ProtoMessage() {}
func (x *MetricEntry) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[21]
+ mi := &file_aether_proto_msgTypes[23]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4936,7 +5316,7 @@ func (x *MetricEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use MetricEntry.ProtoReflect.Descriptor instead.
func (*MetricEntry) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{21}
+ return file_aether_proto_rawDescGZIP(), []int{23}
}
func (x *MetricEntry) GetName() string {
@@ -4969,7 +5349,7 @@ type SwitchWorkspace struct {
func (x *SwitchWorkspace) Reset() {
*x = SwitchWorkspace{}
- mi := &file_aether_proto_msgTypes[22]
+ mi := &file_aether_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -4981,7 +5361,7 @@ func (x *SwitchWorkspace) String() string {
func (*SwitchWorkspace) ProtoMessage() {}
func (x *SwitchWorkspace) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[22]
+ mi := &file_aether_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4994,7 +5374,7 @@ func (x *SwitchWorkspace) ProtoReflect() protoreflect.Message {
// Deprecated: Use SwitchWorkspace.ProtoReflect.Descriptor instead.
func (*SwitchWorkspace) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{22}
+ return file_aether_proto_rawDescGZIP(), []int{24}
}
func (x *SwitchWorkspace) GetNewWorkspaceId() string {
@@ -5045,7 +5425,7 @@ type KVOperation struct {
func (x *KVOperation) Reset() {
*x = KVOperation{}
- mi := &file_aether_proto_msgTypes[23]
+ mi := &file_aether_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5057,7 +5437,7 @@ func (x *KVOperation) String() string {
func (*KVOperation) ProtoMessage() {}
func (x *KVOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[23]
+ mi := &file_aether_proto_msgTypes[25]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5070,7 +5450,7 @@ func (x *KVOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use KVOperation.ProtoReflect.Descriptor instead.
func (*KVOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{23}
+ return file_aether_proto_rawDescGZIP(), []int{25}
}
func (x *KVOperation) GetOp() KVOperation_OpType {
@@ -5209,7 +5589,7 @@ type KVResponse struct {
func (x *KVResponse) Reset() {
*x = KVResponse{}
- mi := &file_aether_proto_msgTypes[24]
+ mi := &file_aether_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5221,7 +5601,7 @@ func (x *KVResponse) String() string {
func (*KVResponse) ProtoMessage() {}
func (x *KVResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[24]
+ mi := &file_aether_proto_msgTypes[26]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5234,7 +5614,7 @@ func (x *KVResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use KVResponse.ProtoReflect.Descriptor instead.
func (*KVResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{24}
+ return file_aether_proto_rawDescGZIP(), []int{26}
}
func (x *KVResponse) GetSuccess() bool {
@@ -5322,13 +5702,22 @@ type IncomingMessage struct {
// for, distinct from the sending identity in source_topic. Empty for direct
// (non-OBO) sends. See MessageEnvelope.on_behalf_subject.
OnBehalfSubject *PrincipalRef `protobuf:"bytes,5,opt,name=on_behalf_subject,json=onBehalfSubject,proto3" json:"on_behalf_subject,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // Gateway-authored receipt from SendMessage.checked_access. Never populated
+ // from the application payload.
+ AccessReceipt *AccessDecisionReceipt `protobuf:"bytes,6,opt,name=access_receipt,json=accessReceipt,proto3" json:"access_receipt,omitempty"`
+ // Gateway-derived authority continuation for this exact delivery target.
+ // Populated only when SendMessage.authority_continuation was explicitly set
+ // and the sender's resolved grant could delegate. Recipients can pass the
+ // authorization context to CheckAccess / BatchCheckAccess; root_grant_id,
+ // expiry, and delivery_target are trusted binding/audit metadata.
+ ForwardedAuthorization *ForwardedAuthorization `protobuf:"bytes,7,opt,name=forwarded_authorization,json=forwardedAuthorization,proto3" json:"forwarded_authorization,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *IncomingMessage) Reset() {
*x = IncomingMessage{}
- mi := &file_aether_proto_msgTypes[25]
+ mi := &file_aether_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5340,7 +5729,7 @@ func (x *IncomingMessage) String() string {
func (*IncomingMessage) ProtoMessage() {}
func (x *IncomingMessage) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[25]
+ mi := &file_aether_proto_msgTypes[27]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5353,7 +5742,7 @@ func (x *IncomingMessage) ProtoReflect() protoreflect.Message {
// Deprecated: Use IncomingMessage.ProtoReflect.Descriptor instead.
func (*IncomingMessage) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{25}
+ return file_aether_proto_rawDescGZIP(), []int{27}
}
func (x *IncomingMessage) GetSourceTopic() string {
@@ -5391,6 +5780,110 @@ func (x *IncomingMessage) GetOnBehalfSubject() *PrincipalRef {
return nil
}
+func (x *IncomingMessage) GetAccessReceipt() *AccessDecisionReceipt {
+ if x != nil {
+ return x.AccessReceipt
+ }
+ return nil
+}
+
+func (x *IncomingMessage) GetForwardedAuthorization() *ForwardedAuthorization {
+ if x != nil {
+ return x.ForwardedAuthorization
+ }
+ return nil
+}
+
+// Trusted authorization continuation carried outside the application payload.
+// The child grant is non-delegable, scope-attenuated to its parent, short-lived,
+// and linked into the parent's revocation cascade.
+type ForwardedAuthorization struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Authorization *AuthorizationContext `protobuf:"bytes,1,opt,name=authorization,proto3" json:"authorization,omitempty"`
+ RootGrantId string `protobuf:"bytes,2,opt,name=root_grant_id,json=rootGrantId,proto3" json:"root_grant_id,omitempty"`
+ ExpiresAtMs int64 `protobuf:"varint,3,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"`
+ DeliveryTarget string `protobuf:"bytes,4,opt,name=delivery_target,json=deliveryTarget,proto3" json:"delivery_target,omitempty"`
+ // Empty only for a reusable service continuation using INHERIT_PARENT.
+ BindingId string `protobuf:"bytes,5,opt,name=binding_id,json=bindingId,proto3" json:"binding_id,omitempty"`
+ // Gateway-authored projection of the effective child scope. Recipients use
+ // this to enforce their local, server-owned invocation authority profile.
+ Scope *AuthorityContinuationScope `protobuf:"bytes,6,opt,name=scope,proto3" json:"scope,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ForwardedAuthorization) Reset() {
+ *x = ForwardedAuthorization{}
+ mi := &file_aether_proto_msgTypes[28]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ForwardedAuthorization) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForwardedAuthorization) ProtoMessage() {}
+
+func (x *ForwardedAuthorization) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[28]
+ 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 ForwardedAuthorization.ProtoReflect.Descriptor instead.
+func (*ForwardedAuthorization) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{28}
+}
+
+func (x *ForwardedAuthorization) GetAuthorization() *AuthorizationContext {
+ if x != nil {
+ return x.Authorization
+ }
+ return nil
+}
+
+func (x *ForwardedAuthorization) GetRootGrantId() string {
+ if x != nil {
+ return x.RootGrantId
+ }
+ return ""
+}
+
+func (x *ForwardedAuthorization) GetExpiresAtMs() int64 {
+ if x != nil {
+ return x.ExpiresAtMs
+ }
+ return 0
+}
+
+func (x *ForwardedAuthorization) GetDeliveryTarget() string {
+ if x != nil {
+ return x.DeliveryTarget
+ }
+ return ""
+}
+
+func (x *ForwardedAuthorization) GetBindingId() string {
+ if x != nil {
+ return x.BindingId
+ }
+ return ""
+}
+
+func (x *ForwardedAuthorization) GetScope() *AuthorityContinuationScope {
+ if x != nil {
+ return x.Scope
+ }
+ return nil
+}
+
type ConfigSnapshot struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Legacy fields. The server stops auto-populating these as part of the
@@ -5416,7 +5909,7 @@ type ConfigSnapshot struct {
func (x *ConfigSnapshot) Reset() {
*x = ConfigSnapshot{}
- mi := &file_aether_proto_msgTypes[26]
+ mi := &file_aether_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5428,7 +5921,7 @@ func (x *ConfigSnapshot) String() string {
func (*ConfigSnapshot) ProtoMessage() {}
func (x *ConfigSnapshot) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[26]
+ mi := &file_aether_proto_msgTypes[29]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5441,7 +5934,7 @@ func (x *ConfigSnapshot) ProtoReflect() protoreflect.Message {
// Deprecated: Use ConfigSnapshot.ProtoReflect.Descriptor instead.
func (*ConfigSnapshot) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{26}
+ return file_aether_proto_rawDescGZIP(), []int{29}
}
// Deprecated: Marked as deprecated in aether.proto.
@@ -5491,7 +5984,7 @@ type Signal struct {
func (x *Signal) Reset() {
*x = Signal{}
- mi := &file_aether_proto_msgTypes[27]
+ mi := &file_aether_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5503,7 +5996,7 @@ func (x *Signal) String() string {
func (*Signal) ProtoMessage() {}
func (x *Signal) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[27]
+ mi := &file_aether_proto_msgTypes[30]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5516,7 +6009,7 @@ func (x *Signal) ProtoReflect() protoreflect.Message {
// Deprecated: Use Signal.ProtoReflect.Descriptor instead.
func (*Signal) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{27}
+ return file_aether_proto_rawDescGZIP(), []int{30}
}
func (x *Signal) GetType() Signal_SignalType {
@@ -5546,7 +6039,7 @@ type ErrorResponse struct {
func (x *ErrorResponse) Reset() {
*x = ErrorResponse{}
- mi := &file_aether_proto_msgTypes[28]
+ mi := &file_aether_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5558,7 +6051,7 @@ func (x *ErrorResponse) String() string {
func (*ErrorResponse) ProtoMessage() {}
func (x *ErrorResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[28]
+ mi := &file_aether_proto_msgTypes[31]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5571,7 +6064,7 @@ func (x *ErrorResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ErrorResponse.ProtoReflect.Descriptor instead.
func (*ErrorResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{28}
+ return file_aether_proto_rawDescGZIP(), []int{31}
}
func (x *ErrorResponse) GetCode() string {
@@ -5642,7 +6135,7 @@ type RetryPolicy struct {
func (x *RetryPolicy) Reset() {
*x = RetryPolicy{}
- mi := &file_aether_proto_msgTypes[29]
+ mi := &file_aether_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5654,7 +6147,7 @@ func (x *RetryPolicy) String() string {
func (*RetryPolicy) ProtoMessage() {}
func (x *RetryPolicy) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[29]
+ mi := &file_aether_proto_msgTypes[32]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5667,7 +6160,7 @@ func (x *RetryPolicy) ProtoReflect() protoreflect.Message {
// Deprecated: Use RetryPolicy.ProtoReflect.Descriptor instead.
func (*RetryPolicy) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{29}
+ return file_aether_proto_rawDescGZIP(), []int{32}
}
func (x *RetryPolicy) GetMaxAttempts() int32 {
@@ -5748,7 +6241,7 @@ type TaskCompletionEvent struct {
func (x *TaskCompletionEvent) Reset() {
*x = TaskCompletionEvent{}
- mi := &file_aether_proto_msgTypes[30]
+ mi := &file_aether_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5760,7 +6253,7 @@ func (x *TaskCompletionEvent) String() string {
func (*TaskCompletionEvent) ProtoMessage() {}
func (x *TaskCompletionEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[30]
+ mi := &file_aether_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5773,7 +6266,7 @@ func (x *TaskCompletionEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskCompletionEvent.ProtoReflect.Descriptor instead.
func (*TaskCompletionEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{30}
+ return file_aether_proto_rawDescGZIP(), []int{33}
}
func (x *TaskCompletionEvent) GetEnabled() bool {
@@ -5858,13 +6351,34 @@ type CreateTaskRequest struct {
// Optional "feed B" config: emit a domain event onto event::* when this task
// reaches a (selected) terminal status. Absent/disabled = no emission.
CompletionEvent *TaskCompletionEvent `protobuf:"bytes,19,opt,name=completion_event,json=completionEvent,proto3" json:"completion_event,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // Optional native parent for a nested task created by a long-lived worker.
+ // The gateway accepts an explicit value only when the caller is the active
+ // parent task's assigned execution identity. This is a request-scoped binding:
+ // it may select a different assigned task than the connection's startup/task-
+ // token association. Empty preserves connection-associated parent inference.
+ ParentTaskId string `protobuf:"bytes,20,opt,name=parent_task_id,json=parentTaskId,proto3" json:"parent_task_id,omitempty"`
+ // TARGETED mode only. QUEUE persists the task for delivery when the exact
+ // static worker reconnects, without requiring an orchestration registry
+ // entry. REJECT fails task creation while the worker is absent.
+ TargetOfflinePolicy TargetOfflinePolicy `protobuf:"varint,21,opt,name=target_offline_policy,json=targetOfflinePolicy,proto3,enum=aether.v1.TargetOfflinePolicy" json:"target_offline_policy,omitempty"`
+ // Minimum delegation capacity the task's final execution identity must
+ // retain after task-authority setup. Currently 0 or 1. Set to 1 when the
+ // worker must perform one explicit downstream authorization continuation
+ // (for example, Sahara querying the tool catalog under the user's authority).
+ // In POOL mode the gateway reserves the additional anchor-to-assignee hop.
+ RequiredDownstreamAuthorityHops uint32 `protobuf:"varint,22,opt,name=required_downstream_authority_hops,json=requiredDownstreamAuthorityHops,proto3" json:"required_downstream_authority_hops,omitempty"`
+ // WorkflowEngine-only authority audience binding. The gateway accepts this
+ // field only from the authenticated WorkflowEngine principal and requires it
+ // to match a workflow_schedule audience on authorization. Ordinary task
+ // creators must leave it empty.
+ OriginatingScheduleId string `protobuf:"bytes,23,opt,name=originating_schedule_id,json=originatingScheduleId,proto3" json:"originating_schedule_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *CreateTaskRequest) Reset() {
*x = CreateTaskRequest{}
- mi := &file_aether_proto_msgTypes[31]
+ mi := &file_aether_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -5876,7 +6390,7 @@ func (x *CreateTaskRequest) String() string {
func (*CreateTaskRequest) ProtoMessage() {}
func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[31]
+ mi := &file_aether_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -5889,7 +6403,7 @@ func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateTaskRequest.ProtoReflect.Descriptor instead.
func (*CreateTaskRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{31}
+ return file_aether_proto_rawDescGZIP(), []int{34}
}
func (x *CreateTaskRequest) GetTaskType() string {
@@ -6025,6 +6539,34 @@ func (x *CreateTaskRequest) GetCompletionEvent() *TaskCompletionEvent {
return nil
}
+func (x *CreateTaskRequest) GetParentTaskId() string {
+ if x != nil {
+ return x.ParentTaskId
+ }
+ return ""
+}
+
+func (x *CreateTaskRequest) GetTargetOfflinePolicy() TargetOfflinePolicy {
+ if x != nil {
+ return x.TargetOfflinePolicy
+ }
+ return TargetOfflinePolicy_TARGET_OFFLINE_POLICY_UNSPECIFIED
+}
+
+func (x *CreateTaskRequest) GetRequiredDownstreamAuthorityHops() uint32 {
+ if x != nil {
+ return x.RequiredDownstreamAuthorityHops
+ }
+ return 0
+}
+
+func (x *CreateTaskRequest) GetOriginatingScheduleId() string {
+ if x != nil {
+ return x.OriginatingScheduleId
+ }
+ return ""
+}
+
// CreateTaskResponse is sent in response to CreateTaskRequest when the
// request carries a non-empty request_id. Gives the creator the server-
// assigned task_id so it can later COMPLETE/FAIL/CANCEL the task.
@@ -6069,7 +6611,7 @@ type CreateTaskResponse struct {
func (x *CreateTaskResponse) Reset() {
*x = CreateTaskResponse{}
- mi := &file_aether_proto_msgTypes[32]
+ mi := &file_aether_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6081,7 +6623,7 @@ func (x *CreateTaskResponse) String() string {
func (*CreateTaskResponse) ProtoMessage() {}
func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[32]
+ mi := &file_aether_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6094,7 +6636,7 @@ func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateTaskResponse.ProtoReflect.Descriptor instead.
func (*CreateTaskResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{32}
+ return file_aether_proto_rawDescGZIP(), []int{35}
}
func (x *CreateTaskResponse) GetSuccess() bool {
@@ -6183,13 +6725,18 @@ type TaskAssignment struct {
CheckpointKey string `protobuf:"bytes,13,opt,name=checkpoint_key,json=checkpointKey,proto3" json:"checkpoint_key,omitempty"`
// Hibernation rehydration: session id to resume. Empty = fresh session.
ResumeSessionId string `protobuf:"bytes,14,opt,name=resume_session_id,json=resumeSessionId,proto3" json:"resume_session_id,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // Task-scoped on-behalf-of authority prepared for the assigned executor.
+ // The grant is audience-bound to this assignee/task and is revoked with the
+ // task lifecycle. It is delivered on the typed execution plane rather than
+ // requiring workers to parse server-enriched metadata.
+ Authorization *AuthorizationContext `protobuf:"bytes,15,opt,name=authorization,proto3" json:"authorization,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *TaskAssignment) Reset() {
*x = TaskAssignment{}
- mi := &file_aether_proto_msgTypes[33]
+ mi := &file_aether_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6201,7 +6748,7 @@ func (x *TaskAssignment) String() string {
func (*TaskAssignment) ProtoMessage() {}
func (x *TaskAssignment) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[33]
+ mi := &file_aether_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6214,7 +6761,7 @@ func (x *TaskAssignment) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskAssignment.ProtoReflect.Descriptor instead.
func (*TaskAssignment) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{33}
+ return file_aether_proto_rawDescGZIP(), []int{36}
}
func (x *TaskAssignment) GetTaskId() string {
@@ -6315,6 +6862,13 @@ func (x *TaskAssignment) GetResumeSessionId() string {
return ""
}
+func (x *TaskAssignment) GetAuthorization() *AuthorizationContext {
+ if x != nil {
+ return x.Authorization
+ }
+ return nil
+}
+
// CheckpointOperation allows agents/tasks to save and load custom state.
// This is separate from message offset tracking (handled automatically by RabbitMQ).
// Use checkpoints to persist application-specific state that needs to survive restarts.
@@ -6340,7 +6894,7 @@ type CheckpointOperation struct {
func (x *CheckpointOperation) Reset() {
*x = CheckpointOperation{}
- mi := &file_aether_proto_msgTypes[34]
+ mi := &file_aether_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6352,7 +6906,7 @@ func (x *CheckpointOperation) String() string {
func (*CheckpointOperation) ProtoMessage() {}
func (x *CheckpointOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[34]
+ mi := &file_aether_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6365,7 +6919,7 @@ func (x *CheckpointOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use CheckpointOperation.ProtoReflect.Descriptor instead.
func (*CheckpointOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{34}
+ return file_aether_proto_rawDescGZIP(), []int{37}
}
func (x *CheckpointOperation) GetOp() CheckpointOperation_OpType {
@@ -6423,7 +6977,7 @@ type CheckpointResponse struct {
func (x *CheckpointResponse) Reset() {
*x = CheckpointResponse{}
- mi := &file_aether_proto_msgTypes[35]
+ mi := &file_aether_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6435,7 +6989,7 @@ func (x *CheckpointResponse) String() string {
func (*CheckpointResponse) ProtoMessage() {}
func (x *CheckpointResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[35]
+ mi := &file_aether_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6448,7 +7002,7 @@ func (x *CheckpointResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CheckpointResponse.ProtoReflect.Descriptor instead.
func (*CheckpointResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{35}
+ return file_aether_proto_rawDescGZIP(), []int{38}
}
func (x *CheckpointResponse) GetSuccess() bool {
@@ -6511,7 +7065,7 @@ type AdminQuery struct {
func (x *AdminQuery) Reset() {
*x = AdminQuery{}
- mi := &file_aether_proto_msgTypes[36]
+ mi := &file_aether_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6523,7 +7077,7 @@ func (x *AdminQuery) String() string {
func (*AdminQuery) ProtoMessage() {}
func (x *AdminQuery) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[36]
+ mi := &file_aether_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6536,7 +7090,7 @@ func (x *AdminQuery) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdminQuery.ProtoReflect.Descriptor instead.
func (*AdminQuery) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{36}
+ return file_aether_proto_rawDescGZIP(), []int{39}
}
func (x *AdminQuery) GetOp() AdminQuery_OpType {
@@ -6581,7 +7135,7 @@ type ConnectionFilter struct {
func (x *ConnectionFilter) Reset() {
*x = ConnectionFilter{}
- mi := &file_aether_proto_msgTypes[37]
+ mi := &file_aether_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6593,7 +7147,7 @@ func (x *ConnectionFilter) String() string {
func (*ConnectionFilter) ProtoMessage() {}
func (x *ConnectionFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[37]
+ mi := &file_aether_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6606,7 +7160,7 @@ func (x *ConnectionFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use ConnectionFilter.ProtoReflect.Descriptor instead.
func (*ConnectionFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{37}
+ return file_aether_proto_rawDescGZIP(), []int{40}
}
func (x *ConnectionFilter) GetType() PrincipalType {
@@ -6657,7 +7211,7 @@ type ConnectionInfo struct {
func (x *ConnectionInfo) Reset() {
*x = ConnectionInfo{}
- mi := &file_aether_proto_msgTypes[38]
+ mi := &file_aether_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6669,7 +7223,7 @@ func (x *ConnectionInfo) String() string {
func (*ConnectionInfo) ProtoMessage() {}
func (x *ConnectionInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[38]
+ mi := &file_aether_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6682,7 +7236,7 @@ func (x *ConnectionInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ConnectionInfo.ProtoReflect.Descriptor instead.
func (*ConnectionInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{38}
+ return file_aether_proto_rawDescGZIP(), []int{41}
}
func (x *ConnectionInfo) GetSessionId() string {
@@ -6783,7 +7337,7 @@ type AdminResponse struct {
func (x *AdminResponse) Reset() {
*x = AdminResponse{}
- mi := &file_aether_proto_msgTypes[39]
+ mi := &file_aether_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6795,7 +7349,7 @@ func (x *AdminResponse) String() string {
func (*AdminResponse) ProtoMessage() {}
func (x *AdminResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[39]
+ mi := &file_aether_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6808,7 +7362,7 @@ func (x *AdminResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AdminResponse.ProtoReflect.Descriptor instead.
func (*AdminResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{39}
+ return file_aether_proto_rawDescGZIP(), []int{42}
}
func (x *AdminResponse) GetSuccess() bool {
@@ -6888,7 +7442,7 @@ type HealthInfo struct {
func (x *HealthInfo) Reset() {
*x = HealthInfo{}
- mi := &file_aether_proto_msgTypes[40]
+ mi := &file_aether_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6900,7 +7454,7 @@ func (x *HealthInfo) String() string {
func (*HealthInfo) ProtoMessage() {}
func (x *HealthInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[40]
+ mi := &file_aether_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6913,7 +7467,7 @@ func (x *HealthInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use HealthInfo.ProtoReflect.Descriptor instead.
func (*HealthInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{40}
+ return file_aether_proto_rawDescGZIP(), []int{43}
}
func (x *HealthInfo) GetStatus() HealthStatus {
@@ -6957,7 +7511,7 @@ type HealthCheck struct {
func (x *HealthCheck) Reset() {
*x = HealthCheck{}
- mi := &file_aether_proto_msgTypes[41]
+ mi := &file_aether_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6969,7 +7523,7 @@ func (x *HealthCheck) String() string {
func (*HealthCheck) ProtoMessage() {}
func (x *HealthCheck) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[41]
+ mi := &file_aether_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6982,7 +7536,7 @@ func (x *HealthCheck) ProtoReflect() protoreflect.Message {
// Deprecated: Use HealthCheck.ProtoReflect.Descriptor instead.
func (*HealthCheck) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{41}
+ return file_aether_proto_rawDescGZIP(), []int{44}
}
func (x *HealthCheck) GetStatus() HealthCheckStatus {
@@ -7024,7 +7578,7 @@ type GatewayInfo struct {
func (x *GatewayInfo) Reset() {
*x = GatewayInfo{}
- mi := &file_aether_proto_msgTypes[42]
+ mi := &file_aether_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7036,7 +7590,7 @@ func (x *GatewayInfo) String() string {
func (*GatewayInfo) ProtoMessage() {}
func (x *GatewayInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[42]
+ mi := &file_aether_proto_msgTypes[45]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7049,7 +7603,7 @@ func (x *GatewayInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use GatewayInfo.ProtoReflect.Descriptor instead.
func (*GatewayInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{42}
+ return file_aether_proto_rawDescGZIP(), []int{45}
}
func (x *GatewayInfo) GetGatewayId() string {
@@ -7137,7 +7691,7 @@ type GatewayStats struct {
func (x *GatewayStats) Reset() {
*x = GatewayStats{}
- mi := &file_aether_proto_msgTypes[43]
+ mi := &file_aether_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7149,7 +7703,7 @@ func (x *GatewayStats) String() string {
func (*GatewayStats) ProtoMessage() {}
func (x *GatewayStats) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[43]
+ mi := &file_aether_proto_msgTypes[46]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7162,7 +7716,7 @@ func (x *GatewayStats) ProtoReflect() protoreflect.Message {
// Deprecated: Use GatewayStats.ProtoReflect.Descriptor instead.
func (*GatewayStats) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{43}
+ return file_aether_proto_rawDescGZIP(), []int{46}
}
func (x *GatewayStats) GetAgentConnections() int32 {
@@ -7299,7 +7853,7 @@ type SessionOperation struct {
func (x *SessionOperation) Reset() {
*x = SessionOperation{}
- mi := &file_aether_proto_msgTypes[44]
+ mi := &file_aether_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7311,7 +7865,7 @@ func (x *SessionOperation) String() string {
func (*SessionOperation) ProtoMessage() {}
func (x *SessionOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[44]
+ mi := &file_aether_proto_msgTypes[47]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7324,7 +7878,7 @@ func (x *SessionOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionOperation.ProtoReflect.Descriptor instead.
func (*SessionOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{44}
+ return file_aether_proto_rawDescGZIP(), []int{47}
}
func (x *SessionOperation) GetOp() SessionOperation_OpType {
@@ -7391,7 +7945,7 @@ type SessionOperationResponse struct {
func (x *SessionOperationResponse) Reset() {
*x = SessionOperationResponse{}
- mi := &file_aether_proto_msgTypes[45]
+ mi := &file_aether_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7403,7 +7957,7 @@ func (x *SessionOperationResponse) String() string {
func (*SessionOperationResponse) ProtoMessage() {}
func (x *SessionOperationResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[45]
+ mi := &file_aether_proto_msgTypes[48]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7416,7 +7970,7 @@ func (x *SessionOperationResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionOperationResponse.ProtoReflect.Descriptor instead.
func (*SessionOperationResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{45}
+ return file_aether_proto_rawDescGZIP(), []int{48}
}
func (x *SessionOperationResponse) GetSuccess() bool {
@@ -7487,7 +8041,7 @@ type TaskQuery struct {
func (x *TaskQuery) Reset() {
*x = TaskQuery{}
- mi := &file_aether_proto_msgTypes[46]
+ mi := &file_aether_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7499,7 +8053,7 @@ func (x *TaskQuery) String() string {
func (*TaskQuery) ProtoMessage() {}
func (x *TaskQuery) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[46]
+ mi := &file_aether_proto_msgTypes[49]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7512,7 +8066,7 @@ func (x *TaskQuery) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskQuery.ProtoReflect.Descriptor instead.
func (*TaskQuery) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{46}
+ return file_aether_proto_rawDescGZIP(), []int{49}
}
func (x *TaskQuery) GetOp() TaskQuery_OpType {
@@ -7610,7 +8164,7 @@ type TaskFilter struct {
func (x *TaskFilter) Reset() {
*x = TaskFilter{}
- mi := &file_aether_proto_msgTypes[47]
+ mi := &file_aether_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7622,7 +8176,7 @@ func (x *TaskFilter) String() string {
func (*TaskFilter) ProtoMessage() {}
func (x *TaskFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[47]
+ mi := &file_aether_proto_msgTypes[50]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7635,7 +8189,7 @@ func (x *TaskFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskFilter.ProtoReflect.Descriptor instead.
func (*TaskFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{47}
+ return file_aether_proto_rawDescGZIP(), []int{50}
}
func (x *TaskFilter) GetStatus() TaskStatus {
@@ -7875,7 +8429,7 @@ type TaskInfo struct {
func (x *TaskInfo) Reset() {
*x = TaskInfo{}
- mi := &file_aether_proto_msgTypes[48]
+ mi := &file_aether_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -7887,7 +8441,7 @@ func (x *TaskInfo) String() string {
func (*TaskInfo) ProtoMessage() {}
func (x *TaskInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[48]
+ mi := &file_aether_proto_msgTypes[51]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -7900,7 +8454,7 @@ func (x *TaskInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskInfo.ProtoReflect.Descriptor instead.
func (*TaskInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{48}
+ return file_aether_proto_rawDescGZIP(), []int{51}
}
func (x *TaskInfo) GetTaskId() string {
@@ -8166,7 +8720,7 @@ type TaskQueryResponse struct {
func (x *TaskQueryResponse) Reset() {
*x = TaskQueryResponse{}
- mi := &file_aether_proto_msgTypes[49]
+ mi := &file_aether_proto_msgTypes[52]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8178,7 +8732,7 @@ func (x *TaskQueryResponse) String() string {
func (*TaskQueryResponse) ProtoMessage() {}
func (x *TaskQueryResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[49]
+ mi := &file_aether_proto_msgTypes[52]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8191,7 +8745,7 @@ func (x *TaskQueryResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskQueryResponse.ProtoReflect.Descriptor instead.
func (*TaskQueryResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{49}
+ return file_aether_proto_rawDescGZIP(), []int{52}
}
func (x *TaskQueryResponse) GetSuccess() bool {
@@ -8269,7 +8823,7 @@ type TaskOperation struct {
func (x *TaskOperation) Reset() {
*x = TaskOperation{}
- mi := &file_aether_proto_msgTypes[50]
+ mi := &file_aether_proto_msgTypes[53]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8281,7 +8835,7 @@ func (x *TaskOperation) String() string {
func (*TaskOperation) ProtoMessage() {}
func (x *TaskOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[50]
+ mi := &file_aether_proto_msgTypes[53]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8294,7 +8848,7 @@ func (x *TaskOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskOperation.ProtoReflect.Descriptor instead.
func (*TaskOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{50}
+ return file_aether_proto_rawDescGZIP(), []int{53}
}
func (x *TaskOperation) GetOp() TaskOperation_OpType {
@@ -8373,7 +8927,7 @@ type WaitSpec struct {
func (x *WaitSpec) Reset() {
*x = WaitSpec{}
- mi := &file_aether_proto_msgTypes[51]
+ mi := &file_aether_proto_msgTypes[54]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8385,7 +8939,7 @@ func (x *WaitSpec) String() string {
func (*WaitSpec) ProtoMessage() {}
func (x *WaitSpec) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[51]
+ mi := &file_aether_proto_msgTypes[54]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8398,7 +8952,7 @@ func (x *WaitSpec) ProtoReflect() protoreflect.Message {
// Deprecated: Use WaitSpec.ProtoReflect.Descriptor instead.
func (*WaitSpec) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{51}
+ return file_aether_proto_rawDescGZIP(), []int{54}
}
func (x *WaitSpec) GetReason() WaitReason {
@@ -8490,7 +9044,7 @@ type HibernationDescriptor struct {
func (x *HibernationDescriptor) Reset() {
*x = HibernationDescriptor{}
- mi := &file_aether_proto_msgTypes[52]
+ mi := &file_aether_proto_msgTypes[55]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8502,7 +9056,7 @@ func (x *HibernationDescriptor) String() string {
func (*HibernationDescriptor) ProtoMessage() {}
func (x *HibernationDescriptor) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[52]
+ mi := &file_aether_proto_msgTypes[55]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8515,7 +9069,7 @@ func (x *HibernationDescriptor) ProtoReflect() protoreflect.Message {
// Deprecated: Use HibernationDescriptor.ProtoReflect.Descriptor instead.
func (*HibernationDescriptor) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{52}
+ return file_aether_proto_rawDescGZIP(), []int{55}
}
func (x *HibernationDescriptor) GetCheckpointKey() string {
@@ -8564,7 +9118,7 @@ type TaskOperationResponse struct {
func (x *TaskOperationResponse) Reset() {
*x = TaskOperationResponse{}
- mi := &file_aether_proto_msgTypes[53]
+ mi := &file_aether_proto_msgTypes[56]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8576,7 +9130,7 @@ func (x *TaskOperationResponse) String() string {
func (*TaskOperationResponse) ProtoMessage() {}
func (x *TaskOperationResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[53]
+ mi := &file_aether_proto_msgTypes[56]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8589,7 +9143,7 @@ func (x *TaskOperationResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskOperationResponse.ProtoReflect.Descriptor instead.
func (*TaskOperationResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{53}
+ return file_aether_proto_rawDescGZIP(), []int{56}
}
func (x *TaskOperationResponse) GetSuccess() bool {
@@ -8654,7 +9208,7 @@ type WorkspaceOperation struct {
func (x *WorkspaceOperation) Reset() {
*x = WorkspaceOperation{}
- mi := &file_aether_proto_msgTypes[54]
+ mi := &file_aether_proto_msgTypes[57]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8666,7 +9220,7 @@ func (x *WorkspaceOperation) String() string {
func (*WorkspaceOperation) ProtoMessage() {}
func (x *WorkspaceOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[54]
+ mi := &file_aether_proto_msgTypes[57]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8679,7 +9233,7 @@ func (x *WorkspaceOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkspaceOperation.ProtoReflect.Descriptor instead.
func (*WorkspaceOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{54}
+ return file_aether_proto_rawDescGZIP(), []int{57}
}
func (x *WorkspaceOperation) GetOp() WorkspaceOperation_OpType {
@@ -8730,7 +9284,7 @@ type WorkspaceFilter struct {
func (x *WorkspaceFilter) Reset() {
*x = WorkspaceFilter{}
- mi := &file_aether_proto_msgTypes[55]
+ mi := &file_aether_proto_msgTypes[58]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8742,7 +9296,7 @@ func (x *WorkspaceFilter) String() string {
func (*WorkspaceFilter) ProtoMessage() {}
func (x *WorkspaceFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[55]
+ mi := &file_aether_proto_msgTypes[58]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8755,7 +9309,7 @@ func (x *WorkspaceFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkspaceFilter.ProtoReflect.Descriptor instead.
func (*WorkspaceFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{55}
+ return file_aether_proto_rawDescGZIP(), []int{58}
}
func (x *WorkspaceFilter) GetTenantId() string {
@@ -8801,7 +9355,7 @@ type WorkspaceInfo struct {
func (x *WorkspaceInfo) Reset() {
*x = WorkspaceInfo{}
- mi := &file_aether_proto_msgTypes[56]
+ mi := &file_aether_proto_msgTypes[59]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8813,7 +9367,7 @@ func (x *WorkspaceInfo) String() string {
func (*WorkspaceInfo) ProtoMessage() {}
func (x *WorkspaceInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[56]
+ mi := &file_aether_proto_msgTypes[59]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8826,7 +9380,7 @@ func (x *WorkspaceInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkspaceInfo.ProtoReflect.Descriptor instead.
func (*WorkspaceInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{56}
+ return file_aether_proto_rawDescGZIP(), []int{59}
}
func (x *WorkspaceInfo) GetWorkspaceId() string {
@@ -8931,7 +9485,7 @@ type WorkspaceResponse struct {
func (x *WorkspaceResponse) Reset() {
*x = WorkspaceResponse{}
- mi := &file_aether_proto_msgTypes[57]
+ mi := &file_aether_proto_msgTypes[60]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -8943,7 +9497,7 @@ func (x *WorkspaceResponse) String() string {
func (*WorkspaceResponse) ProtoMessage() {}
func (x *WorkspaceResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[57]
+ mi := &file_aether_proto_msgTypes[60]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -8956,7 +9510,7 @@ func (x *WorkspaceResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkspaceResponse.ProtoReflect.Descriptor instead.
func (*WorkspaceResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{57}
+ return file_aether_proto_rawDescGZIP(), []int{60}
}
func (x *WorkspaceResponse) GetSuccess() bool {
@@ -9030,7 +9584,7 @@ type MessageFlowInfo struct {
func (x *MessageFlowInfo) Reset() {
*x = MessageFlowInfo{}
- mi := &file_aether_proto_msgTypes[58]
+ mi := &file_aether_proto_msgTypes[61]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9042,7 +9596,7 @@ func (x *MessageFlowInfo) String() string {
func (*MessageFlowInfo) ProtoMessage() {}
func (x *MessageFlowInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[58]
+ mi := &file_aether_proto_msgTypes[61]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9055,7 +9609,7 @@ func (x *MessageFlowInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use MessageFlowInfo.ProtoReflect.Descriptor instead.
func (*MessageFlowInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{58}
+ return file_aether_proto_rawDescGZIP(), []int{61}
}
func (x *MessageFlowInfo) GetWorkspaceId() string {
@@ -9103,7 +9657,7 @@ type FlowNode struct {
func (x *FlowNode) Reset() {
*x = FlowNode{}
- mi := &file_aether_proto_msgTypes[59]
+ mi := &file_aether_proto_msgTypes[62]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9115,7 +9669,7 @@ func (x *FlowNode) String() string {
func (*FlowNode) ProtoMessage() {}
func (x *FlowNode) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[59]
+ mi := &file_aether_proto_msgTypes[62]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9128,7 +9682,7 @@ func (x *FlowNode) ProtoReflect() protoreflect.Message {
// Deprecated: Use FlowNode.ProtoReflect.Descriptor instead.
func (*FlowNode) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{59}
+ return file_aether_proto_rawDescGZIP(), []int{62}
}
func (x *FlowNode) GetId() string {
@@ -9194,7 +9748,7 @@ type FlowEdge struct {
func (x *FlowEdge) Reset() {
*x = FlowEdge{}
- mi := &file_aether_proto_msgTypes[60]
+ mi := &file_aether_proto_msgTypes[63]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9206,7 +9760,7 @@ func (x *FlowEdge) String() string {
func (*FlowEdge) ProtoMessage() {}
func (x *FlowEdge) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[60]
+ mi := &file_aether_proto_msgTypes[63]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9219,7 +9773,7 @@ func (x *FlowEdge) ProtoReflect() protoreflect.Message {
// Deprecated: Use FlowEdge.ProtoReflect.Descriptor instead.
func (*FlowEdge) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{60}
+ return file_aether_proto_rawDescGZIP(), []int{63}
}
func (x *FlowEdge) GetFrom() string {
@@ -9280,7 +9834,7 @@ type AgentOperation struct {
func (x *AgentOperation) Reset() {
*x = AgentOperation{}
- mi := &file_aether_proto_msgTypes[61]
+ mi := &file_aether_proto_msgTypes[64]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9292,7 +9846,7 @@ func (x *AgentOperation) String() string {
func (*AgentOperation) ProtoMessage() {}
func (x *AgentOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[61]
+ mi := &file_aether_proto_msgTypes[64]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9305,7 +9859,7 @@ func (x *AgentOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentOperation.ProtoReflect.Descriptor instead.
func (*AgentOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{61}
+ return file_aether_proto_rawDescGZIP(), []int{64}
}
func (x *AgentOperation) GetOp() AgentOperation_OpType {
@@ -9362,7 +9916,7 @@ type AgentFilter struct {
func (x *AgentFilter) Reset() {
*x = AgentFilter{}
- mi := &file_aether_proto_msgTypes[62]
+ mi := &file_aether_proto_msgTypes[65]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9374,7 +9928,7 @@ func (x *AgentFilter) String() string {
func (*AgentFilter) ProtoMessage() {}
func (x *AgentFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[62]
+ mi := &file_aether_proto_msgTypes[65]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9387,7 +9941,7 @@ func (x *AgentFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentFilter.ProtoReflect.Descriptor instead.
func (*AgentFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{62}
+ return file_aether_proto_rawDescGZIP(), []int{65}
}
func (x *AgentFilter) GetOrchestratorProfile() string {
@@ -9443,7 +9997,7 @@ type AgentRegistrationInfo struct {
func (x *AgentRegistrationInfo) Reset() {
*x = AgentRegistrationInfo{}
- mi := &file_aether_proto_msgTypes[63]
+ mi := &file_aether_proto_msgTypes[66]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9455,7 +10009,7 @@ func (x *AgentRegistrationInfo) String() string {
func (*AgentRegistrationInfo) ProtoMessage() {}
func (x *AgentRegistrationInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[63]
+ mi := &file_aether_proto_msgTypes[66]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9468,7 +10022,7 @@ func (x *AgentRegistrationInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentRegistrationInfo.ProtoReflect.Descriptor instead.
func (*AgentRegistrationInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{63}
+ return file_aether_proto_rawDescGZIP(), []int{66}
}
func (x *AgentRegistrationInfo) GetImplementation() string {
@@ -9555,7 +10109,7 @@ type AgentResourceSchemaEntry struct {
func (x *AgentResourceSchemaEntry) Reset() {
*x = AgentResourceSchemaEntry{}
- mi := &file_aether_proto_msgTypes[64]
+ mi := &file_aether_proto_msgTypes[67]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9567,7 +10121,7 @@ func (x *AgentResourceSchemaEntry) String() string {
func (*AgentResourceSchemaEntry) ProtoMessage() {}
func (x *AgentResourceSchemaEntry) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[64]
+ mi := &file_aether_proto_msgTypes[67]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9580,7 +10134,7 @@ func (x *AgentResourceSchemaEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentResourceSchemaEntry.ProtoReflect.Descriptor instead.
func (*AgentResourceSchemaEntry) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{64}
+ return file_aether_proto_rawDescGZIP(), []int{67}
}
func (x *AgentResourceSchemaEntry) GetResourceTypePrefix() string {
@@ -9617,7 +10171,7 @@ type AgentLaunchParams struct {
func (x *AgentLaunchParams) Reset() {
*x = AgentLaunchParams{}
- mi := &file_aether_proto_msgTypes[65]
+ mi := &file_aether_proto_msgTypes[68]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9629,7 +10183,7 @@ func (x *AgentLaunchParams) String() string {
func (*AgentLaunchParams) ProtoMessage() {}
func (x *AgentLaunchParams) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[65]
+ mi := &file_aether_proto_msgTypes[68]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9642,7 +10196,7 @@ func (x *AgentLaunchParams) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentLaunchParams.ProtoReflect.Descriptor instead.
func (*AgentLaunchParams) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{65}
+ return file_aether_proto_rawDescGZIP(), []int{68}
}
func (x *AgentLaunchParams) GetSpecifier() string {
@@ -9679,7 +10233,7 @@ type OrchestratorInfo struct {
func (x *OrchestratorInfo) Reset() {
*x = OrchestratorInfo{}
- mi := &file_aether_proto_msgTypes[66]
+ mi := &file_aether_proto_msgTypes[69]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9691,7 +10245,7 @@ func (x *OrchestratorInfo) String() string {
func (*OrchestratorInfo) ProtoMessage() {}
func (x *OrchestratorInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[66]
+ mi := &file_aether_proto_msgTypes[69]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9704,7 +10258,7 @@ func (x *OrchestratorInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use OrchestratorInfo.ProtoReflect.Descriptor instead.
func (*OrchestratorInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{66}
+ return file_aether_proto_rawDescGZIP(), []int{69}
}
func (x *OrchestratorInfo) GetOrchestratorId() string {
@@ -9740,7 +10294,7 @@ type AgentLaunchResult struct {
func (x *AgentLaunchResult) Reset() {
*x = AgentLaunchResult{}
- mi := &file_aether_proto_msgTypes[67]
+ mi := &file_aether_proto_msgTypes[70]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9752,7 +10306,7 @@ func (x *AgentLaunchResult) String() string {
func (*AgentLaunchResult) ProtoMessage() {}
func (x *AgentLaunchResult) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[67]
+ mi := &file_aether_proto_msgTypes[70]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9765,7 +10319,7 @@ func (x *AgentLaunchResult) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentLaunchResult.ProtoReflect.Descriptor instead.
func (*AgentLaunchResult) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{67}
+ return file_aether_proto_rawDescGZIP(), []int{70}
}
func (x *AgentLaunchResult) GetTaskId() string {
@@ -9809,7 +10363,7 @@ type AgentResponse struct {
func (x *AgentResponse) Reset() {
*x = AgentResponse{}
- mi := &file_aether_proto_msgTypes[68]
+ mi := &file_aether_proto_msgTypes[71]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9821,7 +10375,7 @@ func (x *AgentResponse) String() string {
func (*AgentResponse) ProtoMessage() {}
func (x *AgentResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[68]
+ mi := &file_aether_proto_msgTypes[71]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9834,7 +10388,7 @@ func (x *AgentResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AgentResponse.ProtoReflect.Descriptor instead.
func (*AgentResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{68}
+ return file_aether_proto_rawDescGZIP(), []int{71}
}
func (x *AgentResponse) GetSuccess() bool {
@@ -9963,7 +10517,7 @@ type ACLOperation struct {
func (x *ACLOperation) Reset() {
*x = ACLOperation{}
- mi := &file_aether_proto_msgTypes[69]
+ mi := &file_aether_proto_msgTypes[72]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -9975,7 +10529,7 @@ func (x *ACLOperation) String() string {
func (*ACLOperation) ProtoMessage() {}
func (x *ACLOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[69]
+ mi := &file_aether_proto_msgTypes[72]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -9988,7 +10542,7 @@ func (x *ACLOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLOperation.ProtoReflect.Descriptor instead.
func (*ACLOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{69}
+ return file_aether_proto_rawDescGZIP(), []int{72}
}
func (x *ACLOperation) GetOp() ACLOperation_OpType {
@@ -10140,7 +10694,7 @@ type ACLRuleFilter struct {
func (x *ACLRuleFilter) Reset() {
*x = ACLRuleFilter{}
- mi := &file_aether_proto_msgTypes[70]
+ mi := &file_aether_proto_msgTypes[73]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10152,7 +10706,7 @@ func (x *ACLRuleFilter) String() string {
func (*ACLRuleFilter) ProtoMessage() {}
func (x *ACLRuleFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[70]
+ mi := &file_aether_proto_msgTypes[73]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10165,7 +10719,7 @@ func (x *ACLRuleFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRuleFilter.ProtoReflect.Descriptor instead.
func (*ACLRuleFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{70}
+ return file_aether_proto_rawDescGZIP(), []int{73}
}
func (x *ACLRuleFilter) GetPrincipalType() string {
@@ -10230,7 +10784,7 @@ type ACLAuditFilter struct {
func (x *ACLAuditFilter) Reset() {
*x = ACLAuditFilter{}
- mi := &file_aether_proto_msgTypes[71]
+ mi := &file_aether_proto_msgTypes[74]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10242,7 +10796,7 @@ func (x *ACLAuditFilter) String() string {
func (*ACLAuditFilter) ProtoMessage() {}
func (x *ACLAuditFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[71]
+ mi := &file_aether_proto_msgTypes[74]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10255,7 +10809,7 @@ func (x *ACLAuditFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAuditFilter.ProtoReflect.Descriptor instead.
func (*ACLAuditFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{71}
+ return file_aether_proto_rawDescGZIP(), []int{74}
}
func (x *ACLAuditFilter) GetStartTime() int64 {
@@ -10346,7 +10900,7 @@ type ACLGrantRequest struct {
func (x *ACLGrantRequest) Reset() {
*x = ACLGrantRequest{}
- mi := &file_aether_proto_msgTypes[72]
+ mi := &file_aether_proto_msgTypes[75]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10358,7 +10912,7 @@ func (x *ACLGrantRequest) String() string {
func (*ACLGrantRequest) ProtoMessage() {}
func (x *ACLGrantRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[72]
+ mi := &file_aether_proto_msgTypes[75]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10371,7 +10925,7 @@ func (x *ACLGrantRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLGrantRequest.ProtoReflect.Descriptor instead.
func (*ACLGrantRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{72}
+ return file_aether_proto_rawDescGZIP(), []int{75}
}
func (x *ACLGrantRequest) GetPrincipalType() string {
@@ -10443,7 +10997,7 @@ type ACLSetFallbackRequest struct {
func (x *ACLSetFallbackRequest) Reset() {
*x = ACLSetFallbackRequest{}
- mi := &file_aether_proto_msgTypes[73]
+ mi := &file_aether_proto_msgTypes[76]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10455,7 +11009,7 @@ func (x *ACLSetFallbackRequest) String() string {
func (*ACLSetFallbackRequest) ProtoMessage() {}
func (x *ACLSetFallbackRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[73]
+ mi := &file_aether_proto_msgTypes[76]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10468,7 +11022,7 @@ func (x *ACLSetFallbackRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLSetFallbackRequest.ProtoReflect.Descriptor instead.
func (*ACLSetFallbackRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{73}
+ return file_aether_proto_rawDescGZIP(), []int{76}
}
func (x *ACLSetFallbackRequest) GetRuleCategory() string {
@@ -10511,7 +11065,7 @@ type ACLAuthorityGrantFilter struct {
func (x *ACLAuthorityGrantFilter) Reset() {
*x = ACLAuthorityGrantFilter{}
- mi := &file_aether_proto_msgTypes[74]
+ mi := &file_aether_proto_msgTypes[77]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10523,7 +11077,7 @@ func (x *ACLAuthorityGrantFilter) String() string {
func (*ACLAuthorityGrantFilter) ProtoMessage() {}
func (x *ACLAuthorityGrantFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[74]
+ mi := &file_aether_proto_msgTypes[77]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10536,7 +11090,7 @@ func (x *ACLAuthorityGrantFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAuthorityGrantFilter.ProtoReflect.Descriptor instead.
func (*ACLAuthorityGrantFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{74}
+ return file_aether_proto_rawDescGZIP(), []int{77}
}
func (x *ACLAuthorityGrantFilter) GetRootGrantId() string {
@@ -10626,7 +11180,7 @@ type ACLAuthorityGrantResourceScopeEntry struct {
func (x *ACLAuthorityGrantResourceScopeEntry) Reset() {
*x = ACLAuthorityGrantResourceScopeEntry{}
- mi := &file_aether_proto_msgTypes[75]
+ mi := &file_aether_proto_msgTypes[78]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10638,7 +11192,7 @@ func (x *ACLAuthorityGrantResourceScopeEntry) String() string {
func (*ACLAuthorityGrantResourceScopeEntry) ProtoMessage() {}
func (x *ACLAuthorityGrantResourceScopeEntry) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[75]
+ mi := &file_aether_proto_msgTypes[78]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10651,7 +11205,7 @@ func (x *ACLAuthorityGrantResourceScopeEntry) ProtoReflect() protoreflect.Messag
// Deprecated: Use ACLAuthorityGrantResourceScopeEntry.ProtoReflect.Descriptor instead.
func (*ACLAuthorityGrantResourceScopeEntry) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{75}
+ return file_aether_proto_rawDescGZIP(), []int{78}
}
func (x *ACLAuthorityGrantResourceScopeEntry) GetResourceType() string {
@@ -10694,7 +11248,7 @@ type ACLAuthorityGrantRequest struct {
func (x *ACLAuthorityGrantRequest) Reset() {
*x = ACLAuthorityGrantRequest{}
- mi := &file_aether_proto_msgTypes[76]
+ mi := &file_aether_proto_msgTypes[79]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10706,7 +11260,7 @@ func (x *ACLAuthorityGrantRequest) String() string {
func (*ACLAuthorityGrantRequest) ProtoMessage() {}
func (x *ACLAuthorityGrantRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[76]
+ mi := &file_aether_proto_msgTypes[79]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10719,7 +11273,7 @@ func (x *ACLAuthorityGrantRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAuthorityGrantRequest.ProtoReflect.Descriptor instead.
func (*ACLAuthorityGrantRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{76}
+ return file_aether_proto_rawDescGZIP(), []int{79}
}
func (x *ACLAuthorityGrantRequest) GetSubject() *PrincipalRef {
@@ -10863,7 +11417,7 @@ type ACLRenewAuthorityGrantRequest struct {
func (x *ACLRenewAuthorityGrantRequest) Reset() {
*x = ACLRenewAuthorityGrantRequest{}
- mi := &file_aether_proto_msgTypes[77]
+ mi := &file_aether_proto_msgTypes[80]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10875,7 +11429,7 @@ func (x *ACLRenewAuthorityGrantRequest) String() string {
func (*ACLRenewAuthorityGrantRequest) ProtoMessage() {}
func (x *ACLRenewAuthorityGrantRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[77]
+ mi := &file_aether_proto_msgTypes[80]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10888,7 +11442,7 @@ func (x *ACLRenewAuthorityGrantRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRenewAuthorityGrantRequest.ProtoReflect.Descriptor instead.
func (*ACLRenewAuthorityGrantRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{77}
+ return file_aether_proto_rawDescGZIP(), []int{80}
}
func (x *ACLRenewAuthorityGrantRequest) GetGrantId() string {
@@ -10933,7 +11487,7 @@ type ACLRuleInfo struct {
func (x *ACLRuleInfo) Reset() {
*x = ACLRuleInfo{}
- mi := &file_aether_proto_msgTypes[78]
+ mi := &file_aether_proto_msgTypes[81]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -10945,7 +11499,7 @@ func (x *ACLRuleInfo) String() string {
func (*ACLRuleInfo) ProtoMessage() {}
func (x *ACLRuleInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[78]
+ mi := &file_aether_proto_msgTypes[81]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -10958,7 +11512,7 @@ func (x *ACLRuleInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRuleInfo.ProtoReflect.Descriptor instead.
func (*ACLRuleInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{78}
+ return file_aether_proto_rawDescGZIP(), []int{81}
}
func (x *ACLRuleInfo) GetRuleId() string {
@@ -11055,7 +11609,7 @@ type ACLFallbackPolicyInfo struct {
func (x *ACLFallbackPolicyInfo) Reset() {
*x = ACLFallbackPolicyInfo{}
- mi := &file_aether_proto_msgTypes[79]
+ mi := &file_aether_proto_msgTypes[82]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11067,7 +11621,7 @@ func (x *ACLFallbackPolicyInfo) String() string {
func (*ACLFallbackPolicyInfo) ProtoMessage() {}
func (x *ACLFallbackPolicyInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[79]
+ mi := &file_aether_proto_msgTypes[82]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11080,7 +11634,7 @@ func (x *ACLFallbackPolicyInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLFallbackPolicyInfo.ProtoReflect.Descriptor instead.
func (*ACLFallbackPolicyInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{79}
+ return file_aether_proto_rawDescGZIP(), []int{82}
}
func (x *ACLFallbackPolicyInfo) GetPolicyId() string {
@@ -11151,7 +11705,7 @@ type ACLAuditEntryInfo struct {
func (x *ACLAuditEntryInfo) Reset() {
*x = ACLAuditEntryInfo{}
- mi := &file_aether_proto_msgTypes[80]
+ mi := &file_aether_proto_msgTypes[83]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11163,7 +11717,7 @@ func (x *ACLAuditEntryInfo) String() string {
func (*ACLAuditEntryInfo) ProtoMessage() {}
func (x *ACLAuditEntryInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[80]
+ mi := &file_aether_proto_msgTypes[83]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11176,7 +11730,7 @@ func (x *ACLAuditEntryInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAuditEntryInfo.ProtoReflect.Descriptor instead.
func (*ACLAuditEntryInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{80}
+ return file_aether_proto_rawDescGZIP(), []int{83}
}
func (x *ACLAuditEntryInfo) GetAuditId() int64 {
@@ -11324,7 +11878,7 @@ type ACLAuthorityGrantInfo struct {
func (x *ACLAuthorityGrantInfo) Reset() {
*x = ACLAuthorityGrantInfo{}
- mi := &file_aether_proto_msgTypes[81]
+ mi := &file_aether_proto_msgTypes[84]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11336,7 +11890,7 @@ func (x *ACLAuthorityGrantInfo) String() string {
func (*ACLAuthorityGrantInfo) ProtoMessage() {}
func (x *ACLAuthorityGrantInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[81]
+ mi := &file_aether_proto_msgTypes[84]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11349,7 +11903,7 @@ func (x *ACLAuthorityGrantInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAuthorityGrantInfo.ProtoReflect.Descriptor instead.
func (*ACLAuthorityGrantInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{81}
+ return file_aether_proto_rawDescGZIP(), []int{84}
}
func (x *ACLAuthorityGrantInfo) GetGrantId() string {
@@ -11539,7 +12093,7 @@ type ACLCleanupResult struct {
func (x *ACLCleanupResult) Reset() {
*x = ACLCleanupResult{}
- mi := &file_aether_proto_msgTypes[82]
+ mi := &file_aether_proto_msgTypes[85]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11551,7 +12105,7 @@ func (x *ACLCleanupResult) String() string {
func (*ACLCleanupResult) ProtoMessage() {}
func (x *ACLCleanupResult) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[82]
+ mi := &file_aether_proto_msgTypes[85]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11564,7 +12118,7 @@ func (x *ACLCleanupResult) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLCleanupResult.ProtoReflect.Descriptor instead.
func (*ACLCleanupResult) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{82}
+ return file_aether_proto_rawDescGZIP(), []int{85}
}
func (x *ACLCleanupResult) GetDeletedCount() int64 {
@@ -11594,7 +12148,7 @@ type ACLGroupRequest struct {
func (x *ACLGroupRequest) Reset() {
*x = ACLGroupRequest{}
- mi := &file_aether_proto_msgTypes[83]
+ mi := &file_aether_proto_msgTypes[86]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11606,7 +12160,7 @@ func (x *ACLGroupRequest) String() string {
func (*ACLGroupRequest) ProtoMessage() {}
func (x *ACLGroupRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[83]
+ mi := &file_aether_proto_msgTypes[86]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11619,7 +12173,7 @@ func (x *ACLGroupRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLGroupRequest.ProtoReflect.Descriptor instead.
func (*ACLGroupRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{83}
+ return file_aether_proto_rawDescGZIP(), []int{86}
}
func (x *ACLGroupRequest) GetName() string {
@@ -11663,7 +12217,7 @@ type ACLRoleRequest struct {
func (x *ACLRoleRequest) Reset() {
*x = ACLRoleRequest{}
- mi := &file_aether_proto_msgTypes[84]
+ mi := &file_aether_proto_msgTypes[87]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11675,7 +12229,7 @@ func (x *ACLRoleRequest) String() string {
func (*ACLRoleRequest) ProtoMessage() {}
func (x *ACLRoleRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[84]
+ mi := &file_aether_proto_msgTypes[87]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11688,7 +12242,7 @@ func (x *ACLRoleRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRoleRequest.ProtoReflect.Descriptor instead.
func (*ACLRoleRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{84}
+ return file_aether_proto_rawDescGZIP(), []int{87}
}
func (x *ACLRoleRequest) GetName() string {
@@ -11732,7 +12286,7 @@ type ACLGroupMemberRequest struct {
func (x *ACLGroupMemberRequest) Reset() {
*x = ACLGroupMemberRequest{}
- mi := &file_aether_proto_msgTypes[85]
+ mi := &file_aether_proto_msgTypes[88]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11744,7 +12298,7 @@ func (x *ACLGroupMemberRequest) String() string {
func (*ACLGroupMemberRequest) ProtoMessage() {}
func (x *ACLGroupMemberRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[85]
+ mi := &file_aether_proto_msgTypes[88]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11757,7 +12311,7 @@ func (x *ACLGroupMemberRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLGroupMemberRequest.ProtoReflect.Descriptor instead.
func (*ACLGroupMemberRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{85}
+ return file_aether_proto_rawDescGZIP(), []int{88}
}
func (x *ACLGroupMemberRequest) GetMemberType() string {
@@ -11801,7 +12355,7 @@ type ACLRoleAssignmentRequest struct {
func (x *ACLRoleAssignmentRequest) Reset() {
*x = ACLRoleAssignmentRequest{}
- mi := &file_aether_proto_msgTypes[86]
+ mi := &file_aether_proto_msgTypes[89]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11813,7 +12367,7 @@ func (x *ACLRoleAssignmentRequest) String() string {
func (*ACLRoleAssignmentRequest) ProtoMessage() {}
func (x *ACLRoleAssignmentRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[86]
+ mi := &file_aether_proto_msgTypes[89]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11826,7 +12380,7 @@ func (x *ACLRoleAssignmentRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRoleAssignmentRequest.ProtoReflect.Descriptor instead.
func (*ACLRoleAssignmentRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{86}
+ return file_aether_proto_rawDescGZIP(), []int{89}
}
func (x *ACLRoleAssignmentRequest) GetAssigneeType() string {
@@ -11872,7 +12426,7 @@ type ACLGroupInfo struct {
func (x *ACLGroupInfo) Reset() {
*x = ACLGroupInfo{}
- mi := &file_aether_proto_msgTypes[87]
+ mi := &file_aether_proto_msgTypes[90]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11884,7 +12438,7 @@ func (x *ACLGroupInfo) String() string {
func (*ACLGroupInfo) ProtoMessage() {}
func (x *ACLGroupInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[87]
+ mi := &file_aether_proto_msgTypes[90]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11897,7 +12451,7 @@ func (x *ACLGroupInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLGroupInfo.ProtoReflect.Descriptor instead.
func (*ACLGroupInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{87}
+ return file_aether_proto_rawDescGZIP(), []int{90}
}
func (x *ACLGroupInfo) GetGroupId() string {
@@ -11957,7 +12511,7 @@ type ACLRoleInfo struct {
func (x *ACLRoleInfo) Reset() {
*x = ACLRoleInfo{}
- mi := &file_aether_proto_msgTypes[88]
+ mi := &file_aether_proto_msgTypes[91]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -11969,7 +12523,7 @@ func (x *ACLRoleInfo) String() string {
func (*ACLRoleInfo) ProtoMessage() {}
func (x *ACLRoleInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[88]
+ mi := &file_aether_proto_msgTypes[91]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -11982,7 +12536,7 @@ func (x *ACLRoleInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRoleInfo.ProtoReflect.Descriptor instead.
func (*ACLRoleInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{88}
+ return file_aether_proto_rawDescGZIP(), []int{91}
}
func (x *ACLRoleInfo) GetRoleId() string {
@@ -12042,7 +12596,7 @@ type ACLGroupMemberInfo struct {
func (x *ACLGroupMemberInfo) Reset() {
*x = ACLGroupMemberInfo{}
- mi := &file_aether_proto_msgTypes[89]
+ mi := &file_aether_proto_msgTypes[92]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12054,7 +12608,7 @@ func (x *ACLGroupMemberInfo) String() string {
func (*ACLGroupMemberInfo) ProtoMessage() {}
func (x *ACLGroupMemberInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[89]
+ mi := &file_aether_proto_msgTypes[92]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12067,7 +12621,7 @@ func (x *ACLGroupMemberInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLGroupMemberInfo.ProtoReflect.Descriptor instead.
func (*ACLGroupMemberInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{89}
+ return file_aether_proto_rawDescGZIP(), []int{92}
}
func (x *ACLGroupMemberInfo) GetGroupName() string {
@@ -12127,7 +12681,7 @@ type ACLRoleAssignmentInfo struct {
func (x *ACLRoleAssignmentInfo) Reset() {
*x = ACLRoleAssignmentInfo{}
- mi := &file_aether_proto_msgTypes[90]
+ mi := &file_aether_proto_msgTypes[93]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12139,7 +12693,7 @@ func (x *ACLRoleAssignmentInfo) String() string {
func (*ACLRoleAssignmentInfo) ProtoMessage() {}
func (x *ACLRoleAssignmentInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[90]
+ mi := &file_aether_proto_msgTypes[93]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12152,7 +12706,7 @@ func (x *ACLRoleAssignmentInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLRoleAssignmentInfo.ProtoReflect.Descriptor instead.
func (*ACLRoleAssignmentInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{90}
+ return file_aether_proto_rawDescGZIP(), []int{93}
}
func (x *ACLRoleAssignmentInfo) GetRoleName() string {
@@ -12212,7 +12766,7 @@ type ACLAccessContributionInfo struct {
func (x *ACLAccessContributionInfo) Reset() {
*x = ACLAccessContributionInfo{}
- mi := &file_aether_proto_msgTypes[91]
+ mi := &file_aether_proto_msgTypes[94]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12224,7 +12778,7 @@ func (x *ACLAccessContributionInfo) String() string {
func (*ACLAccessContributionInfo) ProtoMessage() {}
func (x *ACLAccessContributionInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[91]
+ mi := &file_aether_proto_msgTypes[94]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12237,7 +12791,7 @@ func (x *ACLAccessContributionInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAccessContributionInfo.ProtoReflect.Descriptor instead.
func (*ACLAccessContributionInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{91}
+ return file_aether_proto_rawDescGZIP(), []int{94}
}
func (x *ACLAccessContributionInfo) GetSubject() string {
@@ -12294,7 +12848,7 @@ type ACLAccessExplanationInfo struct {
func (x *ACLAccessExplanationInfo) Reset() {
*x = ACLAccessExplanationInfo{}
- mi := &file_aether_proto_msgTypes[92]
+ mi := &file_aether_proto_msgTypes[95]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12306,7 +12860,7 @@ func (x *ACLAccessExplanationInfo) String() string {
func (*ACLAccessExplanationInfo) ProtoMessage() {}
func (x *ACLAccessExplanationInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[92]
+ mi := &file_aether_proto_msgTypes[95]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12319,7 +12873,7 @@ func (x *ACLAccessExplanationInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLAccessExplanationInfo.ProtoReflect.Descriptor instead.
func (*ACLAccessExplanationInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{92}
+ return file_aether_proto_rawDescGZIP(), []int{95}
}
func (x *ACLAccessExplanationInfo) GetPrincipal() string {
@@ -12422,7 +12976,7 @@ type ACLResponse struct {
func (x *ACLResponse) Reset() {
*x = ACLResponse{}
- mi := &file_aether_proto_msgTypes[93]
+ mi := &file_aether_proto_msgTypes[96]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12434,7 +12988,7 @@ func (x *ACLResponse) String() string {
func (*ACLResponse) ProtoMessage() {}
func (x *ACLResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[93]
+ mi := &file_aether_proto_msgTypes[96]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12447,7 +13001,7 @@ func (x *ACLResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ACLResponse.ProtoReflect.Descriptor instead.
func (*ACLResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{93}
+ return file_aether_proto_rawDescGZIP(), []int{96}
}
func (x *ACLResponse) GetSuccess() bool {
@@ -12619,13 +13173,16 @@ type AuthorityGrantOperation struct {
BatchExchangeRequest *AuthorityGrantBatchExchangeRequest `protobuf:"bytes,8,opt,name=batch_exchange_request,json=batchExchangeRequest,proto3" json:"batch_exchange_request,omitempty"`
// For DERIVE_FOR_TARGET
DeriveForTargetRequest *AuthorityGrantDeriveForTargetRequest `protobuf:"bytes,9,opt,name=derive_for_target_request,json=deriveForTargetRequest,proto3" json:"derive_for_target_request,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // WorkflowEngine-only audience context for GET/REVOKE of a
+ // workflow_schedule grant. Ignored for other actors and operations.
+ WorkflowScheduleId string `protobuf:"bytes,10,opt,name=workflow_schedule_id,json=workflowScheduleId,proto3" json:"workflow_schedule_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *AuthorityGrantOperation) Reset() {
*x = AuthorityGrantOperation{}
- mi := &file_aether_proto_msgTypes[94]
+ mi := &file_aether_proto_msgTypes[97]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12637,7 +13194,7 @@ func (x *AuthorityGrantOperation) String() string {
func (*AuthorityGrantOperation) ProtoMessage() {}
func (x *AuthorityGrantOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[94]
+ mi := &file_aether_proto_msgTypes[97]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12650,7 +13207,7 @@ func (x *AuthorityGrantOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantOperation.ProtoReflect.Descriptor instead.
func (*AuthorityGrantOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{94}
+ return file_aether_proto_rawDescGZIP(), []int{97}
}
func (x *AuthorityGrantOperation) GetOp() AuthorityGrantOperation_OpType {
@@ -12716,6 +13273,13 @@ func (x *AuthorityGrantOperation) GetDeriveForTargetRequest() *AuthorityGrantDer
return nil
}
+func (x *AuthorityGrantOperation) GetWorkflowScheduleId() string {
+ if x != nil {
+ return x.WorkflowScheduleId
+ }
+ return ""
+}
+
// AuthorityGrantExchangeRequest bootstraps a grant for the current actor.
// If source_session_id is empty, only a user may self-exchange.
// If source_session_id is set, the caller must hold the exchange_authority_grants
@@ -12748,7 +13312,7 @@ type AuthorityGrantExchangeRequest struct {
func (x *AuthorityGrantExchangeRequest) Reset() {
*x = AuthorityGrantExchangeRequest{}
- mi := &file_aether_proto_msgTypes[95]
+ mi := &file_aether_proto_msgTypes[98]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12760,7 +13324,7 @@ func (x *AuthorityGrantExchangeRequest) String() string {
func (*AuthorityGrantExchangeRequest) ProtoMessage() {}
func (x *AuthorityGrantExchangeRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[95]
+ mi := &file_aether_proto_msgTypes[98]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12773,7 +13337,7 @@ func (x *AuthorityGrantExchangeRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantExchangeRequest.ProtoReflect.Descriptor instead.
func (*AuthorityGrantExchangeRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{95}
+ return file_aether_proto_rawDescGZIP(), []int{98}
}
func (x *AuthorityGrantExchangeRequest) GetSourceSessionId() string {
@@ -12898,7 +13462,7 @@ type AuthorityGrantDeriveRequest struct {
func (x *AuthorityGrantDeriveRequest) Reset() {
*x = AuthorityGrantDeriveRequest{}
- mi := &file_aether_proto_msgTypes[96]
+ mi := &file_aether_proto_msgTypes[99]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -12910,7 +13474,7 @@ func (x *AuthorityGrantDeriveRequest) String() string {
func (*AuthorityGrantDeriveRequest) ProtoMessage() {}
func (x *AuthorityGrantDeriveRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[96]
+ mi := &file_aether_proto_msgTypes[99]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -12923,7 +13487,7 @@ func (x *AuthorityGrantDeriveRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantDeriveRequest.ProtoReflect.Descriptor instead.
func (*AuthorityGrantDeriveRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{96}
+ return file_aether_proto_rawDescGZIP(), []int{99}
}
func (x *AuthorityGrantDeriveRequest) GetParentGrantId() string {
@@ -13053,7 +13617,7 @@ type AuthorityGrantResponse struct {
func (x *AuthorityGrantResponse) Reset() {
*x = AuthorityGrantResponse{}
- mi := &file_aether_proto_msgTypes[97]
+ mi := &file_aether_proto_msgTypes[100]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13065,7 +13629,7 @@ func (x *AuthorityGrantResponse) String() string {
func (*AuthorityGrantResponse) ProtoMessage() {}
func (x *AuthorityGrantResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[97]
+ mi := &file_aether_proto_msgTypes[100]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13078,7 +13642,7 @@ func (x *AuthorityGrantResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantResponse.ProtoReflect.Descriptor instead.
func (*AuthorityGrantResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{97}
+ return file_aether_proto_rawDescGZIP(), []int{100}
}
func (x *AuthorityGrantResponse) GetSuccess() bool {
@@ -13151,7 +13715,7 @@ type AuthorityGrantListRequest struct {
func (x *AuthorityGrantListRequest) Reset() {
*x = AuthorityGrantListRequest{}
- mi := &file_aether_proto_msgTypes[98]
+ mi := &file_aether_proto_msgTypes[101]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13163,7 +13727,7 @@ func (x *AuthorityGrantListRequest) String() string {
func (*AuthorityGrantListRequest) ProtoMessage() {}
func (x *AuthorityGrantListRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[98]
+ mi := &file_aether_proto_msgTypes[101]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13176,7 +13740,7 @@ func (x *AuthorityGrantListRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantListRequest.ProtoReflect.Descriptor instead.
func (*AuthorityGrantListRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{98}
+ return file_aether_proto_rawDescGZIP(), []int{101}
}
func (x *AuthorityGrantListRequest) GetAudienceType() string {
@@ -13227,7 +13791,7 @@ type AuthorityGrantBatchExchangeRequest struct {
func (x *AuthorityGrantBatchExchangeRequest) Reset() {
*x = AuthorityGrantBatchExchangeRequest{}
- mi := &file_aether_proto_msgTypes[99]
+ mi := &file_aether_proto_msgTypes[102]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13239,7 +13803,7 @@ func (x *AuthorityGrantBatchExchangeRequest) String() string {
func (*AuthorityGrantBatchExchangeRequest) ProtoMessage() {}
func (x *AuthorityGrantBatchExchangeRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[99]
+ mi := &file_aether_proto_msgTypes[102]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13252,7 +13816,7 @@ func (x *AuthorityGrantBatchExchangeRequest) ProtoReflect() protoreflect.Message
// Deprecated: Use AuthorityGrantBatchExchangeRequest.ProtoReflect.Descriptor instead.
func (*AuthorityGrantBatchExchangeRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{99}
+ return file_aether_proto_rawDescGZIP(), []int{102}
}
func (x *AuthorityGrantBatchExchangeRequest) GetRequests() []*AuthorityGrantExchangeRequest {
@@ -13293,7 +13857,7 @@ type AuthorityGrantDeriveForTargetRequest struct {
func (x *AuthorityGrantDeriveForTargetRequest) Reset() {
*x = AuthorityGrantDeriveForTargetRequest{}
- mi := &file_aether_proto_msgTypes[100]
+ mi := &file_aether_proto_msgTypes[103]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13305,7 +13869,7 @@ func (x *AuthorityGrantDeriveForTargetRequest) String() string {
func (*AuthorityGrantDeriveForTargetRequest) ProtoMessage() {}
func (x *AuthorityGrantDeriveForTargetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[100]
+ mi := &file_aether_proto_msgTypes[103]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13318,7 +13882,7 @@ func (x *AuthorityGrantDeriveForTargetRequest) ProtoReflect() protoreflect.Messa
// Deprecated: Use AuthorityGrantDeriveForTargetRequest.ProtoReflect.Descriptor instead.
func (*AuthorityGrantDeriveForTargetRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{100}
+ return file_aether_proto_rawDescGZIP(), []int{103}
}
func (x *AuthorityGrantDeriveForTargetRequest) GetParentGrantId() string {
@@ -13415,7 +13979,7 @@ type AuthorityIdentity struct {
func (x *AuthorityIdentity) Reset() {
*x = AuthorityIdentity{}
- mi := &file_aether_proto_msgTypes[101]
+ mi := &file_aether_proto_msgTypes[104]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13427,7 +13991,7 @@ func (x *AuthorityIdentity) String() string {
func (*AuthorityIdentity) ProtoMessage() {}
func (x *AuthorityIdentity) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[101]
+ mi := &file_aether_proto_msgTypes[104]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13440,7 +14004,7 @@ func (x *AuthorityIdentity) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityIdentity.ProtoReflect.Descriptor instead.
func (*AuthorityIdentity) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{101}
+ return file_aether_proto_rawDescGZIP(), []int{104}
}
func (x *AuthorityIdentity) GetSubject() *PrincipalRef {
@@ -13490,7 +14054,7 @@ type AuthoritySpan struct {
func (x *AuthoritySpan) Reset() {
*x = AuthoritySpan{}
- mi := &file_aether_proto_msgTypes[102]
+ mi := &file_aether_proto_msgTypes[105]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13502,7 +14066,7 @@ func (x *AuthoritySpan) String() string {
func (*AuthoritySpan) ProtoMessage() {}
func (x *AuthoritySpan) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[102]
+ mi := &file_aether_proto_msgTypes[105]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13515,7 +14079,7 @@ func (x *AuthoritySpan) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthoritySpan.ProtoReflect.Descriptor instead.
func (*AuthoritySpan) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{102}
+ return file_aether_proto_rawDescGZIP(), []int{105}
}
func (x *AuthoritySpan) GetWorkspaceScope() []string {
@@ -13592,7 +14156,7 @@ type AuthorityGrantRevocation struct {
func (x *AuthorityGrantRevocation) Reset() {
*x = AuthorityGrantRevocation{}
- mi := &file_aether_proto_msgTypes[103]
+ mi := &file_aether_proto_msgTypes[106]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13604,7 +14168,7 @@ func (x *AuthorityGrantRevocation) String() string {
func (*AuthorityGrantRevocation) ProtoMessage() {}
func (x *AuthorityGrantRevocation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[103]
+ mi := &file_aether_proto_msgTypes[106]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13617,7 +14181,7 @@ func (x *AuthorityGrantRevocation) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantRevocation.ProtoReflect.Descriptor instead.
func (*AuthorityGrantRevocation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{103}
+ return file_aether_proto_rawDescGZIP(), []int{106}
}
func (x *AuthorityGrantRevocation) GetGrantId() string {
@@ -13671,7 +14235,7 @@ type AuthorityRequestRoutingTarget struct {
func (x *AuthorityRequestRoutingTarget) Reset() {
*x = AuthorityRequestRoutingTarget{}
- mi := &file_aether_proto_msgTypes[104]
+ mi := &file_aether_proto_msgTypes[107]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13683,7 +14247,7 @@ func (x *AuthorityRequestRoutingTarget) String() string {
func (*AuthorityRequestRoutingTarget) ProtoMessage() {}
func (x *AuthorityRequestRoutingTarget) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[104]
+ mi := &file_aether_proto_msgTypes[107]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13696,7 +14260,7 @@ func (x *AuthorityRequestRoutingTarget) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityRequestRoutingTarget.ProtoReflect.Descriptor instead.
func (*AuthorityRequestRoutingTarget) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{104}
+ return file_aether_proto_rawDescGZIP(), []int{107}
}
func (x *AuthorityRequestRoutingTarget) GetPrincipal() *PrincipalRef {
@@ -13725,7 +14289,7 @@ type AuthorityRequestResourceScopeEntry struct {
func (x *AuthorityRequestResourceScopeEntry) Reset() {
*x = AuthorityRequestResourceScopeEntry{}
- mi := &file_aether_proto_msgTypes[105]
+ mi := &file_aether_proto_msgTypes[108]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13737,7 +14301,7 @@ func (x *AuthorityRequestResourceScopeEntry) String() string {
func (*AuthorityRequestResourceScopeEntry) ProtoMessage() {}
func (x *AuthorityRequestResourceScopeEntry) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[105]
+ mi := &file_aether_proto_msgTypes[108]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13750,7 +14314,7 @@ func (x *AuthorityRequestResourceScopeEntry) ProtoReflect() protoreflect.Message
// Deprecated: Use AuthorityRequestResourceScopeEntry.ProtoReflect.Descriptor instead.
func (*AuthorityRequestResourceScopeEntry) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{105}
+ return file_aether_proto_rawDescGZIP(), []int{108}
}
func (x *AuthorityRequestResourceScopeEntry) GetResourceType() string {
@@ -13808,7 +14372,7 @@ type AuthorityRequest struct {
func (x *AuthorityRequest) Reset() {
*x = AuthorityRequest{}
- mi := &file_aether_proto_msgTypes[106]
+ mi := &file_aether_proto_msgTypes[109]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -13820,7 +14384,7 @@ func (x *AuthorityRequest) String() string {
func (*AuthorityRequest) ProtoMessage() {}
func (x *AuthorityRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[106]
+ mi := &file_aether_proto_msgTypes[109]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -13833,7 +14397,7 @@ func (x *AuthorityRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityRequest.ProtoReflect.Descriptor instead.
func (*AuthorityRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{106}
+ return file_aether_proto_rawDescGZIP(), []int{109}
}
func (x *AuthorityRequest) GetRequestId() string {
@@ -14006,7 +14570,7 @@ type CreateAuthorityRequestPayload struct {
func (x *CreateAuthorityRequestPayload) Reset() {
*x = CreateAuthorityRequestPayload{}
- mi := &file_aether_proto_msgTypes[107]
+ mi := &file_aether_proto_msgTypes[110]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14018,7 +14582,7 @@ func (x *CreateAuthorityRequestPayload) String() string {
func (*CreateAuthorityRequestPayload) ProtoMessage() {}
func (x *CreateAuthorityRequestPayload) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[107]
+ mi := &file_aether_proto_msgTypes[110]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14031,7 +14595,7 @@ func (x *CreateAuthorityRequestPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use CreateAuthorityRequestPayload.ProtoReflect.Descriptor instead.
func (*CreateAuthorityRequestPayload) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{107}
+ return file_aether_proto_rawDescGZIP(), []int{110}
}
func (x *CreateAuthorityRequestPayload) GetRequestingActor() *PrincipalRef {
@@ -14148,7 +14712,7 @@ type ResolveAuthorityRequestPayload struct {
func (x *ResolveAuthorityRequestPayload) Reset() {
*x = ResolveAuthorityRequestPayload{}
- mi := &file_aether_proto_msgTypes[108]
+ mi := &file_aether_proto_msgTypes[111]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14160,7 +14724,7 @@ func (x *ResolveAuthorityRequestPayload) String() string {
func (*ResolveAuthorityRequestPayload) ProtoMessage() {}
func (x *ResolveAuthorityRequestPayload) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[108]
+ mi := &file_aether_proto_msgTypes[111]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14173,7 +14737,7 @@ func (x *ResolveAuthorityRequestPayload) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResolveAuthorityRequestPayload.ProtoReflect.Descriptor instead.
func (*ResolveAuthorityRequestPayload) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{108}
+ return file_aether_proto_rawDescGZIP(), []int{111}
}
func (x *ResolveAuthorityRequestPayload) GetDecision() ResolveAuthorityRequestPayload_Decision {
@@ -14257,7 +14821,7 @@ type AuthorityRequestListFilter struct {
func (x *AuthorityRequestListFilter) Reset() {
*x = AuthorityRequestListFilter{}
- mi := &file_aether_proto_msgTypes[109]
+ mi := &file_aether_proto_msgTypes[112]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14269,7 +14833,7 @@ func (x *AuthorityRequestListFilter) String() string {
func (*AuthorityRequestListFilter) ProtoMessage() {}
func (x *AuthorityRequestListFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[109]
+ mi := &file_aether_proto_msgTypes[112]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14282,7 +14846,7 @@ func (x *AuthorityRequestListFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityRequestListFilter.ProtoReflect.Descriptor instead.
func (*AuthorityRequestListFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{109}
+ return file_aether_proto_rawDescGZIP(), []int{112}
}
func (x *AuthorityRequestListFilter) GetStatus() AuthorityRequestStatus {
@@ -14339,7 +14903,7 @@ type AuthorityRequestOperation struct {
func (x *AuthorityRequestOperation) Reset() {
*x = AuthorityRequestOperation{}
- mi := &file_aether_proto_msgTypes[110]
+ mi := &file_aether_proto_msgTypes[113]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14351,7 +14915,7 @@ func (x *AuthorityRequestOperation) String() string {
func (*AuthorityRequestOperation) ProtoMessage() {}
func (x *AuthorityRequestOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[110]
+ mi := &file_aether_proto_msgTypes[113]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14364,7 +14928,7 @@ func (x *AuthorityRequestOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityRequestOperation.ProtoReflect.Descriptor instead.
func (*AuthorityRequestOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{110}
+ return file_aether_proto_rawDescGZIP(), []int{113}
}
func (x *AuthorityRequestOperation) GetOp() AuthorityRequestOperation_OpType {
@@ -14432,7 +14996,7 @@ type AuthorityRequestOperationResponse struct {
func (x *AuthorityRequestOperationResponse) Reset() {
*x = AuthorityRequestOperationResponse{}
- mi := &file_aether_proto_msgTypes[111]
+ mi := &file_aether_proto_msgTypes[114]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14444,7 +15008,7 @@ func (x *AuthorityRequestOperationResponse) String() string {
func (*AuthorityRequestOperationResponse) ProtoMessage() {}
func (x *AuthorityRequestOperationResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[111]
+ mi := &file_aether_proto_msgTypes[114]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14457,7 +15021,7 @@ func (x *AuthorityRequestOperationResponse) ProtoReflect() protoreflect.Message
// Deprecated: Use AuthorityRequestOperationResponse.ProtoReflect.Descriptor instead.
func (*AuthorityRequestOperationResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{111}
+ return file_aether_proto_rawDescGZIP(), []int{114}
}
func (x *AuthorityRequestOperationResponse) GetSuccess() bool {
@@ -14515,7 +15079,7 @@ type AuthorityRequestEvent struct {
func (x *AuthorityRequestEvent) Reset() {
*x = AuthorityRequestEvent{}
- mi := &file_aether_proto_msgTypes[112]
+ mi := &file_aether_proto_msgTypes[115]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14527,7 +15091,7 @@ func (x *AuthorityRequestEvent) String() string {
func (*AuthorityRequestEvent) ProtoMessage() {}
func (x *AuthorityRequestEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[112]
+ mi := &file_aether_proto_msgTypes[115]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14540,7 +15104,7 @@ func (x *AuthorityRequestEvent) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityRequestEvent.ProtoReflect.Descriptor instead.
func (*AuthorityRequestEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{112}
+ return file_aether_proto_rawDescGZIP(), []int{115}
}
func (x *AuthorityRequestEvent) GetEventType() AuthorityRequestEvent_EventType {
@@ -14589,7 +15153,7 @@ type TokenOperation struct {
func (x *TokenOperation) Reset() {
*x = TokenOperation{}
- mi := &file_aether_proto_msgTypes[113]
+ mi := &file_aether_proto_msgTypes[116]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14601,7 +15165,7 @@ func (x *TokenOperation) String() string {
func (*TokenOperation) ProtoMessage() {}
func (x *TokenOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[113]
+ mi := &file_aether_proto_msgTypes[116]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14614,7 +15178,7 @@ func (x *TokenOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use TokenOperation.ProtoReflect.Descriptor instead.
func (*TokenOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{113}
+ return file_aether_proto_rawDescGZIP(), []int{116}
}
func (x *TokenOperation) GetOp() TokenOperation_OpType {
@@ -14667,7 +15231,7 @@ type TokenCreateRequest struct {
func (x *TokenCreateRequest) Reset() {
*x = TokenCreateRequest{}
- mi := &file_aether_proto_msgTypes[114]
+ mi := &file_aether_proto_msgTypes[117]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14679,7 +15243,7 @@ func (x *TokenCreateRequest) String() string {
func (*TokenCreateRequest) ProtoMessage() {}
func (x *TokenCreateRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[114]
+ mi := &file_aether_proto_msgTypes[117]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14692,7 +15256,7 @@ func (x *TokenCreateRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use TokenCreateRequest.ProtoReflect.Descriptor instead.
func (*TokenCreateRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{114}
+ return file_aether_proto_rawDescGZIP(), []int{117}
}
func (x *TokenCreateRequest) GetName() string {
@@ -14749,7 +15313,7 @@ type TokenFilter struct {
func (x *TokenFilter) Reset() {
*x = TokenFilter{}
- mi := &file_aether_proto_msgTypes[115]
+ mi := &file_aether_proto_msgTypes[118]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14761,7 +15325,7 @@ func (x *TokenFilter) String() string {
func (*TokenFilter) ProtoMessage() {}
func (x *TokenFilter) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[115]
+ mi := &file_aether_proto_msgTypes[118]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14774,7 +15338,7 @@ func (x *TokenFilter) ProtoReflect() protoreflect.Message {
// Deprecated: Use TokenFilter.ProtoReflect.Descriptor instead.
func (*TokenFilter) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{115}
+ return file_aether_proto_rawDescGZIP(), []int{118}
}
func (x *TokenFilter) GetLimit() int32 {
@@ -14819,7 +15383,7 @@ type TokenInfo struct {
func (x *TokenInfo) Reset() {
*x = TokenInfo{}
- mi := &file_aether_proto_msgTypes[116]
+ mi := &file_aether_proto_msgTypes[119]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14831,7 +15395,7 @@ func (x *TokenInfo) String() string {
func (*TokenInfo) ProtoMessage() {}
func (x *TokenInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[116]
+ mi := &file_aether_proto_msgTypes[119]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14844,7 +15408,7 @@ func (x *TokenInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use TokenInfo.ProtoReflect.Descriptor instead.
func (*TokenInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{116}
+ return file_aether_proto_rawDescGZIP(), []int{119}
}
func (x *TokenInfo) GetId() string {
@@ -14957,7 +15521,7 @@ type TokenResponse struct {
func (x *TokenResponse) Reset() {
*x = TokenResponse{}
- mi := &file_aether_proto_msgTypes[117]
+ mi := &file_aether_proto_msgTypes[120]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -14969,7 +15533,7 @@ func (x *TokenResponse) String() string {
func (*TokenResponse) ProtoMessage() {}
func (x *TokenResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[117]
+ mi := &file_aether_proto_msgTypes[120]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -14982,7 +15546,7 @@ func (x *TokenResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use TokenResponse.ProtoReflect.Descriptor instead.
func (*TokenResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{117}
+ return file_aether_proto_rawDescGZIP(), []int{120}
}
func (x *TokenResponse) GetSuccess() bool {
@@ -15093,7 +15657,7 @@ type ProgressReport struct {
func (x *ProgressReport) Reset() {
*x = ProgressReport{}
- mi := &file_aether_proto_msgTypes[118]
+ mi := &file_aether_proto_msgTypes[121]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15105,7 +15669,7 @@ func (x *ProgressReport) String() string {
func (*ProgressReport) ProtoMessage() {}
func (x *ProgressReport) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[118]
+ mi := &file_aether_proto_msgTypes[121]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15118,7 +15682,7 @@ func (x *ProgressReport) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProgressReport.ProtoReflect.Descriptor instead.
func (*ProgressReport) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{118}
+ return file_aether_proto_rawDescGZIP(), []int{121}
}
func (x *ProgressReport) GetTaskId() string {
@@ -15203,7 +15767,7 @@ type ProgressStep struct {
func (x *ProgressStep) Reset() {
*x = ProgressStep{}
- mi := &file_aether_proto_msgTypes[119]
+ mi := &file_aether_proto_msgTypes[122]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15215,7 +15779,7 @@ func (x *ProgressStep) String() string {
func (*ProgressStep) ProtoMessage() {}
func (x *ProgressStep) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[119]
+ mi := &file_aether_proto_msgTypes[122]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15228,7 +15792,7 @@ func (x *ProgressStep) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProgressStep.ProtoReflect.Descriptor instead.
func (*ProgressStep) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{119}
+ return file_aether_proto_rawDescGZIP(), []int{122}
}
func (x *ProgressStep) GetName() string {
@@ -15305,7 +15869,7 @@ type ProgressUpdate struct {
func (x *ProgressUpdate) Reset() {
*x = ProgressUpdate{}
- mi := &file_aether_proto_msgTypes[120]
+ mi := &file_aether_proto_msgTypes[123]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15317,7 +15881,7 @@ func (x *ProgressUpdate) String() string {
func (*ProgressUpdate) ProtoMessage() {}
func (x *ProgressUpdate) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[120]
+ mi := &file_aether_proto_msgTypes[123]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15330,7 +15894,7 @@ func (x *ProgressUpdate) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProgressUpdate.ProtoReflect.Descriptor instead.
func (*ProgressUpdate) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{120}
+ return file_aether_proto_rawDescGZIP(), []int{123}
}
func (x *ProgressUpdate) GetSource() string {
@@ -15417,26 +15981,271 @@ func (x *ProgressUpdate) GetKind() ProgressKind {
return ProgressKind_PROGRESS_KIND_UNSPECIFIED
}
+// Requested ceiling for the private authority attached to one schedule. The
+// gateway validates/attenuates this against the authenticated caller context;
+// the WorkflowEngine never trusts it directly and never stores it in action
+// JSON. Empty resource or operation scope is invalid.
+type WorkflowScheduleAuthorityScope struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ WorkspaceScope []string `protobuf:"bytes,1,rep,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"`
+ ResourceScope []*ACLAuthorityGrantResourceScopeEntry `protobuf:"bytes,2,rep,name=resource_scope,json=resourceScope,proto3" json:"resource_scope,omitempty"`
+ OperationScope []string `protobuf:"bytes,3,rep,name=operation_scope,json=operationScope,proto3" json:"operation_scope,omitempty"`
+ MaxAccessLevel int32 `protobuf:"varint,4,opt,name=max_access_level,json=maxAccessLevel,proto3" json:"max_access_level,omitempty"`
+ ExpiresAt int64 `protobuf:"varint,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
+ RenewableUntil int64 `protobuf:"varint,6,opt,name=renewable_until,json=renewableUntil,proto3" json:"renewable_until,omitempty"`
+ RequiredTaskAuthorityHops uint32 `protobuf:"varint,7,opt,name=required_task_authority_hops,json=requiredTaskAuthorityHops,proto3" json:"required_task_authority_hops,omitempty"`
+ LifetimeMode WorkflowAuthorityLifetimeMode `protobuf:"varint,8,opt,name=lifetime_mode,json=lifetimeMode,proto3,enum=aether.v1.WorkflowAuthorityLifetimeMode" json:"lifetime_mode,omitempty"`
+ // Version of the deterministic schedule-authority policy shape. Callers
+ // currently send 1; unknown versions fail closed instead of being silently
+ // reinterpreted after an upgrade.
+ PolicyVersion uint32 `protobuf:"varint,9,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *WorkflowScheduleAuthorityScope) Reset() {
+ *x = WorkflowScheduleAuthorityScope{}
+ mi := &file_aether_proto_msgTypes[124]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *WorkflowScheduleAuthorityScope) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WorkflowScheduleAuthorityScope) ProtoMessage() {}
+
+func (x *WorkflowScheduleAuthorityScope) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[124]
+ 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 WorkflowScheduleAuthorityScope.ProtoReflect.Descriptor instead.
+func (*WorkflowScheduleAuthorityScope) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{124}
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetWorkspaceScope() []string {
+ if x != nil {
+ return x.WorkspaceScope
+ }
+ return nil
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetResourceScope() []*ACLAuthorityGrantResourceScopeEntry {
+ if x != nil {
+ return x.ResourceScope
+ }
+ return nil
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetOperationScope() []string {
+ if x != nil {
+ return x.OperationScope
+ }
+ return nil
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetMaxAccessLevel() int32 {
+ if x != nil {
+ return x.MaxAccessLevel
+ }
+ return 0
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetExpiresAt() int64 {
+ if x != nil {
+ return x.ExpiresAt
+ }
+ return 0
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetRenewableUntil() int64 {
+ if x != nil {
+ return x.RenewableUntil
+ }
+ return 0
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetRequiredTaskAuthorityHops() uint32 {
+ if x != nil {
+ return x.RequiredTaskAuthorityHops
+ }
+ return 0
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetLifetimeMode() WorkflowAuthorityLifetimeMode {
+ if x != nil {
+ return x.LifetimeMode
+ }
+ return WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND
+}
+
+func (x *WorkflowScheduleAuthorityScope) GetPolicyVersion() uint32 {
+ if x != nil {
+ return x.PolicyVersion
+ }
+ return 0
+}
+
+// Gateway-authored workflow request identity and schedule authority. The
+// gateway clears any client-supplied value before forwarding. Schedule grant
+// IDs remain outside action JSON, task payload/metadata, and workflow response
+// data. Consumers must treat this object as trusted only on the authenticated
+// WorkflowEngine connection from the gateway.
+type WorkflowRequestContext struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Actor *PrincipalRef `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"`
+ Subject *PrincipalRef `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"`
+ ActorSessionId string `protobuf:"bytes,3,opt,name=actor_session_id,json=actorSessionId,proto3" json:"actor_session_id,omitempty"`
+ ScheduleAuthorization *AuthorizationContext `protobuf:"bytes,4,opt,name=schedule_authorization,json=scheduleAuthorization,proto3" json:"schedule_authorization,omitempty"`
+ RootGrantId string `protobuf:"bytes,5,opt,name=root_grant_id,json=rootGrantId,proto3" json:"root_grant_id,omitempty"`
+ SourceGrantId string `protobuf:"bytes,6,opt,name=source_grant_id,json=sourceGrantId,proto3" json:"source_grant_id,omitempty"`
+ ExpiresAtMs int64 `protobuf:"varint,7,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"`
+ PolicyDigest string `protobuf:"bytes,8,opt,name=policy_digest,json=policyDigest,proto3" json:"policy_digest,omitempty"`
+ LifetimeMode WorkflowAuthorityLifetimeMode `protobuf:"varint,9,opt,name=lifetime_mode,json=lifetimeMode,proto3,enum=aether.v1.WorkflowAuthorityLifetimeMode" json:"lifetime_mode,omitempty"`
+ PolicyVersion uint32 `protobuf:"varint,10,opt,name=policy_version,json=policyVersion,proto3" json:"policy_version,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *WorkflowRequestContext) Reset() {
+ *x = WorkflowRequestContext{}
+ mi := &file_aether_proto_msgTypes[125]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *WorkflowRequestContext) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WorkflowRequestContext) ProtoMessage() {}
+
+func (x *WorkflowRequestContext) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[125]
+ 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 WorkflowRequestContext.ProtoReflect.Descriptor instead.
+func (*WorkflowRequestContext) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{125}
+}
+
+func (x *WorkflowRequestContext) GetActor() *PrincipalRef {
+ if x != nil {
+ return x.Actor
+ }
+ return nil
+}
+
+func (x *WorkflowRequestContext) GetSubject() *PrincipalRef {
+ if x != nil {
+ return x.Subject
+ }
+ return nil
+}
+
+func (x *WorkflowRequestContext) GetActorSessionId() string {
+ if x != nil {
+ return x.ActorSessionId
+ }
+ return ""
+}
+
+func (x *WorkflowRequestContext) GetScheduleAuthorization() *AuthorizationContext {
+ if x != nil {
+ return x.ScheduleAuthorization
+ }
+ return nil
+}
+
+func (x *WorkflowRequestContext) GetRootGrantId() string {
+ if x != nil {
+ return x.RootGrantId
+ }
+ return ""
+}
+
+func (x *WorkflowRequestContext) GetSourceGrantId() string {
+ if x != nil {
+ return x.SourceGrantId
+ }
+ return ""
+}
+
+func (x *WorkflowRequestContext) GetExpiresAtMs() int64 {
+ if x != nil {
+ return x.ExpiresAtMs
+ }
+ return 0
+}
+
+func (x *WorkflowRequestContext) GetPolicyDigest() string {
+ if x != nil {
+ return x.PolicyDigest
+ }
+ return ""
+}
+
+func (x *WorkflowRequestContext) GetLifetimeMode() WorkflowAuthorityLifetimeMode {
+ if x != nil {
+ return x.LifetimeMode
+ }
+ return WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND
+}
+
+func (x *WorkflowRequestContext) GetPolicyVersion() uint32 {
+ if x != nil {
+ return x.PolicyVersion
+ }
+ return 0
+}
+
// WorkflowOperation allows clients to manage workflow rules, definitions,
// schedules, executions, and state machines through the gRPC streaming interface.
// Operations are forwarded by the gateway to the connected workflow engine and
// responses are relayed back to the requesting client.
type WorkflowOperation struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Op WorkflowOperation_OpType `protobuf:"varint,1,opt,name=op,proto3,enum=aether.v1.WorkflowOperation_OpType" json:"op,omitempty"`
- Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // Entity ID for GET/UPDATE/DELETE
- SecondaryId string `protobuf:"bytes,3,opt,name=secondary_id,json=secondaryId,proto3" json:"secondary_id,omitempty"` // e.g., instance_id for SM instance ops
- Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` // Workspace filter
- Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` // JSON payload for CREATE/UPDATE ops
- RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // Correlation ID
- StatusFilter string `protobuf:"bytes,7,opt,name=status_filter,json=statusFilter,proto3" json:"status_filter,omitempty"` // For LIST_EXECUTIONS
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Op WorkflowOperation_OpType `protobuf:"varint,1,opt,name=op,proto3,enum=aether.v1.WorkflowOperation_OpType" json:"op,omitempty"`
+ Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` // Entity ID for GET/UPDATE/DELETE
+ SecondaryId string `protobuf:"bytes,3,opt,name=secondary_id,json=secondaryId,proto3" json:"secondary_id,omitempty"` // e.g., instance_id for SM instance ops
+ Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"` // Workspace filter
+ Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` // JSON payload for CREATE/UPDATE ops
+ RequestId string `protobuf:"bytes,6,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // Correlation ID
+ StatusFilter string `protobuf:"bytes,7,opt,name=status_filter,json=statusFilter,proto3" json:"status_filter,omitempty"` // For LIST_EXECUTIONS
+ // Optional caller OBO authority. Resolved by the gateway before the request
+ // is authorized and forwarded.
+ Authorization *AuthorizationContext `protobuf:"bytes,8,opt,name=authorization,proto3" json:"authorization,omitempty"`
+ // Optional requested authority for CREATE_SCHEDULE / UPSERT_SCHEDULE. The
+ // gateway derives or mints the exact WorkflowEngine schedule grant and
+ // forwards only the resulting trusted request_context.
+ ScheduleAuthorityScope *WorkflowScheduleAuthorityScope `protobuf:"bytes,9,opt,name=schedule_authority_scope,json=scheduleAuthorityScope,proto3" json:"schedule_authority_scope,omitempty"`
+ // Gateway-authored; caller values are always discarded.
+ RequestContext *WorkflowRequestContext `protobuf:"bytes,10,opt,name=request_context,json=requestContext,proto3" json:"request_context,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *WorkflowOperation) Reset() {
*x = WorkflowOperation{}
- mi := &file_aether_proto_msgTypes[121]
+ mi := &file_aether_proto_msgTypes[126]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15448,7 +16257,7 @@ func (x *WorkflowOperation) String() string {
func (*WorkflowOperation) ProtoMessage() {}
func (x *WorkflowOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[121]
+ mi := &file_aether_proto_msgTypes[126]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15461,7 +16270,7 @@ func (x *WorkflowOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkflowOperation.ProtoReflect.Descriptor instead.
func (*WorkflowOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{121}
+ return file_aether_proto_rawDescGZIP(), []int{126}
}
func (x *WorkflowOperation) GetOp() WorkflowOperation_OpType {
@@ -15513,6 +16322,27 @@ func (x *WorkflowOperation) GetStatusFilter() string {
return ""
}
+func (x *WorkflowOperation) GetAuthorization() *AuthorizationContext {
+ if x != nil {
+ return x.Authorization
+ }
+ return nil
+}
+
+func (x *WorkflowOperation) GetScheduleAuthorityScope() *WorkflowScheduleAuthorityScope {
+ if x != nil {
+ return x.ScheduleAuthorityScope
+ }
+ return nil
+}
+
+func (x *WorkflowOperation) GetRequestContext() *WorkflowRequestContext {
+ if x != nil {
+ return x.RequestContext
+ }
+ return nil
+}
+
// WorkflowResponse is sent in response to WorkflowOperation.
// Uses JSON-encoded bytes for response payloads to avoid duplicating
// the workflow server's internal types in the proto definition.
@@ -15530,7 +16360,7 @@ type WorkflowResponse struct {
func (x *WorkflowResponse) Reset() {
*x = WorkflowResponse{}
- mi := &file_aether_proto_msgTypes[122]
+ mi := &file_aether_proto_msgTypes[127]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15542,7 +16372,7 @@ func (x *WorkflowResponse) String() string {
func (*WorkflowResponse) ProtoMessage() {}
func (x *WorkflowResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[122]
+ mi := &file_aether_proto_msgTypes[127]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15555,7 +16385,7 @@ func (x *WorkflowResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use WorkflowResponse.ProtoReflect.Descriptor instead.
func (*WorkflowResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{122}
+ return file_aether_proto_rawDescGZIP(), []int{127}
}
func (x *WorkflowResponse) GetSuccess() bool {
@@ -15643,13 +16473,18 @@ type MessageEnvelope struct {
// need to *act for* the subject use the task authority-grant path
// (CreateTaskResponse.authority_grant_id), not this field.
OnBehalfSubject *PrincipalRef `protobuf:"bytes,7,opt,name=on_behalf_subject,json=onBehalfSubject,proto3" json:"on_behalf_subject,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // Gateway-authored exact-resource decision propagated to the recipient.
+ AccessReceipt *AccessDecisionReceipt `protobuf:"bytes,8,opt,name=access_receipt,json=accessReceipt,proto3" json:"access_receipt,omitempty"`
+ // Gateway-authored authority continuation. This internal envelope field is
+ // copied to IncomingMessage and is never accepted from application payloads.
+ ForwardedAuthorization *ForwardedAuthorization `protobuf:"bytes,9,opt,name=forwarded_authorization,json=forwardedAuthorization,proto3" json:"forwarded_authorization,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *MessageEnvelope) Reset() {
*x = MessageEnvelope{}
- mi := &file_aether_proto_msgTypes[123]
+ mi := &file_aether_proto_msgTypes[128]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15661,7 +16496,7 @@ func (x *MessageEnvelope) String() string {
func (*MessageEnvelope) ProtoMessage() {}
func (x *MessageEnvelope) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[123]
+ mi := &file_aether_proto_msgTypes[128]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15674,7 +16509,7 @@ func (x *MessageEnvelope) ProtoReflect() protoreflect.Message {
// Deprecated: Use MessageEnvelope.ProtoReflect.Descriptor instead.
func (*MessageEnvelope) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{123}
+ return file_aether_proto_rawDescGZIP(), []int{128}
}
func (x *MessageEnvelope) GetSource() string {
@@ -15726,6 +16561,20 @@ func (x *MessageEnvelope) GetOnBehalfSubject() *PrincipalRef {
return nil
}
+func (x *MessageEnvelope) GetAccessReceipt() *AccessDecisionReceipt {
+ if x != nil {
+ return x.AccessReceipt
+ }
+ return nil
+}
+
+func (x *MessageEnvelope) GetForwardedAuthorization() *ForwardedAuthorization {
+ if x != nil {
+ return x.ForwardedAuthorization
+ }
+ return nil
+}
+
// AuditQuery requests entries from the comprehensive audit log.
// Requires system-level admin access or workspace-scoped read access.
type AuditQuery struct {
@@ -15758,7 +16607,7 @@ type AuditQuery struct {
func (x *AuditQuery) Reset() {
*x = AuditQuery{}
- mi := &file_aether_proto_msgTypes[124]
+ mi := &file_aether_proto_msgTypes[129]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15770,7 +16619,7 @@ func (x *AuditQuery) String() string {
func (*AuditQuery) ProtoMessage() {}
func (x *AuditQuery) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[124]
+ mi := &file_aether_proto_msgTypes[129]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15783,7 +16632,7 @@ func (x *AuditQuery) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuditQuery.ProtoReflect.Descriptor instead.
func (*AuditQuery) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{124}
+ return file_aether_proto_rawDescGZIP(), []int{129}
}
func (x *AuditQuery) GetRequestId() string {
@@ -15947,7 +16796,7 @@ type AuditQueryResponse struct {
func (x *AuditQueryResponse) Reset() {
*x = AuditQueryResponse{}
- mi := &file_aether_proto_msgTypes[125]
+ mi := &file_aether_proto_msgTypes[130]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -15959,7 +16808,7 @@ func (x *AuditQueryResponse) String() string {
func (*AuditQueryResponse) ProtoMessage() {}
func (x *AuditQueryResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[125]
+ mi := &file_aether_proto_msgTypes[130]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -15972,7 +16821,7 @@ func (x *AuditQueryResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuditQueryResponse.ProtoReflect.Descriptor instead.
func (*AuditQueryResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{125}
+ return file_aether_proto_rawDescGZIP(), []int{130}
}
func (x *AuditQueryResponse) GetRequestId() string {
@@ -16042,7 +16891,7 @@ type AuditEntry struct {
func (x *AuditEntry) Reset() {
*x = AuditEntry{}
- mi := &file_aether_proto_msgTypes[126]
+ mi := &file_aether_proto_msgTypes[131]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16054,7 +16903,7 @@ func (x *AuditEntry) String() string {
func (*AuditEntry) ProtoMessage() {}
func (x *AuditEntry) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[126]
+ mi := &file_aether_proto_msgTypes[131]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16067,7 +16916,7 @@ func (x *AuditEntry) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuditEntry.ProtoReflect.Descriptor instead.
func (*AuditEntry) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{126}
+ return file_aether_proto_rawDescGZIP(), []int{131}
}
func (x *AuditEntry) GetAuditId() int64 {
@@ -16254,7 +17103,7 @@ type SubmitAuditEventRequest struct {
func (x *SubmitAuditEventRequest) Reset() {
*x = SubmitAuditEventRequest{}
- mi := &file_aether_proto_msgTypes[127]
+ mi := &file_aether_proto_msgTypes[132]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16266,7 +17115,7 @@ func (x *SubmitAuditEventRequest) String() string {
func (*SubmitAuditEventRequest) ProtoMessage() {}
func (x *SubmitAuditEventRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[127]
+ mi := &file_aether_proto_msgTypes[132]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16279,7 +17128,7 @@ func (x *SubmitAuditEventRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SubmitAuditEventRequest.ProtoReflect.Descriptor instead.
func (*SubmitAuditEventRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{127}
+ return file_aether_proto_rawDescGZIP(), []int{132}
}
func (x *SubmitAuditEventRequest) GetEventType() string {
@@ -16360,7 +17209,7 @@ type SubmitAuditEventResponse struct {
func (x *SubmitAuditEventResponse) Reset() {
*x = SubmitAuditEventResponse{}
- mi := &file_aether_proto_msgTypes[128]
+ mi := &file_aether_proto_msgTypes[133]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16372,7 +17221,7 @@ func (x *SubmitAuditEventResponse) String() string {
func (*SubmitAuditEventResponse) ProtoMessage() {}
func (x *SubmitAuditEventResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[128]
+ mi := &file_aether_proto_msgTypes[133]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16385,7 +17234,7 @@ func (x *SubmitAuditEventResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SubmitAuditEventResponse.ProtoReflect.Descriptor instead.
func (*SubmitAuditEventResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{128}
+ return file_aether_proto_rawDescGZIP(), []int{133}
}
func (x *SubmitAuditEventResponse) GetClientRequestId() string {
@@ -16459,13 +17308,21 @@ type ProxyHttpRequest struct {
// upward by the sidecar relay floor (sandboxes can bump higher but never
// lower than the inbound chain depth they observed).
ProxyChainDepth uint32 `protobuf:"varint,16,opt,name=proxy_chain_depth,json=proxyChainDepth,proto3" json:"proxy_chain_depth,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ // Optional exact logical-resource authorization evaluated by the gateway
+ // after route authorization and wildcard target resolution. A denied or
+ // unavailable check prevents delivery to the terminator.
+ CheckedAccess *ResourceAccessRequest `protobuf:"bytes,17,opt,name=checked_access,json=checkedAccess,proto3" json:"checked_access,omitempty"`
+ // Gateway-authored result of checked_access. The gateway always clears any
+ // caller-supplied value before evaluation; terminators must trust this only
+ // as transport metadata on the delivered envelope.
+ AccessReceipt *AccessDecisionReceipt `protobuf:"bytes,18,opt,name=access_receipt,json=accessReceipt,proto3" json:"access_receipt,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *ProxyHttpRequest) Reset() {
*x = ProxyHttpRequest{}
- mi := &file_aether_proto_msgTypes[129]
+ mi := &file_aether_proto_msgTypes[134]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16477,7 +17334,7 @@ func (x *ProxyHttpRequest) String() string {
func (*ProxyHttpRequest) ProtoMessage() {}
func (x *ProxyHttpRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[129]
+ mi := &file_aether_proto_msgTypes[134]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16490,7 +17347,7 @@ func (x *ProxyHttpRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyHttpRequest.ProtoReflect.Descriptor instead.
func (*ProxyHttpRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{129}
+ return file_aether_proto_rawDescGZIP(), []int{134}
}
func (x *ProxyHttpRequest) GetRequestId() string {
@@ -16605,6 +17462,20 @@ func (x *ProxyHttpRequest) GetProxyChainDepth() uint32 {
return 0
}
+func (x *ProxyHttpRequest) GetCheckedAccess() *ResourceAccessRequest {
+ if x != nil {
+ return x.CheckedAccess
+ }
+ return nil
+}
+
+func (x *ProxyHttpRequest) GetAccessReceipt() *AccessDecisionReceipt {
+ if x != nil {
+ return x.AccessReceipt
+ }
+ return nil
+}
+
// ProxyHttpResponse is sent in reply to a ProxyHttpRequest. Errors are
// signalled via the `error` field; non-error responses carry the backend's
// status code, headers, and body (chunked when too large to inline).
@@ -16622,7 +17493,7 @@ type ProxyHttpResponse struct {
func (x *ProxyHttpResponse) Reset() {
*x = ProxyHttpResponse{}
- mi := &file_aether_proto_msgTypes[130]
+ mi := &file_aether_proto_msgTypes[135]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16634,7 +17505,7 @@ func (x *ProxyHttpResponse) String() string {
func (*ProxyHttpResponse) ProtoMessage() {}
func (x *ProxyHttpResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[130]
+ mi := &file_aether_proto_msgTypes[135]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16647,7 +17518,7 @@ func (x *ProxyHttpResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyHttpResponse.ProtoReflect.Descriptor instead.
func (*ProxyHttpResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{130}
+ return file_aether_proto_rawDescGZIP(), []int{135}
}
func (x *ProxyHttpResponse) GetRequestId() string {
@@ -16707,7 +17578,7 @@ type ProxyHttpBodyChunk struct {
func (x *ProxyHttpBodyChunk) Reset() {
*x = ProxyHttpBodyChunk{}
- mi := &file_aether_proto_msgTypes[131]
+ mi := &file_aether_proto_msgTypes[136]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16719,7 +17590,7 @@ func (x *ProxyHttpBodyChunk) String() string {
func (*ProxyHttpBodyChunk) ProtoMessage() {}
func (x *ProxyHttpBodyChunk) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[131]
+ mi := &file_aether_proto_msgTypes[136]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16732,7 +17603,7 @@ func (x *ProxyHttpBodyChunk) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyHttpBodyChunk.ProtoReflect.Descriptor instead.
func (*ProxyHttpBodyChunk) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{131}
+ return file_aether_proto_rawDescGZIP(), []int{136}
}
func (x *ProxyHttpBodyChunk) GetRequestId() string {
@@ -16782,7 +17653,7 @@ type ProxyError struct {
func (x *ProxyError) Reset() {
*x = ProxyError{}
- mi := &file_aether_proto_msgTypes[132]
+ mi := &file_aether_proto_msgTypes[137]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16794,7 +17665,7 @@ func (x *ProxyError) String() string {
func (*ProxyError) ProtoMessage() {}
func (x *ProxyError) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[132]
+ mi := &file_aether_proto_msgTypes[137]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16807,7 +17678,7 @@ func (x *ProxyError) ProtoReflect() protoreflect.Message {
// Deprecated: Use ProxyError.ProtoReflect.Descriptor instead.
func (*ProxyError) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{132}
+ return file_aether_proto_rawDescGZIP(), []int{137}
}
func (x *ProxyError) GetKind() ProxyError_Kind {
@@ -16850,7 +17721,7 @@ type TunnelOpen struct {
func (x *TunnelOpen) Reset() {
*x = TunnelOpen{}
- mi := &file_aether_proto_msgTypes[133]
+ mi := &file_aether_proto_msgTypes[138]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16862,7 +17733,7 @@ func (x *TunnelOpen) String() string {
func (*TunnelOpen) ProtoMessage() {}
func (x *TunnelOpen) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[133]
+ mi := &file_aether_proto_msgTypes[138]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16875,7 +17746,7 @@ func (x *TunnelOpen) ProtoReflect() protoreflect.Message {
// Deprecated: Use TunnelOpen.ProtoReflect.Descriptor instead.
func (*TunnelOpen) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{133}
+ return file_aether_proto_rawDescGZIP(), []int{138}
}
func (x *TunnelOpen) GetTunnelId() string {
@@ -16967,7 +17838,7 @@ type TunnelData struct {
func (x *TunnelData) Reset() {
*x = TunnelData{}
- mi := &file_aether_proto_msgTypes[134]
+ mi := &file_aether_proto_msgTypes[139]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -16979,7 +17850,7 @@ func (x *TunnelData) String() string {
func (*TunnelData) ProtoMessage() {}
func (x *TunnelData) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[134]
+ mi := &file_aether_proto_msgTypes[139]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -16992,7 +17863,7 @@ func (x *TunnelData) ProtoReflect() protoreflect.Message {
// Deprecated: Use TunnelData.ProtoReflect.Descriptor instead.
func (*TunnelData) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{134}
+ return file_aether_proto_rawDescGZIP(), []int{139}
}
func (x *TunnelData) GetTunnelId() string {
@@ -17034,7 +17905,7 @@ type TunnelClose struct {
func (x *TunnelClose) Reset() {
*x = TunnelClose{}
- mi := &file_aether_proto_msgTypes[135]
+ mi := &file_aether_proto_msgTypes[140]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17046,7 +17917,7 @@ func (x *TunnelClose) String() string {
func (*TunnelClose) ProtoMessage() {}
func (x *TunnelClose) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[135]
+ mi := &file_aether_proto_msgTypes[140]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17059,7 +17930,7 @@ func (x *TunnelClose) ProtoReflect() protoreflect.Message {
// Deprecated: Use TunnelClose.ProtoReflect.Descriptor instead.
func (*TunnelClose) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{135}
+ return file_aether_proto_rawDescGZIP(), []int{140}
}
func (x *TunnelClose) GetTunnelId() string {
@@ -17094,7 +17965,7 @@ type TunnelAck struct {
func (x *TunnelAck) Reset() {
*x = TunnelAck{}
- mi := &file_aether_proto_msgTypes[136]
+ mi := &file_aether_proto_msgTypes[141]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17106,7 +17977,7 @@ func (x *TunnelAck) String() string {
func (*TunnelAck) ProtoMessage() {}
func (x *TunnelAck) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[136]
+ mi := &file_aether_proto_msgTypes[141]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17119,7 +17990,7 @@ func (x *TunnelAck) ProtoReflect() protoreflect.Message {
// Deprecated: Use TunnelAck.ProtoReflect.Descriptor instead.
func (*TunnelAck) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{136}
+ return file_aether_proto_rawDescGZIP(), []int{141}
}
func (x *TunnelAck) GetTunnelId() string {
@@ -17166,7 +18037,7 @@ type ResolveAuthorityRequest struct {
func (x *ResolveAuthorityRequest) Reset() {
*x = ResolveAuthorityRequest{}
- mi := &file_aether_proto_msgTypes[137]
+ mi := &file_aether_proto_msgTypes[142]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17178,7 +18049,7 @@ func (x *ResolveAuthorityRequest) String() string {
func (*ResolveAuthorityRequest) ProtoMessage() {}
func (x *ResolveAuthorityRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[137]
+ mi := &file_aether_proto_msgTypes[142]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17191,7 +18062,7 @@ func (x *ResolveAuthorityRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResolveAuthorityRequest.ProtoReflect.Descriptor instead.
func (*ResolveAuthorityRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{137}
+ return file_aether_proto_rawDescGZIP(), []int{142}
}
func (x *ResolveAuthorityRequest) GetRequestId() string {
@@ -17252,7 +18123,7 @@ type ResolveAuthorityResponse struct {
func (x *ResolveAuthorityResponse) Reset() {
*x = ResolveAuthorityResponse{}
- mi := &file_aether_proto_msgTypes[138]
+ mi := &file_aether_proto_msgTypes[143]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17264,7 +18135,7 @@ func (x *ResolveAuthorityResponse) String() string {
func (*ResolveAuthorityResponse) ProtoMessage() {}
func (x *ResolveAuthorityResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[138]
+ mi := &file_aether_proto_msgTypes[143]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17277,7 +18148,7 @@ func (x *ResolveAuthorityResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResolveAuthorityResponse.ProtoReflect.Descriptor instead.
func (*ResolveAuthorityResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{138}
+ return file_aether_proto_rawDescGZIP(), []int{143}
}
func (x *ResolveAuthorityResponse) GetRequestId() string {
@@ -17324,7 +18195,7 @@ type ResolvedAuthority struct {
func (x *ResolvedAuthority) Reset() {
*x = ResolvedAuthority{}
- mi := &file_aether_proto_msgTypes[139]
+ mi := &file_aether_proto_msgTypes[144]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17336,7 +18207,7 @@ func (x *ResolvedAuthority) String() string {
func (*ResolvedAuthority) ProtoMessage() {}
func (x *ResolvedAuthority) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[139]
+ mi := &file_aether_proto_msgTypes[144]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17349,7 +18220,7 @@ func (x *ResolvedAuthority) ProtoReflect() protoreflect.Message {
// Deprecated: Use ResolvedAuthority.ProtoReflect.Descriptor instead.
func (*ResolvedAuthority) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{139}
+ return file_aether_proto_rawDescGZIP(), []int{144}
}
func (x *ResolvedAuthority) GetActor() *PrincipalRef {
@@ -17396,7 +18267,7 @@ type AuthorityGrantInfo struct {
func (x *AuthorityGrantInfo) Reset() {
*x = AuthorityGrantInfo{}
- mi := &file_aether_proto_msgTypes[140]
+ mi := &file_aether_proto_msgTypes[145]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17408,7 +18279,7 @@ func (x *AuthorityGrantInfo) String() string {
func (*AuthorityGrantInfo) ProtoMessage() {}
func (x *AuthorityGrantInfo) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[140]
+ mi := &file_aether_proto_msgTypes[145]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17421,7 +18292,7 @@ func (x *AuthorityGrantInfo) ProtoReflect() protoreflect.Message {
// Deprecated: Use AuthorityGrantInfo.ProtoReflect.Descriptor instead.
func (*AuthorityGrantInfo) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{140}
+ return file_aether_proto_rawDescGZIP(), []int{145}
}
func (x *AuthorityGrantInfo) GetGrantId() string {
@@ -17514,7 +18385,7 @@ type ConnectionStatusRequest struct {
func (x *ConnectionStatusRequest) Reset() {
*x = ConnectionStatusRequest{}
- mi := &file_aether_proto_msgTypes[141]
+ mi := &file_aether_proto_msgTypes[146]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17526,7 +18397,7 @@ func (x *ConnectionStatusRequest) String() string {
func (*ConnectionStatusRequest) ProtoMessage() {}
func (x *ConnectionStatusRequest) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[141]
+ mi := &file_aether_proto_msgTypes[146]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17539,7 +18410,7 @@ func (x *ConnectionStatusRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ConnectionStatusRequest.ProtoReflect.Descriptor instead.
func (*ConnectionStatusRequest) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{141}
+ return file_aether_proto_rawDescGZIP(), []int{146}
}
func (x *ConnectionStatusRequest) GetRequestId() string {
@@ -17572,7 +18443,7 @@ type ConnectionStatusResponse struct {
func (x *ConnectionStatusResponse) Reset() {
*x = ConnectionStatusResponse{}
- mi := &file_aether_proto_msgTypes[142]
+ mi := &file_aether_proto_msgTypes[147]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17584,7 +18455,7 @@ func (x *ConnectionStatusResponse) String() string {
func (*ConnectionStatusResponse) ProtoMessage() {}
func (x *ConnectionStatusResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[142]
+ mi := &file_aether_proto_msgTypes[147]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17597,7 +18468,7 @@ func (x *ConnectionStatusResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use ConnectionStatusResponse.ProtoReflect.Descriptor instead.
func (*ConnectionStatusResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{142}
+ return file_aether_proto_rawDescGZIP(), []int{147}
}
func (x *ConnectionStatusResponse) GetRequestId() string {
@@ -17663,7 +18534,7 @@ type TaskSubscriptionOperation struct {
func (x *TaskSubscriptionOperation) Reset() {
*x = TaskSubscriptionOperation{}
- mi := &file_aether_proto_msgTypes[143]
+ mi := &file_aether_proto_msgTypes[148]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17675,7 +18546,7 @@ func (x *TaskSubscriptionOperation) String() string {
func (*TaskSubscriptionOperation) ProtoMessage() {}
func (x *TaskSubscriptionOperation) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[143]
+ mi := &file_aether_proto_msgTypes[148]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17688,7 +18559,7 @@ func (x *TaskSubscriptionOperation) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskSubscriptionOperation.ProtoReflect.Descriptor instead.
func (*TaskSubscriptionOperation) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{143}
+ return file_aether_proto_rawDescGZIP(), []int{148}
}
func (x *TaskSubscriptionOperation) GetOp() TaskSubscriptionOperation_OpType {
@@ -17749,7 +18620,7 @@ type TaskSubscriptionOperationResponse struct {
func (x *TaskSubscriptionOperationResponse) Reset() {
*x = TaskSubscriptionOperationResponse{}
- mi := &file_aether_proto_msgTypes[144]
+ mi := &file_aether_proto_msgTypes[149]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -17761,7 +18632,7 @@ func (x *TaskSubscriptionOperationResponse) String() string {
func (*TaskSubscriptionOperationResponse) ProtoMessage() {}
func (x *TaskSubscriptionOperationResponse) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[144]
+ mi := &file_aether_proto_msgTypes[149]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17774,7 +18645,7 @@ func (x *TaskSubscriptionOperationResponse) ProtoReflect() protoreflect.Message
// Deprecated: Use TaskSubscriptionOperationResponse.ProtoReflect.Descriptor instead.
func (*TaskSubscriptionOperationResponse) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{144}
+ return file_aether_proto_rawDescGZIP(), []int{149}
}
func (x *TaskSubscriptionOperationResponse) GetSuccess() bool {
@@ -17834,21 +18705,515 @@ type TaskEvent struct {
sizeCache protoimpl.SizeCache
}
-func (x *TaskEvent) Reset() {
- *x = TaskEvent{}
- mi := &file_aether_proto_msgTypes[145]
+func (x *TaskEvent) Reset() {
+ *x = TaskEvent{}
+ mi := &file_aether_proto_msgTypes[150]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskEvent) ProtoMessage() {}
+
+func (x *TaskEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[150]
+ 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 TaskEvent.ProtoReflect.Descriptor instead.
+func (*TaskEvent) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{150}
+}
+
+func (x *TaskEvent) GetTaskId() string {
+ if x != nil {
+ return x.TaskId
+ }
+ return ""
+}
+
+func (x *TaskEvent) GetEmittedAtUnixMs() int64 {
+ if x != nil {
+ return x.EmittedAtUnixMs
+ }
+ return 0
+}
+
+func (x *TaskEvent) GetWorkspace() string {
+ if x != nil {
+ return x.Workspace
+ }
+ return ""
+}
+
+func (x *TaskEvent) GetParentTaskId() string {
+ if x != nil {
+ return x.ParentTaskId
+ }
+ return ""
+}
+
+func (x *TaskEvent) GetSubscriptionId() string {
+ if x != nil {
+ return x.SubscriptionId
+ }
+ return ""
+}
+
+func (x *TaskEvent) GetEvent() isTaskEvent_Event {
+ if x != nil {
+ return x.Event
+ }
+ return nil
+}
+
+func (x *TaskEvent) GetStatusChanged() *TaskStatusChangedEvent {
+ if x != nil {
+ if x, ok := x.Event.(*TaskEvent_StatusChanged); ok {
+ return x.StatusChanged
+ }
+ }
+ return nil
+}
+
+func (x *TaskEvent) GetProgress() *TaskProgressEvent {
+ if x != nil {
+ if x, ok := x.Event.(*TaskEvent_Progress); ok {
+ return x.Progress
+ }
+ }
+ return nil
+}
+
+func (x *TaskEvent) GetChildLifecycle() *TaskChildLifecycleEvent {
+ if x != nil {
+ if x, ok := x.Event.(*TaskEvent_ChildLifecycle); ok {
+ return x.ChildLifecycle
+ }
+ }
+ return nil
+}
+
+func (x *TaskEvent) GetAuthorityRequest() *TaskAuthorityRequestEventRelay {
+ if x != nil {
+ if x, ok := x.Event.(*TaskEvent_AuthorityRequest); ok {
+ return x.AuthorityRequest
+ }
+ }
+ return nil
+}
+
+type isTaskEvent_Event interface {
+ isTaskEvent_Event()
+}
+
+type TaskEvent_StatusChanged struct {
+ StatusChanged *TaskStatusChangedEvent `protobuf:"bytes,10,opt,name=status_changed,json=statusChanged,proto3,oneof"`
+}
+
+type TaskEvent_Progress struct {
+ Progress *TaskProgressEvent `protobuf:"bytes,11,opt,name=progress,proto3,oneof"`
+}
+
+type TaskEvent_ChildLifecycle struct {
+ ChildLifecycle *TaskChildLifecycleEvent `protobuf:"bytes,12,opt,name=child_lifecycle,json=childLifecycle,proto3,oneof"`
+}
+
+type TaskEvent_AuthorityRequest struct {
+ AuthorityRequest *TaskAuthorityRequestEventRelay `protobuf:"bytes,13,opt,name=authority_request,json=authorityRequest,proto3,oneof"`
+}
+
+func (*TaskEvent_StatusChanged) isTaskEvent_Event() {}
+
+func (*TaskEvent_Progress) isTaskEvent_Event() {}
+
+func (*TaskEvent_ChildLifecycle) isTaskEvent_Event() {}
+
+func (*TaskEvent_AuthorityRequest) isTaskEvent_Event() {}
+
+// TaskStatusChangedEvent fires on lifecycle transitions: pending->running,
+// running->completed, running->failed, paused->running, etc.
+type TaskStatusChangedEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ FromStatus TaskStatus `protobuf:"varint,1,opt,name=from_status,json=fromStatus,proto3,enum=aether.v1.TaskStatus" json:"from_status,omitempty"`
+ ToStatus TaskStatus `protobuf:"varint,2,opt,name=to_status,json=toStatus,proto3,enum=aether.v1.TaskStatus" json:"to_status,omitempty"`
+ Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` // optional narrative (e.g. cancel reason, fail message)
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TaskStatusChangedEvent) Reset() {
+ *x = TaskStatusChangedEvent{}
+ mi := &file_aether_proto_msgTypes[151]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskStatusChangedEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskStatusChangedEvent) ProtoMessage() {}
+
+func (x *TaskStatusChangedEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[151]
+ 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 TaskStatusChangedEvent.ProtoReflect.Descriptor instead.
+func (*TaskStatusChangedEvent) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{151}
+}
+
+func (x *TaskStatusChangedEvent) GetFromStatus() TaskStatus {
+ if x != nil {
+ return x.FromStatus
+ }
+ return TaskStatus_TASK_STATUS_UNSPECIFIED
+}
+
+func (x *TaskStatusChangedEvent) GetToStatus() TaskStatus {
+ if x != nil {
+ return x.ToStatus
+ }
+ return TaskStatus_TASK_STATUS_UNSPECIFIED
+}
+
+func (x *TaskStatusChangedEvent) GetReason() string {
+ if x != nil {
+ return x.Reason
+ }
+ return ""
+}
+
+// TaskProgressEvent is the task-scoped projection of a ProgressUpdate whose
+// task_id matches the subscribed task.
+type TaskProgressEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
+ Progress float64 `protobuf:"fixed64,2,opt,name=progress,proto3" json:"progress,omitempty"`
+ Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TaskProgressEvent) Reset() {
+ *x = TaskProgressEvent{}
+ mi := &file_aether_proto_msgTypes[152]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskProgressEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskProgressEvent) ProtoMessage() {}
+
+func (x *TaskProgressEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[152]
+ 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 TaskProgressEvent.ProtoReflect.Descriptor instead.
+func (*TaskProgressEvent) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{152}
+}
+
+func (x *TaskProgressEvent) GetState() string {
+ if x != nil {
+ return x.State
+ }
+ return ""
+}
+
+func (x *TaskProgressEvent) GetProgress() float64 {
+ if x != nil {
+ return x.Progress
+ }
+ return 0
+}
+
+func (x *TaskProgressEvent) GetMessage() string {
+ if x != nil {
+ return x.Message
+ }
+ return ""
+}
+
+func (x *TaskProgressEvent) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
+// TaskChildLifecycleEvent reports a child task's lifecycle transition. The
+// `lifecycle` field is a coarse classifier: "spawned" | "transitioned" |
+// "completed".
+type TaskChildLifecycleEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ChildTaskId string `protobuf:"bytes,1,opt,name=child_task_id,json=childTaskId,proto3" json:"child_task_id,omitempty"`
+ ChildStatus TaskStatus `protobuf:"varint,2,opt,name=child_status,json=childStatus,proto3,enum=aether.v1.TaskStatus" json:"child_status,omitempty"`
+ Lifecycle string `protobuf:"bytes,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TaskChildLifecycleEvent) Reset() {
+ *x = TaskChildLifecycleEvent{}
+ mi := &file_aether_proto_msgTypes[153]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskChildLifecycleEvent) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskChildLifecycleEvent) ProtoMessage() {}
+
+func (x *TaskChildLifecycleEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[153]
+ 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 TaskChildLifecycleEvent.ProtoReflect.Descriptor instead.
+func (*TaskChildLifecycleEvent) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{153}
+}
+
+func (x *TaskChildLifecycleEvent) GetChildTaskId() string {
+ if x != nil {
+ return x.ChildTaskId
+ }
+ return ""
+}
+
+func (x *TaskChildLifecycleEvent) GetChildStatus() TaskStatus {
+ if x != nil {
+ return x.ChildStatus
+ }
+ return TaskStatus_TASK_STATUS_UNSPECIFIED
+}
+
+func (x *TaskChildLifecycleEvent) GetLifecycle() string {
+ if x != nil {
+ return x.Lifecycle
+ }
+ return ""
+}
+
+// TaskAuthorityRequestEventRelay re-emits a Phase 2 AuthorityRequestEvent onto
+// the per-task event stream when the request's task_id matches the subscribed
+// task. Just embeds the existing event type.
+type TaskAuthorityRequestEventRelay struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Event *AuthorityRequestEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TaskAuthorityRequestEventRelay) Reset() {
+ *x = TaskAuthorityRequestEventRelay{}
+ mi := &file_aether_proto_msgTypes[154]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskAuthorityRequestEventRelay) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskAuthorityRequestEventRelay) ProtoMessage() {}
+
+func (x *TaskAuthorityRequestEventRelay) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[154]
+ 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 TaskAuthorityRequestEventRelay.ProtoReflect.Descriptor instead.
+func (*TaskAuthorityRequestEventRelay) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{154}
+}
+
+func (x *TaskAuthorityRequestEventRelay) GetEvent() *AuthorityRequestEvent {
+ if x != nil {
+ return x.Event
+ }
+ return nil
+}
+
+// ResourceAccessRequest is the portable runtime authorization tuple evaluated
+// by the gateway. It is intentionally independent of any tool protocol: the
+// same primitive gates workspace views, catalog providers/entries, and future
+// logical resources. All string fields are required except workspace.
+type ResourceAccessRequest 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"`
+ Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"`
+ Workspace string `protobuf:"bytes,4,opt,name=workspace,proto3" json:"workspace,omitempty"`
+ RequiredAccessLevel int32 `protobuf:"varint,5,opt,name=required_access_level,json=requiredAccessLevel,proto3" json:"required_access_level,omitempty"`
+ // Caller-generated correlation binding for a single logical action. A
+ // recipient compares this value with its application payload/request.
+ CorrelationId string `protobuf:"bytes,6,opt,name=correlation_id,json=correlationId,proto3" json:"correlation_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ResourceAccessRequest) Reset() {
+ *x = ResourceAccessRequest{}
+ mi := &file_aether_proto_msgTypes[155]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ResourceAccessRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ResourceAccessRequest) ProtoMessage() {}
+
+func (x *ResourceAccessRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[155]
+ 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 ResourceAccessRequest.ProtoReflect.Descriptor instead.
+func (*ResourceAccessRequest) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{155}
+}
+
+func (x *ResourceAccessRequest) GetResourceType() string {
+ if x != nil {
+ return x.ResourceType
+ }
+ return ""
+}
+
+func (x *ResourceAccessRequest) GetResourceId() string {
+ if x != nil {
+ return x.ResourceId
+ }
+ return ""
+}
+
+func (x *ResourceAccessRequest) GetOperation() string {
+ if x != nil {
+ return x.Operation
+ }
+ return ""
+}
+
+func (x *ResourceAccessRequest) GetWorkspace() string {
+ if x != nil {
+ return x.Workspace
+ }
+ return ""
+}
+
+func (x *ResourceAccessRequest) GetRequiredAccessLevel() int32 {
+ if x != nil {
+ return x.RequiredAccessLevel
+ }
+ return 0
+}
+
+func (x *ResourceAccessRequest) GetCorrelationId() string {
+ if x != nil {
+ return x.CorrelationId
+ }
+ return ""
+}
+
+// AccessDecisionReceipt is gateway-authored transport metadata. Receivers may
+// trust it only when it arrived in the Aether envelope, never when an
+// equivalent object appears inside an application payload.
+type AccessDecisionReceipt struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ DecisionId string `protobuf:"bytes,1,opt,name=decision_id,json=decisionId,proto3" json:"decision_id,omitempty"`
+ Request *ResourceAccessRequest `protobuf:"bytes,2,opt,name=request,proto3" json:"request,omitempty"`
+ Allowed bool `protobuf:"varint,3,opt,name=allowed,proto3" json:"allowed,omitempty"`
+ Decision string `protobuf:"bytes,4,opt,name=decision,proto3" json:"decision,omitempty"` // "ALLOW" or "DENY"
+ EffectiveAccessLevel int32 `protobuf:"varint,5,opt,name=effective_access_level,json=effectiveAccessLevel,proto3" json:"effective_access_level,omitempty"`
+ Actor *PrincipalRef `protobuf:"bytes,6,opt,name=actor,proto3" json:"actor,omitempty"` // authenticated connected principal
+ Subject *PrincipalRef `protobuf:"bytes,7,opt,name=subject,proto3" json:"subject,omitempty"` // populated for on-behalf-of checks
+ RootSubject *PrincipalRef `protobuf:"bytes,8,opt,name=root_subject,json=rootSubject,proto3" json:"root_subject,omitempty"` // populated when the grant records one
+ AuthorityMode string `protobuf:"bytes,9,opt,name=authority_mode,json=authorityMode,proto3" json:"authority_mode,omitempty"` // "direct" or "on_behalf_of"
+ GrantId string `protobuf:"bytes,10,opt,name=grant_id,json=grantId,proto3" json:"grant_id,omitempty"`
+ RootGrantId string `protobuf:"bytes,11,opt,name=root_grant_id,json=rootGrantId,proto3" json:"root_grant_id,omitempty"`
+ EvaluatedAtMs int64 `protobuf:"varint,12,opt,name=evaluated_at_ms,json=evaluatedAtMs,proto3" json:"evaluated_at_ms,omitempty"`
+ ExpiresAtMs int64 `protobuf:"varint,13,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"`
+ DenialCode string `protobuf:"bytes,14,opt,name=denial_code,json=denialCode,proto3" json:"denial_code,omitempty"` // stable code; empty for allowed checks
+ // Populated for checked SendMessage and ProxyHTTP delivery. This binds the
+ // receipt to the concrete post-wildcard-resolution target that received it.
+ DeliveryTarget string `protobuf:"bytes,15,opt,name=delivery_target,json=deliveryTarget,proto3" json:"delivery_target,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *AccessDecisionReceipt) Reset() {
+ *x = AccessDecisionReceipt{}
+ mi := &file_aether_proto_msgTypes[156]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *TaskEvent) String() string {
+func (x *AccessDecisionReceipt) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*TaskEvent) ProtoMessage() {}
+func (*AccessDecisionReceipt) ProtoMessage() {}
-func (x *TaskEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[145]
+func (x *AccessDecisionReceipt) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[156]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -17859,143 +19224,140 @@ func (x *TaskEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use TaskEvent.ProtoReflect.Descriptor instead.
-func (*TaskEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{145}
+// Deprecated: Use AccessDecisionReceipt.ProtoReflect.Descriptor instead.
+func (*AccessDecisionReceipt) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{156}
}
-func (x *TaskEvent) GetTaskId() string {
+func (x *AccessDecisionReceipt) GetDecisionId() string {
if x != nil {
- return x.TaskId
+ return x.DecisionId
}
return ""
}
-func (x *TaskEvent) GetEmittedAtUnixMs() int64 {
+func (x *AccessDecisionReceipt) GetRequest() *ResourceAccessRequest {
if x != nil {
- return x.EmittedAtUnixMs
+ return x.Request
}
- return 0
+ return nil
}
-func (x *TaskEvent) GetWorkspace() string {
+func (x *AccessDecisionReceipt) GetAllowed() bool {
if x != nil {
- return x.Workspace
+ return x.Allowed
}
- return ""
+ return false
}
-func (x *TaskEvent) GetParentTaskId() string {
+func (x *AccessDecisionReceipt) GetDecision() string {
if x != nil {
- return x.ParentTaskId
+ return x.Decision
}
return ""
}
-func (x *TaskEvent) GetSubscriptionId() string {
+func (x *AccessDecisionReceipt) GetEffectiveAccessLevel() int32 {
if x != nil {
- return x.SubscriptionId
+ return x.EffectiveAccessLevel
}
- return ""
+ return 0
}
-func (x *TaskEvent) GetEvent() isTaskEvent_Event {
+func (x *AccessDecisionReceipt) GetActor() *PrincipalRef {
if x != nil {
- return x.Event
+ return x.Actor
}
return nil
}
-func (x *TaskEvent) GetStatusChanged() *TaskStatusChangedEvent {
+func (x *AccessDecisionReceipt) GetSubject() *PrincipalRef {
if x != nil {
- if x, ok := x.Event.(*TaskEvent_StatusChanged); ok {
- return x.StatusChanged
- }
+ return x.Subject
}
return nil
}
-func (x *TaskEvent) GetProgress() *TaskProgressEvent {
+func (x *AccessDecisionReceipt) GetRootSubject() *PrincipalRef {
if x != nil {
- if x, ok := x.Event.(*TaskEvent_Progress); ok {
- return x.Progress
- }
+ return x.RootSubject
}
return nil
}
-func (x *TaskEvent) GetChildLifecycle() *TaskChildLifecycleEvent {
+func (x *AccessDecisionReceipt) GetAuthorityMode() string {
if x != nil {
- if x, ok := x.Event.(*TaskEvent_ChildLifecycle); ok {
- return x.ChildLifecycle
- }
+ return x.AuthorityMode
}
- return nil
+ return ""
}
-func (x *TaskEvent) GetAuthorityRequest() *TaskAuthorityRequestEventRelay {
+func (x *AccessDecisionReceipt) GetGrantId() string {
if x != nil {
- if x, ok := x.Event.(*TaskEvent_AuthorityRequest); ok {
- return x.AuthorityRequest
- }
+ return x.GrantId
}
- return nil
+ return ""
}
-type isTaskEvent_Event interface {
- isTaskEvent_Event()
+func (x *AccessDecisionReceipt) GetRootGrantId() string {
+ if x != nil {
+ return x.RootGrantId
+ }
+ return ""
}
-type TaskEvent_StatusChanged struct {
- StatusChanged *TaskStatusChangedEvent `protobuf:"bytes,10,opt,name=status_changed,json=statusChanged,proto3,oneof"`
+func (x *AccessDecisionReceipt) GetEvaluatedAtMs() int64 {
+ if x != nil {
+ return x.EvaluatedAtMs
+ }
+ return 0
}
-type TaskEvent_Progress struct {
- Progress *TaskProgressEvent `protobuf:"bytes,11,opt,name=progress,proto3,oneof"`
+func (x *AccessDecisionReceipt) GetExpiresAtMs() int64 {
+ if x != nil {
+ return x.ExpiresAtMs
+ }
+ return 0
}
-type TaskEvent_ChildLifecycle struct {
- ChildLifecycle *TaskChildLifecycleEvent `protobuf:"bytes,12,opt,name=child_lifecycle,json=childLifecycle,proto3,oneof"`
+func (x *AccessDecisionReceipt) GetDenialCode() string {
+ if x != nil {
+ return x.DenialCode
+ }
+ return ""
}
-type TaskEvent_AuthorityRequest struct {
- AuthorityRequest *TaskAuthorityRequestEventRelay `protobuf:"bytes,13,opt,name=authority_request,json=authorityRequest,proto3,oneof"`
+func (x *AccessDecisionReceipt) GetDeliveryTarget() string {
+ if x != nil {
+ return x.DeliveryTarget
+ }
+ return ""
}
-func (*TaskEvent_StatusChanged) isTaskEvent_Event() {}
-
-func (*TaskEvent_Progress) isTaskEvent_Event() {}
-
-func (*TaskEvent_ChildLifecycle) isTaskEvent_Event() {}
-
-func (*TaskEvent_AuthorityRequest) isTaskEvent_Event() {}
-
-// TaskStatusChangedEvent fires on lifecycle transitions: pending->running,
-// running->completed, running->failed, paused->running, etc.
-type TaskStatusChangedEvent struct {
+type AccessCheckOperation struct {
state protoimpl.MessageState `protogen:"open.v1"`
- FromStatus TaskStatus `protobuf:"varint,1,opt,name=from_status,json=fromStatus,proto3,enum=aether.v1.TaskStatus" json:"from_status,omitempty"`
- ToStatus TaskStatus `protobuf:"varint,2,opt,name=to_status,json=toStatus,proto3,enum=aether.v1.TaskStatus" json:"to_status,omitempty"`
- Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` // optional narrative (e.g. cancel reason, fail message)
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ Access *ResourceAccessRequest `protobuf:"bytes,2,opt,name=access,proto3" json:"access,omitempty"`
+ Authorization *AuthorizationContext `protobuf:"bytes,3,opt,name=authorization,proto3" json:"authorization,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *TaskStatusChangedEvent) Reset() {
- *x = TaskStatusChangedEvent{}
- mi := &file_aether_proto_msgTypes[146]
+func (x *AccessCheckOperation) Reset() {
+ *x = AccessCheckOperation{}
+ mi := &file_aether_proto_msgTypes[157]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *TaskStatusChangedEvent) String() string {
+func (x *AccessCheckOperation) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*TaskStatusChangedEvent) ProtoMessage() {}
+func (*AccessCheckOperation) ProtoMessage() {}
-func (x *TaskStatusChangedEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[146]
+func (x *AccessCheckOperation) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[157]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -18006,59 +19368,57 @@ func (x *TaskStatusChangedEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use TaskStatusChangedEvent.ProtoReflect.Descriptor instead.
-func (*TaskStatusChangedEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{146}
+// Deprecated: Use AccessCheckOperation.ProtoReflect.Descriptor instead.
+func (*AccessCheckOperation) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{157}
}
-func (x *TaskStatusChangedEvent) GetFromStatus() TaskStatus {
+func (x *AccessCheckOperation) GetRequestId() string {
if x != nil {
- return x.FromStatus
+ return x.RequestId
}
- return TaskStatus_TASK_STATUS_UNSPECIFIED
+ return ""
}
-func (x *TaskStatusChangedEvent) GetToStatus() TaskStatus {
+func (x *AccessCheckOperation) GetAccess() *ResourceAccessRequest {
if x != nil {
- return x.ToStatus
+ return x.Access
}
- return TaskStatus_TASK_STATUS_UNSPECIFIED
+ return nil
}
-func (x *TaskStatusChangedEvent) GetReason() string {
+func (x *AccessCheckOperation) GetAuthorization() *AuthorizationContext {
if x != nil {
- return x.Reason
+ return x.Authorization
}
- return ""
+ return nil
}
-// TaskProgressEvent is the task-scoped projection of a ProgressUpdate whose
-// task_id matches the subscribed task.
-type TaskProgressEvent struct {
+type AccessCheckResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
- State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
- Progress float64 `protobuf:"fixed64,2,opt,name=progress,proto3" json:"progress,omitempty"`
- Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
- Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` // evaluation completed; denial is success
+ Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+ Decision *AccessDecisionReceipt `protobuf:"bytes,4,opt,name=decision,proto3" json:"decision,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *TaskProgressEvent) Reset() {
- *x = TaskProgressEvent{}
- mi := &file_aether_proto_msgTypes[147]
+func (x *AccessCheckResponse) Reset() {
+ *x = AccessCheckResponse{}
+ mi := &file_aether_proto_msgTypes[158]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *TaskProgressEvent) String() string {
+func (x *AccessCheckResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*TaskProgressEvent) ProtoMessage() {}
+func (*AccessCheckResponse) ProtoMessage() {}
-func (x *TaskProgressEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[147]
+func (x *AccessCheckResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[158]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -18069,66 +19429,63 @@ func (x *TaskProgressEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use TaskProgressEvent.ProtoReflect.Descriptor instead.
-func (*TaskProgressEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{147}
+// Deprecated: Use AccessCheckResponse.ProtoReflect.Descriptor instead.
+func (*AccessCheckResponse) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{158}
}
-func (x *TaskProgressEvent) GetState() string {
+func (x *AccessCheckResponse) GetRequestId() string {
if x != nil {
- return x.State
+ return x.RequestId
}
return ""
}
-func (x *TaskProgressEvent) GetProgress() float64 {
+func (x *AccessCheckResponse) GetSuccess() bool {
if x != nil {
- return x.Progress
+ return x.Success
}
- return 0
+ return false
}
-func (x *TaskProgressEvent) GetMessage() string {
+func (x *AccessCheckResponse) GetError() string {
if x != nil {
- return x.Message
+ return x.Error
}
return ""
}
-func (x *TaskProgressEvent) GetMetadata() map[string]string {
+func (x *AccessCheckResponse) GetDecision() *AccessDecisionReceipt {
if x != nil {
- return x.Metadata
+ return x.Decision
}
return nil
}
-// TaskChildLifecycleEvent reports a child task's lifecycle transition. The
-// `lifecycle` field is a coarse classifier: "spawned" | "transitioned" |
-// "completed".
-type TaskChildLifecycleEvent struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- ChildTaskId string `protobuf:"bytes,1,opt,name=child_task_id,json=childTaskId,proto3" json:"child_task_id,omitempty"`
- ChildStatus TaskStatus `protobuf:"varint,2,opt,name=child_status,json=childStatus,proto3,enum=aether.v1.TaskStatus" json:"child_status,omitempty"`
- Lifecycle string `protobuf:"bytes,3,opt,name=lifecycle,proto3" json:"lifecycle,omitempty"`
+type BatchAccessCheckOperation struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ Access []*ResourceAccessRequest `protobuf:"bytes,2,rep,name=access,proto3" json:"access,omitempty"`
+ Authorization *AuthorizationContext `protobuf:"bytes,3,opt,name=authorization,proto3" json:"authorization,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *TaskChildLifecycleEvent) Reset() {
- *x = TaskChildLifecycleEvent{}
- mi := &file_aether_proto_msgTypes[148]
+func (x *BatchAccessCheckOperation) Reset() {
+ *x = BatchAccessCheckOperation{}
+ mi := &file_aether_proto_msgTypes[159]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *TaskChildLifecycleEvent) String() string {
+func (x *BatchAccessCheckOperation) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*TaskChildLifecycleEvent) ProtoMessage() {}
+func (*BatchAccessCheckOperation) ProtoMessage() {}
-func (x *TaskChildLifecycleEvent) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[148]
+func (x *BatchAccessCheckOperation) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[159]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -18139,57 +19496,58 @@ func (x *TaskChildLifecycleEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use TaskChildLifecycleEvent.ProtoReflect.Descriptor instead.
-func (*TaskChildLifecycleEvent) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{148}
+// Deprecated: Use BatchAccessCheckOperation.ProtoReflect.Descriptor instead.
+func (*BatchAccessCheckOperation) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{159}
}
-func (x *TaskChildLifecycleEvent) GetChildTaskId() string {
+func (x *BatchAccessCheckOperation) GetRequestId() string {
if x != nil {
- return x.ChildTaskId
+ return x.RequestId
}
return ""
}
-func (x *TaskChildLifecycleEvent) GetChildStatus() TaskStatus {
+func (x *BatchAccessCheckOperation) GetAccess() []*ResourceAccessRequest {
if x != nil {
- return x.ChildStatus
+ return x.Access
}
- return TaskStatus_TASK_STATUS_UNSPECIFIED
+ return nil
}
-func (x *TaskChildLifecycleEvent) GetLifecycle() string {
+func (x *BatchAccessCheckOperation) GetAuthorization() *AuthorizationContext {
if x != nil {
- return x.Lifecycle
+ return x.Authorization
}
- return ""
+ return nil
}
-// TaskAuthorityRequestEventRelay re-emits a Phase 2 AuthorityRequestEvent onto
-// the per-task event stream when the request's task_id matches the subscribed
-// task. Just embeds the existing event type.
-type TaskAuthorityRequestEventRelay struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Event *AuthorityRequestEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"`
+type BatchAccessCheckResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
+ Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
+ // Same order and cardinality as BatchAccessCheckOperation.access.
+ Decisions []*AccessDecisionReceipt `protobuf:"bytes,4,rep,name=decisions,proto3" json:"decisions,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *TaskAuthorityRequestEventRelay) Reset() {
- *x = TaskAuthorityRequestEventRelay{}
- mi := &file_aether_proto_msgTypes[149]
+func (x *BatchAccessCheckResponse) Reset() {
+ *x = BatchAccessCheckResponse{}
+ mi := &file_aether_proto_msgTypes[160]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *TaskAuthorityRequestEventRelay) String() string {
+func (x *BatchAccessCheckResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*TaskAuthorityRequestEventRelay) ProtoMessage() {}
+func (*BatchAccessCheckResponse) ProtoMessage() {}
-func (x *TaskAuthorityRequestEventRelay) ProtoReflect() protoreflect.Message {
- mi := &file_aether_proto_msgTypes[149]
+func (x *BatchAccessCheckResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_aether_proto_msgTypes[160]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -18200,14 +19558,35 @@ func (x *TaskAuthorityRequestEventRelay) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use TaskAuthorityRequestEventRelay.ProtoReflect.Descriptor instead.
-func (*TaskAuthorityRequestEventRelay) Descriptor() ([]byte, []int) {
- return file_aether_proto_rawDescGZIP(), []int{149}
+// Deprecated: Use BatchAccessCheckResponse.ProtoReflect.Descriptor instead.
+func (*BatchAccessCheckResponse) Descriptor() ([]byte, []int) {
+ return file_aether_proto_rawDescGZIP(), []int{160}
}
-func (x *TaskAuthorityRequestEventRelay) GetEvent() *AuthorityRequestEvent {
+func (x *BatchAccessCheckResponse) GetRequestId() string {
if x != nil {
- return x.Event
+ return x.RequestId
+ }
+ return ""
+}
+
+func (x *BatchAccessCheckResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+func (x *BatchAccessCheckResponse) GetError() string {
+ if x != nil {
+ return x.Error
+ }
+ return ""
+}
+
+func (x *BatchAccessCheckResponse) GetDecisions() []*AccessDecisionReceipt {
+ if x != nil {
+ return x.Decisions
}
return nil
}
@@ -18216,7 +19595,7 @@ var File_aether_proto protoreflect.FileDescriptor
const file_aether_proto_rawDesc = "" +
"\n" +
- "\faether.proto\x12\taether.v1\"\xeb\x10\n" +
+ "\faether.proto\x12\taether.v1\"\x87\x12\n" +
"\x0fUpstreamMessage\x12/\n" +
"\x04init\x18\x01 \x01(\v2\x19.aether.v1.InitConnectionH\x00R\x04init\x12,\n" +
"\x04send\x18\x02 \x01(\v2\x16.aether.v1.SendMessageH\x00R\x04send\x12G\n" +
@@ -18258,9 +19637,11 @@ const file_aether_proto_rawDesc = "" +
"\x19connection_status_request\x18\x1c \x01(\v2\".aether.v1.ConnectionStatusRequestH\x00R\x17connectionStatusRequest\x12R\n" +
"\x12submit_audit_event\x18\x1d \x01(\v2\".aether.v1.SubmitAuditEventRequestH\x00R\x10submitAuditEvent\x12X\n" +
"\x14authority_request_op\x18\x1e \x01(\v2$.aether.v1.AuthorityRequestOperationH\x00R\x12authorityRequestOp\x12X\n" +
- "\x14task_subscription_op\x18\x1f \x01(\v2$.aether.v1.TaskSubscriptionOperationH\x00R\x12taskSubscriptionOp\x12+\n" +
+ "\x14task_subscription_op\x18\x1f \x01(\v2$.aether.v1.TaskSubscriptionOperationH\x00R\x12taskSubscriptionOp\x12D\n" +
+ "\faccess_check\x18! \x01(\v2\x1f.aether.v1.AccessCheckOperationH\x00R\vaccessCheck\x12T\n" +
+ "\x12batch_access_check\x18\" \x01(\v2$.aether.v1.BatchAccessCheckOperationH\x00R\x10batchAccessCheck\x12+\n" +
"\x11active_extensions\x18 \x03(\tR\x10activeExtensionsB\t\n" +
- "\apayload\"\xe4\x14\n" +
+ "\apayload\"\xa0\x16\n" +
"\x11DownstreamMessage\x12.\n" +
"\x03msg\x18\x01 \x01(\v2\x1a.aether.v1.IncomingMessageH\x00R\x03msg\x123\n" +
"\x06config\x18\x02 \x01(\v2\x19.aether.v1.ConfigSnapshotH\x00R\x06config\x12+\n" +
@@ -18307,7 +19688,9 @@ const file_aether_proto_rawDesc = "" +
"\x0ftask_hibernated\x18# \x01(\v2\x19.aether.v1.TaskHibernatedH\x00R\x0etaskHibernated\x12l\n" +
"\x1atask_subscription_response\x18$ \x01(\v2,.aether.v1.TaskSubscriptionOperationResponseH\x00R\x18taskSubscriptionResponse\x125\n" +
"\n" +
- "task_event\x18% \x01(\v2\x14.aether.v1.TaskEventH\x00R\ttaskEvent\x12+\n" +
+ "task_event\x18% \x01(\v2\x14.aether.v1.TaskEventH\x00R\ttaskEvent\x12T\n" +
+ "\x15access_check_response\x18' \x01(\v2\x1e.aether.v1.AccessCheckResponseH\x00R\x13accessCheckResponse\x12d\n" +
+ "\x1bbatch_access_check_response\x18( \x01(\v2#.aether.v1.BatchAccessCheckResponseH\x00R\x18batchAccessCheckResponse\x12+\n" +
"\x11active_extensions\x18& \x03(\tR\x10activeExtensionsB\t\n" +
"\apayload\"k\n" +
"\x0eTaskHibernated\x12\x17\n" +
@@ -18410,13 +19793,30 @@ const file_aether_proto_rawDesc = "" +
"audienceId\x12(\n" +
"\x10max_access_level\x18\x04 \x01(\x05R\x0emaxAccessLevel\x12'\n" +
"\x0fworkspace_scope\x18\x05 \x03(\tR\x0eworkspaceScope\x12\"\n" +
- "\rexpires_at_ms\x18\x06 \x01(\x03R\vexpiresAtMs\"\xf1\x01\n" +
+ "\rexpires_at_ms\x18\x06 \x01(\x03R\vexpiresAtMs\"\x9a\x03\n" +
"\vSendMessage\x12!\n" +
"\ftarget_topic\x18\x01 \x01(\tR\vtargetTopic\x12\x18\n" +
"\apayload\x18\x02 \x01(\fR\apayload\x129\n" +
"\fmessage_type\x18\x03 \x01(\x0e2\x16.aether.v1.MessageTypeR\vmessageType\x12E\n" +
"\rauthorization\x18\x04 \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\x12#\n" +
- "\rapp_workspace\x18\x05 \x01(\tR\fappWorkspace\"\xff\x01\n" +
+ "\rapp_workspace\x18\x05 \x01(\tR\fappWorkspace\x12G\n" +
+ "\x0echecked_access\x18\x06 \x01(\v2 .aether.v1.ResourceAccessRequestR\rcheckedAccess\x12^\n" +
+ "\x16authority_continuation\x18\a \x01(\v2'.aether.v1.AuthorityContinuationRequestR\x15authorityContinuation\"\xef\x01\n" +
+ "\x1aAuthorityContinuationScope\x12'\n" +
+ "\x0fworkspace_scope\x18\x01 \x03(\tR\x0eworkspaceScope\x12U\n" +
+ "\x0eresource_scope\x18\x02 \x03(\v2..aether.v1.ACLAuthorityGrantResourceScopeEntryR\rresourceScope\x12'\n" +
+ "\x0foperation_scope\x18\x03 \x03(\tR\x0eoperationScope\x12(\n" +
+ "\x10max_access_level\x18\x04 \x01(\x05R\x0emaxAccessLevel\"\xae\x02\n" +
+ "\x1cAuthorityContinuationRequest\x12P\n" +
+ "\n" +
+ "scope_mode\x18\x01 \x01(\x0e21.aether.v1.AuthorityContinuationRequest.ScopeModeR\tscopeMode\x12\x1d\n" +
+ "\n" +
+ "binding_id\x18\x02 \x01(\tR\tbindingId\x12;\n" +
+ "\x05scope\x18\x03 \x01(\v2%.aether.v1.AuthorityContinuationScopeR\x05scope\"`\n" +
+ "\tScopeMode\x12\x1a\n" +
+ "\x16SCOPE_MODE_UNSPECIFIED\x10\x00\x12\x1d\n" +
+ "\x19SCOPE_MODE_INHERIT_PARENT\x10\x01\x12\x18\n" +
+ "\x14SCOPE_MODE_ATTENUATE\x10\x02\"\xff\x01\n" +
"\x06Metric\x12\x19\n" +
"\btrace_id\x18\x01 \x01(\tR\atraceId\x120\n" +
"\aentries\x18\x02 \x03(\v2\x16.aether.v1.MetricEntryR\aentries\x12;\n" +
@@ -18496,13 +19896,23 @@ const file_aether_proto_rawDesc = "" +
"\n" +
"KvMapEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\xec\x01\n" +
+ "\x05value\x18\x02 \x01(\fR\x05value:\x028\x01\"\x91\x03\n" +
"\x0fIncomingMessage\x12!\n" +
"\fsource_topic\x18\x01 \x01(\tR\vsourceTopic\x12\x18\n" +
"\apayload\x18\x02 \x01(\fR\apayload\x129\n" +
"\fmessage_type\x18\x03 \x01(\x0e2\x16.aether.v1.MessageTypeR\vmessageType\x12\x1c\n" +
"\tworkspace\x18\x04 \x01(\tR\tworkspace\x12C\n" +
- "\x11on_behalf_subject\x18\x05 \x01(\v2\x17.aether.v1.PrincipalRefR\x0fonBehalfSubject\"\xf0\x05\n" +
+ "\x11on_behalf_subject\x18\x05 \x01(\v2\x17.aether.v1.PrincipalRefR\x0fonBehalfSubject\x12G\n" +
+ "\x0eaccess_receipt\x18\x06 \x01(\v2 .aether.v1.AccessDecisionReceiptR\raccessReceipt\x12Z\n" +
+ "\x17forwarded_authorization\x18\a \x01(\v2!.aether.v1.ForwardedAuthorizationR\x16forwardedAuthorization\"\xac\x02\n" +
+ "\x16ForwardedAuthorization\x12E\n" +
+ "\rauthorization\x18\x01 \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\x12\"\n" +
+ "\rroot_grant_id\x18\x02 \x01(\tR\vrootGrantId\x12\"\n" +
+ "\rexpires_at_ms\x18\x03 \x01(\x03R\vexpiresAtMs\x12'\n" +
+ "\x0fdelivery_target\x18\x04 \x01(\tR\x0edeliveryTarget\x12\x1d\n" +
+ "\n" +
+ "binding_id\x18\x05 \x01(\tR\tbindingId\x12;\n" +
+ "\x05scope\x18\x06 \x01(\v2%.aether.v1.AuthorityContinuationScopeR\x05scope\"\xf0\x05\n" +
"\x0eConfigSnapshot\x125\n" +
"\x02kv\x18\x01 \x03(\v2!.aether.v1.ConfigSnapshot.KvEntryB\x02\x18\x01R\x02kv\x12H\n" +
"\tglobal_kv\x18\x02 \x03(\v2'.aether.v1.ConfigSnapshot.GlobalKvEntryB\x02\x18\x01R\bglobalKv\x12M\n" +
@@ -18554,7 +19964,8 @@ const file_aether_proto_rawDesc = "" +
"\n" +
"event_name\x18\x02 \x01(\tR\teventName\x126\n" +
"\von_statuses\x18\x03 \x03(\x0e2\x15.aether.v1.TaskStatusR\n" +
- "onStatuses\"\xd9\b\n" +
+ "onStatuses\"\xd8\n" +
+ "\n" +
"\x11CreateTaskRequest\x12\x1b\n" +
"\ttask_type\x18\x01 \x01(\tR\btaskType\x12\x1c\n" +
"\tworkspace\x18\x02 \x01(\tR\tworkspace\x12F\n" +
@@ -18579,7 +19990,11 @@ const file_aether_proto_rawDesc = "" +
"\x0ecorrelation_id\x18\x11 \x01(\tR\rcorrelationId\x12 \n" +
"\froot_task_id\x18\x12 \x01(\tR\n" +
"rootTaskId\x12I\n" +
- "\x10completion_event\x18\x13 \x01(\v2\x1e.aether.v1.TaskCompletionEventR\x0fcompletionEvent\x1aG\n" +
+ "\x10completion_event\x18\x13 \x01(\v2\x1e.aether.v1.TaskCompletionEventR\x0fcompletionEvent\x12$\n" +
+ "\x0eparent_task_id\x18\x14 \x01(\tR\fparentTaskId\x12R\n" +
+ "\x15target_offline_policy\x18\x15 \x01(\x0e2\x1e.aether.v1.TargetOfflinePolicyR\x13targetOfflinePolicy\x12K\n" +
+ "\"required_downstream_authority_hops\x18\x16 \x01(\rR\x1frequiredDownstreamAuthorityHops\x126\n" +
+ "\x17originating_schedule_id\x18\x17 \x01(\tR\x15originatingScheduleId\x1aG\n" +
"\x19LaunchParamOverridesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a;\n" +
@@ -18599,7 +20014,7 @@ const file_aether_proto_rawDesc = "" +
"assignedTo\x12\x1d\n" +
"\n" +
"task_token\x18\b \x01(\tR\ttaskToken\x12,\n" +
- "\x12authority_grant_id\x18\t \x01(\tR\x10authorityGrantId\"\xca\x05\n" +
+ "\x12authority_grant_id\x18\t \x01(\tR\x10authorityGrantId\"\x91\x06\n" +
"\x0eTaskAssignment\x12\x17\n" +
"\atask_id\x18\x01 \x01(\tR\x06taskId\x12\x1b\n" +
"\ttask_type\x18\x02 \x01(\tR\btaskType\x12\x1f\n" +
@@ -18618,7 +20033,8 @@ const file_aether_proto_rawDesc = "" +
"\n" +
"task_class\x18\f \x01(\x0e2\x14.aether.v1.TaskClassR\ttaskClass\x12%\n" +
"\x0echeckpoint_key\x18\r \x01(\tR\rcheckpointKey\x12*\n" +
- "\x11resume_session_id\x18\x0e \x01(\tR\x0fresumeSessionId\x1a;\n" +
+ "\x11resume_session_id\x18\x0e \x01(\tR\x0fresumeSessionId\x12E\n" +
+ "\rauthorization\x18\x0f \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a?\n" +
@@ -19405,7 +20821,7 @@ const file_aether_proto_rawDesc = "" +
"\x05roles\x18\x13 \x03(\v2\x16.aether.v1.ACLRoleInfoR\x05roles\x12B\n" +
"\rgroup_members\x18\x14 \x03(\v2\x1d.aether.v1.ACLGroupMemberInfoR\fgroupMembers\x12K\n" +
"\x10role_assignments\x18\x15 \x03(\v2 .aether.v1.ACLRoleAssignmentInfoR\x0froleAssignments\x12E\n" +
- "\vexplanation\x18\x16 \x01(\v2#.aether.v1.ACLAccessExplanationInfoR\vexplanationJ\x04\b\a\x10\b\"\xb6\x06\n" +
+ "\vexplanation\x18\x16 \x01(\v2#.aether.v1.ACLAccessExplanationInfoR\vexplanationJ\x04\b\a\x10\b\"\xe8\x06\n" +
"\x17AuthorityGrantOperation\x129\n" +
"\x02op\x18\x01 \x01(\x0e2).aether.v1.AuthorityGrantOperation.OpTypeR\x02op\x12\x19\n" +
"\bgrant_id\x18\x02 \x01(\tR\agrantId\x12S\n" +
@@ -19416,7 +20832,9 @@ const file_aether_proto_rawDesc = "" +
"request_id\x18\x06 \x01(\tR\trequestId\x12G\n" +
"\flist_request\x18\a \x01(\v2$.aether.v1.AuthorityGrantListRequestR\vlistRequest\x12c\n" +
"\x16batch_exchange_request\x18\b \x01(\v2-.aether.v1.AuthorityGrantBatchExchangeRequestR\x14batchExchangeRequest\x12j\n" +
- "\x19derive_for_target_request\x18\t \x01(\v2/.aether.v1.AuthorityGrantDeriveForTargetRequestR\x16deriveForTargetRequest\"\x98\x01\n" +
+ "\x19derive_for_target_request\x18\t \x01(\v2/.aether.v1.AuthorityGrantDeriveForTargetRequestR\x16deriveForTargetRequest\x120\n" +
+ "\x14workflow_schedule_id\x18\n" +
+ " \x01(\tR\x12workflowScheduleId\"\x98\x01\n" +
"\x06OpType\x12\f\n" +
"\bEXCHANGE\x10\x00\x12\n" +
"\n" +
@@ -19751,7 +21169,30 @@ const file_aether_proto_rawDesc = "" +
"\x04kind\x18\f \x01(\x0e2\x17.aether.v1.ProgressKindR\x04kind\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x98\x06\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xf2\x03\n" +
+ "\x1eWorkflowScheduleAuthorityScope\x12'\n" +
+ "\x0fworkspace_scope\x18\x01 \x03(\tR\x0eworkspaceScope\x12U\n" +
+ "\x0eresource_scope\x18\x02 \x03(\v2..aether.v1.ACLAuthorityGrantResourceScopeEntryR\rresourceScope\x12'\n" +
+ "\x0foperation_scope\x18\x03 \x03(\tR\x0eoperationScope\x12(\n" +
+ "\x10max_access_level\x18\x04 \x01(\x05R\x0emaxAccessLevel\x12\x1d\n" +
+ "\n" +
+ "expires_at\x18\x05 \x01(\x03R\texpiresAt\x12'\n" +
+ "\x0frenewable_until\x18\x06 \x01(\x03R\x0erenewableUntil\x12?\n" +
+ "\x1crequired_task_authority_hops\x18\a \x01(\rR\x19requiredTaskAuthorityHops\x12M\n" +
+ "\rlifetime_mode\x18\b \x01(\x0e2(.aether.v1.WorkflowAuthorityLifetimeModeR\flifetimeMode\x12%\n" +
+ "\x0epolicy_version\x18\t \x01(\rR\rpolicyVersion\"\x87\x04\n" +
+ "\x16WorkflowRequestContext\x12-\n" +
+ "\x05actor\x18\x01 \x01(\v2\x17.aether.v1.PrincipalRefR\x05actor\x121\n" +
+ "\asubject\x18\x02 \x01(\v2\x17.aether.v1.PrincipalRefR\asubject\x12(\n" +
+ "\x10actor_session_id\x18\x03 \x01(\tR\x0eactorSessionId\x12V\n" +
+ "\x16schedule_authorization\x18\x04 \x01(\v2\x1f.aether.v1.AuthorizationContextR\x15scheduleAuthorization\x12\"\n" +
+ "\rroot_grant_id\x18\x05 \x01(\tR\vrootGrantId\x12&\n" +
+ "\x0fsource_grant_id\x18\x06 \x01(\tR\rsourceGrantId\x12\"\n" +
+ "\rexpires_at_ms\x18\a \x01(\x03R\vexpiresAtMs\x12#\n" +
+ "\rpolicy_digest\x18\b \x01(\tR\fpolicyDigest\x12M\n" +
+ "\rlifetime_mode\x18\t \x01(\x0e2(.aether.v1.WorkflowAuthorityLifetimeModeR\flifetimeMode\x12%\n" +
+ "\x0epolicy_version\x18\n" +
+ " \x01(\rR\rpolicyVersion\"\x90\b\n" +
"\x11WorkflowOperation\x123\n" +
"\x02op\x18\x01 \x01(\x0e2#.aether.v1.WorkflowOperation.OpTypeR\x02op\x12\x0e\n" +
"\x02id\x18\x02 \x01(\tR\x02id\x12!\n" +
@@ -19760,7 +21201,11 @@ const file_aether_proto_rawDesc = "" +
"\x04data\x18\x05 \x01(\fR\x04data\x12\x1d\n" +
"\n" +
"request_id\x18\x06 \x01(\tR\trequestId\x12#\n" +
- "\rstatus_filter\x18\a \x01(\tR\fstatusFilter\"\xa4\x04\n" +
+ "\rstatus_filter\x18\a \x01(\tR\fstatusFilter\x12E\n" +
+ "\rauthorization\x18\b \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\x12c\n" +
+ "\x18schedule_authority_scope\x18\t \x01(\v2).aether.v1.WorkflowScheduleAuthorityScopeR\x16scheduleAuthorityScope\x12J\n" +
+ "\x0frequest_context\x18\n" +
+ " \x01(\v2!.aether.v1.WorkflowRequestContextR\x0erequestContext\"\xa4\x04\n" +
"\x06OpType\x12\x0e\n" +
"\n" +
"LIST_RULES\x10\x00\x12\f\n" +
@@ -19800,7 +21245,7 @@ const file_aether_proto_rawDesc = "" +
"\vtotal_count\x18\x05 \x01(\x05R\n" +
"totalCount\x12\x1d\n" +
"\n" +
- "request_id\x18\x06 \x01(\tR\trequestId\"\x87\x03\n" +
+ "request_id\x18\x06 \x01(\tR\trequestId\"\xac\x04\n" +
"\x0fMessageEnvelope\x12\x16\n" +
"\x06source\x18\x01 \x01(\tR\x06source\x12\x18\n" +
"\apayload\x18\x02 \x01(\fR\apayload\x129\n" +
@@ -19808,7 +21253,9 @@ const file_aether_proto_rawDesc = "" +
"\ftimestamp_ms\x18\x04 \x01(\x03R\vtimestampMs\x12D\n" +
"\bmetadata\x18\x05 \x03(\v2(.aether.v1.MessageEnvelope.MetadataEntryR\bmetadata\x12\x1c\n" +
"\tworkspace\x18\x06 \x01(\tR\tworkspace\x12C\n" +
- "\x11on_behalf_subject\x18\a \x01(\v2\x17.aether.v1.PrincipalRefR\x0fonBehalfSubject\x1a;\n" +
+ "\x11on_behalf_subject\x18\a \x01(\v2\x17.aether.v1.PrincipalRefR\x0fonBehalfSubject\x12G\n" +
+ "\x0eaccess_receipt\x18\b \x01(\v2 .aether.v1.AccessDecisionReceiptR\raccessReceipt\x12Z\n" +
+ "\x17forwarded_authorization\x18\t \x01(\v2!.aether.v1.ForwardedAuthorizationR\x16forwardedAuthorization\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x86\x06\n" +
@@ -19902,7 +21349,7 @@ const file_aether_proto_rawDesc = "" +
"\asuccess\x18\x02 \x01(\bR\asuccess\x12\x1d\n" +
"\n" +
"error_code\x18\x03 \x01(\tR\terrorCode\x12#\n" +
- "\rerror_message\x18\x04 \x01(\tR\ferrorMessage\"\xea\x05\n" +
+ "\rerror_message\x18\x04 \x01(\tR\ferrorMessage\"\xfc\x06\n" +
"\x10ProxyHttpRequest\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12!\n" +
@@ -19922,7 +21369,9 @@ const file_aether_proto_rawDesc = "" +
"\x1cstream_response_indefinitely\x18\r \x01(\bR\x1astreamResponseIndefinitely\x123\n" +
"\x16stream_idle_timeout_ms\x18\x0e \x01(\x03R\x13streamIdleTimeoutMs\x125\n" +
"\x17max_response_body_bytes\x18\x0f \x01(\x03R\x14maxResponseBodyBytes\x12*\n" +
- "\x11proxy_chain_depth\x18\x10 \x01(\rR\x0fproxyChainDepth\x1a:\n" +
+ "\x11proxy_chain_depth\x18\x10 \x01(\rR\x0fproxyChainDepth\x12G\n" +
+ "\x0echecked_access\x18\x11 \x01(\v2 .aether.v1.ResourceAccessRequestR\rcheckedAccess\x12G\n" +
+ "\x0eaccess_receipt\x18\x12 \x01(\v2 .aether.v1.AccessDecisionReceiptR\raccessReceipt\x1a:\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb8\x02\n" +
@@ -20098,7 +21547,56 @@ const file_aether_proto_rawDesc = "" +
"\fchild_status\x18\x02 \x01(\x0e2\x15.aether.v1.TaskStatusR\vchildStatus\x12\x1c\n" +
"\tlifecycle\x18\x03 \x01(\tR\tlifecycle\"X\n" +
"\x1eTaskAuthorityRequestEventRelay\x126\n" +
- "\x05event\x18\x01 \x01(\v2 .aether.v1.AuthorityRequestEventR\x05event*t\n" +
+ "\x05event\x18\x01 \x01(\v2 .aether.v1.AuthorityRequestEventR\x05event\"\xf4\x01\n" +
+ "\x15ResourceAccessRequest\x12#\n" +
+ "\rresource_type\x18\x01 \x01(\tR\fresourceType\x12\x1f\n" +
+ "\vresource_id\x18\x02 \x01(\tR\n" +
+ "resourceId\x12\x1c\n" +
+ "\toperation\x18\x03 \x01(\tR\toperation\x12\x1c\n" +
+ "\tworkspace\x18\x04 \x01(\tR\tworkspace\x122\n" +
+ "\x15required_access_level\x18\x05 \x01(\x05R\x13requiredAccessLevel\x12%\n" +
+ "\x0ecorrelation_id\x18\x06 \x01(\tR\rcorrelationId\"\xfa\x04\n" +
+ "\x15AccessDecisionReceipt\x12\x1f\n" +
+ "\vdecision_id\x18\x01 \x01(\tR\n" +
+ "decisionId\x12:\n" +
+ "\arequest\x18\x02 \x01(\v2 .aether.v1.ResourceAccessRequestR\arequest\x12\x18\n" +
+ "\aallowed\x18\x03 \x01(\bR\aallowed\x12\x1a\n" +
+ "\bdecision\x18\x04 \x01(\tR\bdecision\x124\n" +
+ "\x16effective_access_level\x18\x05 \x01(\x05R\x14effectiveAccessLevel\x12-\n" +
+ "\x05actor\x18\x06 \x01(\v2\x17.aether.v1.PrincipalRefR\x05actor\x121\n" +
+ "\asubject\x18\a \x01(\v2\x17.aether.v1.PrincipalRefR\asubject\x12:\n" +
+ "\froot_subject\x18\b \x01(\v2\x17.aether.v1.PrincipalRefR\vrootSubject\x12%\n" +
+ "\x0eauthority_mode\x18\t \x01(\tR\rauthorityMode\x12\x19\n" +
+ "\bgrant_id\x18\n" +
+ " \x01(\tR\agrantId\x12\"\n" +
+ "\rroot_grant_id\x18\v \x01(\tR\vrootGrantId\x12&\n" +
+ "\x0fevaluated_at_ms\x18\f \x01(\x03R\revaluatedAtMs\x12\"\n" +
+ "\rexpires_at_ms\x18\r \x01(\x03R\vexpiresAtMs\x12\x1f\n" +
+ "\vdenial_code\x18\x0e \x01(\tR\n" +
+ "denialCode\x12'\n" +
+ "\x0fdelivery_target\x18\x0f \x01(\tR\x0edeliveryTarget\"\xb6\x01\n" +
+ "\x14AccessCheckOperation\x12\x1d\n" +
+ "\n" +
+ "request_id\x18\x01 \x01(\tR\trequestId\x128\n" +
+ "\x06access\x18\x02 \x01(\v2 .aether.v1.ResourceAccessRequestR\x06access\x12E\n" +
+ "\rauthorization\x18\x03 \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\"\xa2\x01\n" +
+ "\x13AccessCheckResponse\x12\x1d\n" +
+ "\n" +
+ "request_id\x18\x01 \x01(\tR\trequestId\x12\x18\n" +
+ "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" +
+ "\x05error\x18\x03 \x01(\tR\x05error\x12<\n" +
+ "\bdecision\x18\x04 \x01(\v2 .aether.v1.AccessDecisionReceiptR\bdecision\"\xbb\x01\n" +
+ "\x19BatchAccessCheckOperation\x12\x1d\n" +
+ "\n" +
+ "request_id\x18\x01 \x01(\tR\trequestId\x128\n" +
+ "\x06access\x18\x02 \x03(\v2 .aether.v1.ResourceAccessRequestR\x06access\x12E\n" +
+ "\rauthorization\x18\x03 \x01(\v2\x1f.aether.v1.AuthorizationContextR\rauthorization\"\xa9\x01\n" +
+ "\x18BatchAccessCheckResponse\x12\x1d\n" +
+ "\n" +
+ "request_id\x18\x01 \x01(\tR\trequestId\x12\x18\n" +
+ "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x14\n" +
+ "\x05error\x18\x03 \x01(\tR\x05error\x12>\n" +
+ "\tdecisions\x18\x04 \x03(\v2 .aether.v1.AccessDecisionReceiptR\tdecisions*t\n" +
"\vMessageType\x12\x1c\n" +
"\x18MESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\b\n" +
"\x04CHAT\x10\x01\x12\v\n" +
@@ -20171,7 +21669,12 @@ const file_aether_proto_rawDesc = "" +
"\x1cBACKOFF_STRATEGY_UNSPECIFIED\x10\x00\x12\x1a\n" +
"\x16BACKOFF_STRATEGY_FIXED\x10\x01\x12 \n" +
"\x1cBACKOFF_STRATEGY_EXPONENTIAL\x10\x02\x12&\n" +
- "\"BACKOFF_STRATEGY_EXPLICIT_SCHEDULE\x10\x03*\x94\x01\n" +
+ "\"BACKOFF_STRATEGY_EXPLICIT_SCHEDULE\x10\x03*\xa6\x01\n" +
+ "\x13TargetOfflinePolicy\x12%\n" +
+ "!TARGET_OFFLINE_POLICY_UNSPECIFIED\x10\x00\x12%\n" +
+ "!TARGET_OFFLINE_POLICY_ORCHESTRATE\x10\x01\x12\x1f\n" +
+ "\x1bTARGET_OFFLINE_POLICY_QUEUE\x10\x02\x12 \n" +
+ "\x1cTARGET_OFFLINE_POLICY_REJECT\x10\x03*\x94\x01\n" +
"\n" +
"WaitReason\x12\x1b\n" +
"\x17WAIT_REASON_UNSPECIFIED\x10\x00\x12\x15\n" +
@@ -20190,7 +21693,10 @@ const file_aether_proto_rawDesc = "" +
"\x19PROGRESS_KIND_UNSPECIFIED\x10\x00\x12\x16\n" +
"\x12PROGRESS_KIND_CHAT\x10\x01\x12\x15\n" +
"\x11PROGRESS_KIND_APP\x10\x02\x12\x16\n" +
- "\x12PROGRESS_KIND_TASK\x10\x032X\n" +
+ "\x12PROGRESS_KIND_TASK\x10\x03*v\n" +
+ "\x1dWorkflowAuthorityLifetimeMode\x12,\n" +
+ "(WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND\x10\x00\x12'\n" +
+ "#WORKFLOW_AUTHORITY_LIFETIME_DURABLE\x10\x012X\n" +
"\rAetherGateway\x12G\n" +
"\aConnect\x12\x1a.aether.v1.UpstreamMessage\x1a\x1c.aether.v1.DownstreamMessage(\x010\x01B-Z+github.com/scitrera/aether/api/proto;aetherb\x06proto3"
@@ -20206,8 +21712,8 @@ func file_aether_proto_rawDescGZIP() []byte {
return file_aether_proto_rawDescData
}
-var file_aether_proto_enumTypes = make([]protoimpl.EnumInfo, 34)
-var file_aether_proto_msgTypes = make([]protoimpl.MessageInfo, 188)
+var file_aether_proto_enumTypes = make([]protoimpl.EnumInfo, 37)
+var file_aether_proto_msgTypes = make([]protoimpl.MessageInfo, 199)
var file_aether_proto_goTypes = []any{
(MessageType)(0), // 0: aether.v1.MessageType
(PrincipalType)(0), // 1: aether.v1.PrincipalType
@@ -20219,542 +21725,594 @@ var file_aether_proto_goTypes = []any{
(TaskClass)(0), // 7: aether.v1.TaskClass
(TaskPriority)(0), // 8: aether.v1.TaskPriority
(BackoffStrategy)(0), // 9: aether.v1.BackoffStrategy
- (WaitReason)(0), // 10: aether.v1.WaitReason
- (AuthorityRequestStatus)(0), // 11: aether.v1.AuthorityRequestStatus
- (ProgressKind)(0), // 12: aether.v1.ProgressKind
- (KVOperation_OpType)(0), // 13: aether.v1.KVOperation.OpType
- (KVOperation_Scope)(0), // 14: aether.v1.KVOperation.Scope
- (Signal_SignalType)(0), // 15: aether.v1.Signal.SignalType
- (CheckpointOperation_OpType)(0), // 16: aether.v1.CheckpointOperation.OpType
- (AdminQuery_OpType)(0), // 17: aether.v1.AdminQuery.OpType
- (SessionOperation_OpType)(0), // 18: aether.v1.SessionOperation.OpType
- (TaskQuery_OpType)(0), // 19: aether.v1.TaskQuery.OpType
- (TaskOperation_OpType)(0), // 20: aether.v1.TaskOperation.OpType
- (WorkspaceOperation_OpType)(0), // 21: aether.v1.WorkspaceOperation.OpType
- (AgentOperation_OpType)(0), // 22: aether.v1.AgentOperation.OpType
- (ACLOperation_OpType)(0), // 23: aether.v1.ACLOperation.OpType
- (AuthorityGrantOperation_OpType)(0), // 24: aether.v1.AuthorityGrantOperation.OpType
- (ResolveAuthorityRequestPayload_Decision)(0), // 25: aether.v1.ResolveAuthorityRequestPayload.Decision
- (AuthorityRequestOperation_OpType)(0), // 26: aether.v1.AuthorityRequestOperation.OpType
- (AuthorityRequestEvent_EventType)(0), // 27: aether.v1.AuthorityRequestEvent.EventType
- (TokenOperation_OpType)(0), // 28: aether.v1.TokenOperation.OpType
- (WorkflowOperation_OpType)(0), // 29: aether.v1.WorkflowOperation.OpType
- (ProxyError_Kind)(0), // 30: aether.v1.ProxyError.Kind
- (TunnelOpen_Protocol)(0), // 31: aether.v1.TunnelOpen.Protocol
- (TunnelClose_Reason)(0), // 32: aether.v1.TunnelClose.Reason
- (TaskSubscriptionOperation_OpType)(0), // 33: aether.v1.TaskSubscriptionOperation.OpType
- (*UpstreamMessage)(nil), // 34: aether.v1.UpstreamMessage
- (*DownstreamMessage)(nil), // 35: aether.v1.DownstreamMessage
- (*TaskHibernated)(nil), // 36: aether.v1.TaskHibernated
- (*ConnectionAck)(nil), // 37: aether.v1.ConnectionAck
- (*InitConnection)(nil), // 38: aether.v1.InitConnection
- (*BuildInfo)(nil), // 39: aether.v1.BuildInfo
- (*ExtensionDeclaration)(nil), // 40: aether.v1.ExtensionDeclaration
- (*NegotiatedExtension)(nil), // 41: aether.v1.NegotiatedExtension
- (*WorkflowEngineIdentity)(nil), // 42: aether.v1.WorkflowEngineIdentity
- (*MetricsBridgeIdentity)(nil), // 43: aether.v1.MetricsBridgeIdentity
- (*OrchestratorIdentity)(nil), // 44: aether.v1.OrchestratorIdentity
- (*BridgeIdentity)(nil), // 45: aether.v1.BridgeIdentity
- (*ServiceIdentity)(nil), // 46: aether.v1.ServiceIdentity
- (*AgentIdentity)(nil), // 47: aether.v1.AgentIdentity
- (*TaskIdentity)(nil), // 48: aether.v1.TaskIdentity
- (*UserIdentity)(nil), // 49: aether.v1.UserIdentity
- (*PrincipalRef)(nil), // 50: aether.v1.PrincipalRef
- (*AuthorizationContext)(nil), // 51: aether.v1.AuthorizationContext
- (*ResolvedAuthorityInfo)(nil), // 52: aether.v1.ResolvedAuthorityInfo
- (*SendMessage)(nil), // 53: aether.v1.SendMessage
- (*Metric)(nil), // 54: aether.v1.Metric
- (*MetricEntry)(nil), // 55: aether.v1.MetricEntry
- (*SwitchWorkspace)(nil), // 56: aether.v1.SwitchWorkspace
- (*KVOperation)(nil), // 57: aether.v1.KVOperation
- (*KVResponse)(nil), // 58: aether.v1.KVResponse
- (*IncomingMessage)(nil), // 59: aether.v1.IncomingMessage
- (*ConfigSnapshot)(nil), // 60: aether.v1.ConfigSnapshot
- (*Signal)(nil), // 61: aether.v1.Signal
- (*ErrorResponse)(nil), // 62: aether.v1.ErrorResponse
- (*RetryPolicy)(nil), // 63: aether.v1.RetryPolicy
- (*TaskCompletionEvent)(nil), // 64: aether.v1.TaskCompletionEvent
- (*CreateTaskRequest)(nil), // 65: aether.v1.CreateTaskRequest
- (*CreateTaskResponse)(nil), // 66: aether.v1.CreateTaskResponse
- (*TaskAssignment)(nil), // 67: aether.v1.TaskAssignment
- (*CheckpointOperation)(nil), // 68: aether.v1.CheckpointOperation
- (*CheckpointResponse)(nil), // 69: aether.v1.CheckpointResponse
- (*AdminQuery)(nil), // 70: aether.v1.AdminQuery
- (*ConnectionFilter)(nil), // 71: aether.v1.ConnectionFilter
- (*ConnectionInfo)(nil), // 72: aether.v1.ConnectionInfo
- (*AdminResponse)(nil), // 73: aether.v1.AdminResponse
- (*HealthInfo)(nil), // 74: aether.v1.HealthInfo
- (*HealthCheck)(nil), // 75: aether.v1.HealthCheck
- (*GatewayInfo)(nil), // 76: aether.v1.GatewayInfo
- (*GatewayStats)(nil), // 77: aether.v1.GatewayStats
- (*SessionOperation)(nil), // 78: aether.v1.SessionOperation
- (*SessionOperationResponse)(nil), // 79: aether.v1.SessionOperationResponse
- (*TaskQuery)(nil), // 80: aether.v1.TaskQuery
- (*TaskFilter)(nil), // 81: aether.v1.TaskFilter
- (*TaskInfo)(nil), // 82: aether.v1.TaskInfo
- (*TaskQueryResponse)(nil), // 83: aether.v1.TaskQueryResponse
- (*TaskOperation)(nil), // 84: aether.v1.TaskOperation
- (*WaitSpec)(nil), // 85: aether.v1.WaitSpec
- (*HibernationDescriptor)(nil), // 86: aether.v1.HibernationDescriptor
- (*TaskOperationResponse)(nil), // 87: aether.v1.TaskOperationResponse
- (*WorkspaceOperation)(nil), // 88: aether.v1.WorkspaceOperation
- (*WorkspaceFilter)(nil), // 89: aether.v1.WorkspaceFilter
- (*WorkspaceInfo)(nil), // 90: aether.v1.WorkspaceInfo
- (*WorkspaceResponse)(nil), // 91: aether.v1.WorkspaceResponse
- (*MessageFlowInfo)(nil), // 92: aether.v1.MessageFlowInfo
- (*FlowNode)(nil), // 93: aether.v1.FlowNode
- (*FlowEdge)(nil), // 94: aether.v1.FlowEdge
- (*AgentOperation)(nil), // 95: aether.v1.AgentOperation
- (*AgentFilter)(nil), // 96: aether.v1.AgentFilter
- (*AgentRegistrationInfo)(nil), // 97: aether.v1.AgentRegistrationInfo
- (*AgentResourceSchemaEntry)(nil), // 98: aether.v1.AgentResourceSchemaEntry
- (*AgentLaunchParams)(nil), // 99: aether.v1.AgentLaunchParams
- (*OrchestratorInfo)(nil), // 100: aether.v1.OrchestratorInfo
- (*AgentLaunchResult)(nil), // 101: aether.v1.AgentLaunchResult
- (*AgentResponse)(nil), // 102: aether.v1.AgentResponse
- (*ACLOperation)(nil), // 103: aether.v1.ACLOperation
- (*ACLRuleFilter)(nil), // 104: aether.v1.ACLRuleFilter
- (*ACLAuditFilter)(nil), // 105: aether.v1.ACLAuditFilter
- (*ACLGrantRequest)(nil), // 106: aether.v1.ACLGrantRequest
- (*ACLSetFallbackRequest)(nil), // 107: aether.v1.ACLSetFallbackRequest
- (*ACLAuthorityGrantFilter)(nil), // 108: aether.v1.ACLAuthorityGrantFilter
- (*ACLAuthorityGrantResourceScopeEntry)(nil), // 109: aether.v1.ACLAuthorityGrantResourceScopeEntry
- (*ACLAuthorityGrantRequest)(nil), // 110: aether.v1.ACLAuthorityGrantRequest
- (*ACLRenewAuthorityGrantRequest)(nil), // 111: aether.v1.ACLRenewAuthorityGrantRequest
- (*ACLRuleInfo)(nil), // 112: aether.v1.ACLRuleInfo
- (*ACLFallbackPolicyInfo)(nil), // 113: aether.v1.ACLFallbackPolicyInfo
- (*ACLAuditEntryInfo)(nil), // 114: aether.v1.ACLAuditEntryInfo
- (*ACLAuthorityGrantInfo)(nil), // 115: aether.v1.ACLAuthorityGrantInfo
- (*ACLCleanupResult)(nil), // 116: aether.v1.ACLCleanupResult
- (*ACLGroupRequest)(nil), // 117: aether.v1.ACLGroupRequest
- (*ACLRoleRequest)(nil), // 118: aether.v1.ACLRoleRequest
- (*ACLGroupMemberRequest)(nil), // 119: aether.v1.ACLGroupMemberRequest
- (*ACLRoleAssignmentRequest)(nil), // 120: aether.v1.ACLRoleAssignmentRequest
- (*ACLGroupInfo)(nil), // 121: aether.v1.ACLGroupInfo
- (*ACLRoleInfo)(nil), // 122: aether.v1.ACLRoleInfo
- (*ACLGroupMemberInfo)(nil), // 123: aether.v1.ACLGroupMemberInfo
- (*ACLRoleAssignmentInfo)(nil), // 124: aether.v1.ACLRoleAssignmentInfo
- (*ACLAccessContributionInfo)(nil), // 125: aether.v1.ACLAccessContributionInfo
- (*ACLAccessExplanationInfo)(nil), // 126: aether.v1.ACLAccessExplanationInfo
- (*ACLResponse)(nil), // 127: aether.v1.ACLResponse
- (*AuthorityGrantOperation)(nil), // 128: aether.v1.AuthorityGrantOperation
- (*AuthorityGrantExchangeRequest)(nil), // 129: aether.v1.AuthorityGrantExchangeRequest
- (*AuthorityGrantDeriveRequest)(nil), // 130: aether.v1.AuthorityGrantDeriveRequest
- (*AuthorityGrantResponse)(nil), // 131: aether.v1.AuthorityGrantResponse
- (*AuthorityGrantListRequest)(nil), // 132: aether.v1.AuthorityGrantListRequest
- (*AuthorityGrantBatchExchangeRequest)(nil), // 133: aether.v1.AuthorityGrantBatchExchangeRequest
- (*AuthorityGrantDeriveForTargetRequest)(nil), // 134: aether.v1.AuthorityGrantDeriveForTargetRequest
- (*AuthorityIdentity)(nil), // 135: aether.v1.AuthorityIdentity
- (*AuthoritySpan)(nil), // 136: aether.v1.AuthoritySpan
- (*AuthorityGrantRevocation)(nil), // 137: aether.v1.AuthorityGrantRevocation
- (*AuthorityRequestRoutingTarget)(nil), // 138: aether.v1.AuthorityRequestRoutingTarget
- (*AuthorityRequestResourceScopeEntry)(nil), // 139: aether.v1.AuthorityRequestResourceScopeEntry
- (*AuthorityRequest)(nil), // 140: aether.v1.AuthorityRequest
- (*CreateAuthorityRequestPayload)(nil), // 141: aether.v1.CreateAuthorityRequestPayload
- (*ResolveAuthorityRequestPayload)(nil), // 142: aether.v1.ResolveAuthorityRequestPayload
- (*AuthorityRequestListFilter)(nil), // 143: aether.v1.AuthorityRequestListFilter
- (*AuthorityRequestOperation)(nil), // 144: aether.v1.AuthorityRequestOperation
- (*AuthorityRequestOperationResponse)(nil), // 145: aether.v1.AuthorityRequestOperationResponse
- (*AuthorityRequestEvent)(nil), // 146: aether.v1.AuthorityRequestEvent
- (*TokenOperation)(nil), // 147: aether.v1.TokenOperation
- (*TokenCreateRequest)(nil), // 148: aether.v1.TokenCreateRequest
- (*TokenFilter)(nil), // 149: aether.v1.TokenFilter
- (*TokenInfo)(nil), // 150: aether.v1.TokenInfo
- (*TokenResponse)(nil), // 151: aether.v1.TokenResponse
- (*ProgressReport)(nil), // 152: aether.v1.ProgressReport
- (*ProgressStep)(nil), // 153: aether.v1.ProgressStep
- (*ProgressUpdate)(nil), // 154: aether.v1.ProgressUpdate
- (*WorkflowOperation)(nil), // 155: aether.v1.WorkflowOperation
- (*WorkflowResponse)(nil), // 156: aether.v1.WorkflowResponse
- (*MessageEnvelope)(nil), // 157: aether.v1.MessageEnvelope
- (*AuditQuery)(nil), // 158: aether.v1.AuditQuery
- (*AuditQueryResponse)(nil), // 159: aether.v1.AuditQueryResponse
- (*AuditEntry)(nil), // 160: aether.v1.AuditEntry
- (*SubmitAuditEventRequest)(nil), // 161: aether.v1.SubmitAuditEventRequest
- (*SubmitAuditEventResponse)(nil), // 162: aether.v1.SubmitAuditEventResponse
- (*ProxyHttpRequest)(nil), // 163: aether.v1.ProxyHttpRequest
- (*ProxyHttpResponse)(nil), // 164: aether.v1.ProxyHttpResponse
- (*ProxyHttpBodyChunk)(nil), // 165: aether.v1.ProxyHttpBodyChunk
- (*ProxyError)(nil), // 166: aether.v1.ProxyError
- (*TunnelOpen)(nil), // 167: aether.v1.TunnelOpen
- (*TunnelData)(nil), // 168: aether.v1.TunnelData
- (*TunnelClose)(nil), // 169: aether.v1.TunnelClose
- (*TunnelAck)(nil), // 170: aether.v1.TunnelAck
- (*ResolveAuthorityRequest)(nil), // 171: aether.v1.ResolveAuthorityRequest
- (*ResolveAuthorityResponse)(nil), // 172: aether.v1.ResolveAuthorityResponse
- (*ResolvedAuthority)(nil), // 173: aether.v1.ResolvedAuthority
- (*AuthorityGrantInfo)(nil), // 174: aether.v1.AuthorityGrantInfo
- (*ConnectionStatusRequest)(nil), // 175: aether.v1.ConnectionStatusRequest
- (*ConnectionStatusResponse)(nil), // 176: aether.v1.ConnectionStatusResponse
- (*TaskSubscriptionOperation)(nil), // 177: aether.v1.TaskSubscriptionOperation
- (*TaskSubscriptionOperationResponse)(nil), // 178: aether.v1.TaskSubscriptionOperationResponse
- (*TaskEvent)(nil), // 179: aether.v1.TaskEvent
- (*TaskStatusChangedEvent)(nil), // 180: aether.v1.TaskStatusChangedEvent
- (*TaskProgressEvent)(nil), // 181: aether.v1.TaskProgressEvent
- (*TaskChildLifecycleEvent)(nil), // 182: aether.v1.TaskChildLifecycleEvent
- (*TaskAuthorityRequestEventRelay)(nil), // 183: aether.v1.TaskAuthorityRequestEventRelay
- nil, // 184: aether.v1.InitConnection.CredentialsEntry
- nil, // 185: aether.v1.Metric.MetadataEntry
- nil, // 186: aether.v1.KVResponse.KvMapEntry
- nil, // 187: aether.v1.ConfigSnapshot.KvEntry
- nil, // 188: aether.v1.ConfigSnapshot.GlobalKvEntry
- nil, // 189: aether.v1.ConfigSnapshot.TaskContextEntry
- nil, // 190: aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry
- nil, // 191: aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry
- nil, // 192: aether.v1.CreateTaskRequest.LaunchParamOverridesEntry
- nil, // 193: aether.v1.CreateTaskRequest.MetadataEntry
- nil, // 194: aether.v1.TaskAssignment.MetadataEntry
- nil, // 195: aether.v1.TaskAssignment.LaunchParamsEntry
- nil, // 196: aether.v1.HealthInfo.ChecksEntry
- nil, // 197: aether.v1.TaskInfo.MetadataEntry
- nil, // 198: aether.v1.WaitSpec.InputMatchEntry
- nil, // 199: aether.v1.WorkspaceInfo.MetadataEntry
- nil, // 200: aether.v1.AgentRegistrationInfo.LaunchParamsEntry
- nil, // 201: aether.v1.AgentRegistrationInfo.CapabilitiesEntry
- nil, // 202: aether.v1.AgentLaunchParams.ParamOverridesEntry
- nil, // 203: aether.v1.ACLAuthorityGrantRequest.MetadataEntry
- nil, // 204: aether.v1.ACLAuditEntryInfo.MetadataEntry
- nil, // 205: aether.v1.ACLAuthorityGrantInfo.MetadataEntry
- nil, // 206: aether.v1.ACLGroupRequest.MetadataEntry
- nil, // 207: aether.v1.ACLRoleRequest.MetadataEntry
- nil, // 208: aether.v1.ACLGroupInfo.MetadataEntry
- nil, // 209: aether.v1.ACLRoleInfo.MetadataEntry
- nil, // 210: aether.v1.AuthorityGrantExchangeRequest.MetadataEntry
- nil, // 211: aether.v1.AuthorityGrantDeriveRequest.MetadataEntry
- nil, // 212: aether.v1.AuthorityRequest.MetadataEntry
- nil, // 213: aether.v1.CreateAuthorityRequestPayload.MetadataEntry
- nil, // 214: aether.v1.ProgressReport.MetadataEntry
- nil, // 215: aether.v1.ProgressUpdate.MetadataEntry
- nil, // 216: aether.v1.MessageEnvelope.MetadataEntry
- nil, // 217: aether.v1.SubmitAuditEventRequest.MetadataEntry
- nil, // 218: aether.v1.ProxyHttpRequest.HeadersEntry
- nil, // 219: aether.v1.ProxyHttpResponse.HeadersEntry
- nil, // 220: aether.v1.TunnelOpen.MetadataEntry
- nil, // 221: aether.v1.TaskProgressEvent.MetadataEntry
+ (TargetOfflinePolicy)(0), // 10: aether.v1.TargetOfflinePolicy
+ (WaitReason)(0), // 11: aether.v1.WaitReason
+ (AuthorityRequestStatus)(0), // 12: aether.v1.AuthorityRequestStatus
+ (ProgressKind)(0), // 13: aether.v1.ProgressKind
+ (WorkflowAuthorityLifetimeMode)(0), // 14: aether.v1.WorkflowAuthorityLifetimeMode
+ (AuthorityContinuationRequest_ScopeMode)(0), // 15: aether.v1.AuthorityContinuationRequest.ScopeMode
+ (KVOperation_OpType)(0), // 16: aether.v1.KVOperation.OpType
+ (KVOperation_Scope)(0), // 17: aether.v1.KVOperation.Scope
+ (Signal_SignalType)(0), // 18: aether.v1.Signal.SignalType
+ (CheckpointOperation_OpType)(0), // 19: aether.v1.CheckpointOperation.OpType
+ (AdminQuery_OpType)(0), // 20: aether.v1.AdminQuery.OpType
+ (SessionOperation_OpType)(0), // 21: aether.v1.SessionOperation.OpType
+ (TaskQuery_OpType)(0), // 22: aether.v1.TaskQuery.OpType
+ (TaskOperation_OpType)(0), // 23: aether.v1.TaskOperation.OpType
+ (WorkspaceOperation_OpType)(0), // 24: aether.v1.WorkspaceOperation.OpType
+ (AgentOperation_OpType)(0), // 25: aether.v1.AgentOperation.OpType
+ (ACLOperation_OpType)(0), // 26: aether.v1.ACLOperation.OpType
+ (AuthorityGrantOperation_OpType)(0), // 27: aether.v1.AuthorityGrantOperation.OpType
+ (ResolveAuthorityRequestPayload_Decision)(0), // 28: aether.v1.ResolveAuthorityRequestPayload.Decision
+ (AuthorityRequestOperation_OpType)(0), // 29: aether.v1.AuthorityRequestOperation.OpType
+ (AuthorityRequestEvent_EventType)(0), // 30: aether.v1.AuthorityRequestEvent.EventType
+ (TokenOperation_OpType)(0), // 31: aether.v1.TokenOperation.OpType
+ (WorkflowOperation_OpType)(0), // 32: aether.v1.WorkflowOperation.OpType
+ (ProxyError_Kind)(0), // 33: aether.v1.ProxyError.Kind
+ (TunnelOpen_Protocol)(0), // 34: aether.v1.TunnelOpen.Protocol
+ (TunnelClose_Reason)(0), // 35: aether.v1.TunnelClose.Reason
+ (TaskSubscriptionOperation_OpType)(0), // 36: aether.v1.TaskSubscriptionOperation.OpType
+ (*UpstreamMessage)(nil), // 37: aether.v1.UpstreamMessage
+ (*DownstreamMessage)(nil), // 38: aether.v1.DownstreamMessage
+ (*TaskHibernated)(nil), // 39: aether.v1.TaskHibernated
+ (*ConnectionAck)(nil), // 40: aether.v1.ConnectionAck
+ (*InitConnection)(nil), // 41: aether.v1.InitConnection
+ (*BuildInfo)(nil), // 42: aether.v1.BuildInfo
+ (*ExtensionDeclaration)(nil), // 43: aether.v1.ExtensionDeclaration
+ (*NegotiatedExtension)(nil), // 44: aether.v1.NegotiatedExtension
+ (*WorkflowEngineIdentity)(nil), // 45: aether.v1.WorkflowEngineIdentity
+ (*MetricsBridgeIdentity)(nil), // 46: aether.v1.MetricsBridgeIdentity
+ (*OrchestratorIdentity)(nil), // 47: aether.v1.OrchestratorIdentity
+ (*BridgeIdentity)(nil), // 48: aether.v1.BridgeIdentity
+ (*ServiceIdentity)(nil), // 49: aether.v1.ServiceIdentity
+ (*AgentIdentity)(nil), // 50: aether.v1.AgentIdentity
+ (*TaskIdentity)(nil), // 51: aether.v1.TaskIdentity
+ (*UserIdentity)(nil), // 52: aether.v1.UserIdentity
+ (*PrincipalRef)(nil), // 53: aether.v1.PrincipalRef
+ (*AuthorizationContext)(nil), // 54: aether.v1.AuthorizationContext
+ (*ResolvedAuthorityInfo)(nil), // 55: aether.v1.ResolvedAuthorityInfo
+ (*SendMessage)(nil), // 56: aether.v1.SendMessage
+ (*AuthorityContinuationScope)(nil), // 57: aether.v1.AuthorityContinuationScope
+ (*AuthorityContinuationRequest)(nil), // 58: aether.v1.AuthorityContinuationRequest
+ (*Metric)(nil), // 59: aether.v1.Metric
+ (*MetricEntry)(nil), // 60: aether.v1.MetricEntry
+ (*SwitchWorkspace)(nil), // 61: aether.v1.SwitchWorkspace
+ (*KVOperation)(nil), // 62: aether.v1.KVOperation
+ (*KVResponse)(nil), // 63: aether.v1.KVResponse
+ (*IncomingMessage)(nil), // 64: aether.v1.IncomingMessage
+ (*ForwardedAuthorization)(nil), // 65: aether.v1.ForwardedAuthorization
+ (*ConfigSnapshot)(nil), // 66: aether.v1.ConfigSnapshot
+ (*Signal)(nil), // 67: aether.v1.Signal
+ (*ErrorResponse)(nil), // 68: aether.v1.ErrorResponse
+ (*RetryPolicy)(nil), // 69: aether.v1.RetryPolicy
+ (*TaskCompletionEvent)(nil), // 70: aether.v1.TaskCompletionEvent
+ (*CreateTaskRequest)(nil), // 71: aether.v1.CreateTaskRequest
+ (*CreateTaskResponse)(nil), // 72: aether.v1.CreateTaskResponse
+ (*TaskAssignment)(nil), // 73: aether.v1.TaskAssignment
+ (*CheckpointOperation)(nil), // 74: aether.v1.CheckpointOperation
+ (*CheckpointResponse)(nil), // 75: aether.v1.CheckpointResponse
+ (*AdminQuery)(nil), // 76: aether.v1.AdminQuery
+ (*ConnectionFilter)(nil), // 77: aether.v1.ConnectionFilter
+ (*ConnectionInfo)(nil), // 78: aether.v1.ConnectionInfo
+ (*AdminResponse)(nil), // 79: aether.v1.AdminResponse
+ (*HealthInfo)(nil), // 80: aether.v1.HealthInfo
+ (*HealthCheck)(nil), // 81: aether.v1.HealthCheck
+ (*GatewayInfo)(nil), // 82: aether.v1.GatewayInfo
+ (*GatewayStats)(nil), // 83: aether.v1.GatewayStats
+ (*SessionOperation)(nil), // 84: aether.v1.SessionOperation
+ (*SessionOperationResponse)(nil), // 85: aether.v1.SessionOperationResponse
+ (*TaskQuery)(nil), // 86: aether.v1.TaskQuery
+ (*TaskFilter)(nil), // 87: aether.v1.TaskFilter
+ (*TaskInfo)(nil), // 88: aether.v1.TaskInfo
+ (*TaskQueryResponse)(nil), // 89: aether.v1.TaskQueryResponse
+ (*TaskOperation)(nil), // 90: aether.v1.TaskOperation
+ (*WaitSpec)(nil), // 91: aether.v1.WaitSpec
+ (*HibernationDescriptor)(nil), // 92: aether.v1.HibernationDescriptor
+ (*TaskOperationResponse)(nil), // 93: aether.v1.TaskOperationResponse
+ (*WorkspaceOperation)(nil), // 94: aether.v1.WorkspaceOperation
+ (*WorkspaceFilter)(nil), // 95: aether.v1.WorkspaceFilter
+ (*WorkspaceInfo)(nil), // 96: aether.v1.WorkspaceInfo
+ (*WorkspaceResponse)(nil), // 97: aether.v1.WorkspaceResponse
+ (*MessageFlowInfo)(nil), // 98: aether.v1.MessageFlowInfo
+ (*FlowNode)(nil), // 99: aether.v1.FlowNode
+ (*FlowEdge)(nil), // 100: aether.v1.FlowEdge
+ (*AgentOperation)(nil), // 101: aether.v1.AgentOperation
+ (*AgentFilter)(nil), // 102: aether.v1.AgentFilter
+ (*AgentRegistrationInfo)(nil), // 103: aether.v1.AgentRegistrationInfo
+ (*AgentResourceSchemaEntry)(nil), // 104: aether.v1.AgentResourceSchemaEntry
+ (*AgentLaunchParams)(nil), // 105: aether.v1.AgentLaunchParams
+ (*OrchestratorInfo)(nil), // 106: aether.v1.OrchestratorInfo
+ (*AgentLaunchResult)(nil), // 107: aether.v1.AgentLaunchResult
+ (*AgentResponse)(nil), // 108: aether.v1.AgentResponse
+ (*ACLOperation)(nil), // 109: aether.v1.ACLOperation
+ (*ACLRuleFilter)(nil), // 110: aether.v1.ACLRuleFilter
+ (*ACLAuditFilter)(nil), // 111: aether.v1.ACLAuditFilter
+ (*ACLGrantRequest)(nil), // 112: aether.v1.ACLGrantRequest
+ (*ACLSetFallbackRequest)(nil), // 113: aether.v1.ACLSetFallbackRequest
+ (*ACLAuthorityGrantFilter)(nil), // 114: aether.v1.ACLAuthorityGrantFilter
+ (*ACLAuthorityGrantResourceScopeEntry)(nil), // 115: aether.v1.ACLAuthorityGrantResourceScopeEntry
+ (*ACLAuthorityGrantRequest)(nil), // 116: aether.v1.ACLAuthorityGrantRequest
+ (*ACLRenewAuthorityGrantRequest)(nil), // 117: aether.v1.ACLRenewAuthorityGrantRequest
+ (*ACLRuleInfo)(nil), // 118: aether.v1.ACLRuleInfo
+ (*ACLFallbackPolicyInfo)(nil), // 119: aether.v1.ACLFallbackPolicyInfo
+ (*ACLAuditEntryInfo)(nil), // 120: aether.v1.ACLAuditEntryInfo
+ (*ACLAuthorityGrantInfo)(nil), // 121: aether.v1.ACLAuthorityGrantInfo
+ (*ACLCleanupResult)(nil), // 122: aether.v1.ACLCleanupResult
+ (*ACLGroupRequest)(nil), // 123: aether.v1.ACLGroupRequest
+ (*ACLRoleRequest)(nil), // 124: aether.v1.ACLRoleRequest
+ (*ACLGroupMemberRequest)(nil), // 125: aether.v1.ACLGroupMemberRequest
+ (*ACLRoleAssignmentRequest)(nil), // 126: aether.v1.ACLRoleAssignmentRequest
+ (*ACLGroupInfo)(nil), // 127: aether.v1.ACLGroupInfo
+ (*ACLRoleInfo)(nil), // 128: aether.v1.ACLRoleInfo
+ (*ACLGroupMemberInfo)(nil), // 129: aether.v1.ACLGroupMemberInfo
+ (*ACLRoleAssignmentInfo)(nil), // 130: aether.v1.ACLRoleAssignmentInfo
+ (*ACLAccessContributionInfo)(nil), // 131: aether.v1.ACLAccessContributionInfo
+ (*ACLAccessExplanationInfo)(nil), // 132: aether.v1.ACLAccessExplanationInfo
+ (*ACLResponse)(nil), // 133: aether.v1.ACLResponse
+ (*AuthorityGrantOperation)(nil), // 134: aether.v1.AuthorityGrantOperation
+ (*AuthorityGrantExchangeRequest)(nil), // 135: aether.v1.AuthorityGrantExchangeRequest
+ (*AuthorityGrantDeriveRequest)(nil), // 136: aether.v1.AuthorityGrantDeriveRequest
+ (*AuthorityGrantResponse)(nil), // 137: aether.v1.AuthorityGrantResponse
+ (*AuthorityGrantListRequest)(nil), // 138: aether.v1.AuthorityGrantListRequest
+ (*AuthorityGrantBatchExchangeRequest)(nil), // 139: aether.v1.AuthorityGrantBatchExchangeRequest
+ (*AuthorityGrantDeriveForTargetRequest)(nil), // 140: aether.v1.AuthorityGrantDeriveForTargetRequest
+ (*AuthorityIdentity)(nil), // 141: aether.v1.AuthorityIdentity
+ (*AuthoritySpan)(nil), // 142: aether.v1.AuthoritySpan
+ (*AuthorityGrantRevocation)(nil), // 143: aether.v1.AuthorityGrantRevocation
+ (*AuthorityRequestRoutingTarget)(nil), // 144: aether.v1.AuthorityRequestRoutingTarget
+ (*AuthorityRequestResourceScopeEntry)(nil), // 145: aether.v1.AuthorityRequestResourceScopeEntry
+ (*AuthorityRequest)(nil), // 146: aether.v1.AuthorityRequest
+ (*CreateAuthorityRequestPayload)(nil), // 147: aether.v1.CreateAuthorityRequestPayload
+ (*ResolveAuthorityRequestPayload)(nil), // 148: aether.v1.ResolveAuthorityRequestPayload
+ (*AuthorityRequestListFilter)(nil), // 149: aether.v1.AuthorityRequestListFilter
+ (*AuthorityRequestOperation)(nil), // 150: aether.v1.AuthorityRequestOperation
+ (*AuthorityRequestOperationResponse)(nil), // 151: aether.v1.AuthorityRequestOperationResponse
+ (*AuthorityRequestEvent)(nil), // 152: aether.v1.AuthorityRequestEvent
+ (*TokenOperation)(nil), // 153: aether.v1.TokenOperation
+ (*TokenCreateRequest)(nil), // 154: aether.v1.TokenCreateRequest
+ (*TokenFilter)(nil), // 155: aether.v1.TokenFilter
+ (*TokenInfo)(nil), // 156: aether.v1.TokenInfo
+ (*TokenResponse)(nil), // 157: aether.v1.TokenResponse
+ (*ProgressReport)(nil), // 158: aether.v1.ProgressReport
+ (*ProgressStep)(nil), // 159: aether.v1.ProgressStep
+ (*ProgressUpdate)(nil), // 160: aether.v1.ProgressUpdate
+ (*WorkflowScheduleAuthorityScope)(nil), // 161: aether.v1.WorkflowScheduleAuthorityScope
+ (*WorkflowRequestContext)(nil), // 162: aether.v1.WorkflowRequestContext
+ (*WorkflowOperation)(nil), // 163: aether.v1.WorkflowOperation
+ (*WorkflowResponse)(nil), // 164: aether.v1.WorkflowResponse
+ (*MessageEnvelope)(nil), // 165: aether.v1.MessageEnvelope
+ (*AuditQuery)(nil), // 166: aether.v1.AuditQuery
+ (*AuditQueryResponse)(nil), // 167: aether.v1.AuditQueryResponse
+ (*AuditEntry)(nil), // 168: aether.v1.AuditEntry
+ (*SubmitAuditEventRequest)(nil), // 169: aether.v1.SubmitAuditEventRequest
+ (*SubmitAuditEventResponse)(nil), // 170: aether.v1.SubmitAuditEventResponse
+ (*ProxyHttpRequest)(nil), // 171: aether.v1.ProxyHttpRequest
+ (*ProxyHttpResponse)(nil), // 172: aether.v1.ProxyHttpResponse
+ (*ProxyHttpBodyChunk)(nil), // 173: aether.v1.ProxyHttpBodyChunk
+ (*ProxyError)(nil), // 174: aether.v1.ProxyError
+ (*TunnelOpen)(nil), // 175: aether.v1.TunnelOpen
+ (*TunnelData)(nil), // 176: aether.v1.TunnelData
+ (*TunnelClose)(nil), // 177: aether.v1.TunnelClose
+ (*TunnelAck)(nil), // 178: aether.v1.TunnelAck
+ (*ResolveAuthorityRequest)(nil), // 179: aether.v1.ResolveAuthorityRequest
+ (*ResolveAuthorityResponse)(nil), // 180: aether.v1.ResolveAuthorityResponse
+ (*ResolvedAuthority)(nil), // 181: aether.v1.ResolvedAuthority
+ (*AuthorityGrantInfo)(nil), // 182: aether.v1.AuthorityGrantInfo
+ (*ConnectionStatusRequest)(nil), // 183: aether.v1.ConnectionStatusRequest
+ (*ConnectionStatusResponse)(nil), // 184: aether.v1.ConnectionStatusResponse
+ (*TaskSubscriptionOperation)(nil), // 185: aether.v1.TaskSubscriptionOperation
+ (*TaskSubscriptionOperationResponse)(nil), // 186: aether.v1.TaskSubscriptionOperationResponse
+ (*TaskEvent)(nil), // 187: aether.v1.TaskEvent
+ (*TaskStatusChangedEvent)(nil), // 188: aether.v1.TaskStatusChangedEvent
+ (*TaskProgressEvent)(nil), // 189: aether.v1.TaskProgressEvent
+ (*TaskChildLifecycleEvent)(nil), // 190: aether.v1.TaskChildLifecycleEvent
+ (*TaskAuthorityRequestEventRelay)(nil), // 191: aether.v1.TaskAuthorityRequestEventRelay
+ (*ResourceAccessRequest)(nil), // 192: aether.v1.ResourceAccessRequest
+ (*AccessDecisionReceipt)(nil), // 193: aether.v1.AccessDecisionReceipt
+ (*AccessCheckOperation)(nil), // 194: aether.v1.AccessCheckOperation
+ (*AccessCheckResponse)(nil), // 195: aether.v1.AccessCheckResponse
+ (*BatchAccessCheckOperation)(nil), // 196: aether.v1.BatchAccessCheckOperation
+ (*BatchAccessCheckResponse)(nil), // 197: aether.v1.BatchAccessCheckResponse
+ nil, // 198: aether.v1.InitConnection.CredentialsEntry
+ nil, // 199: aether.v1.Metric.MetadataEntry
+ nil, // 200: aether.v1.KVResponse.KvMapEntry
+ nil, // 201: aether.v1.ConfigSnapshot.KvEntry
+ nil, // 202: aether.v1.ConfigSnapshot.GlobalKvEntry
+ nil, // 203: aether.v1.ConfigSnapshot.TaskContextEntry
+ nil, // 204: aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry
+ nil, // 205: aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry
+ nil, // 206: aether.v1.CreateTaskRequest.LaunchParamOverridesEntry
+ nil, // 207: aether.v1.CreateTaskRequest.MetadataEntry
+ nil, // 208: aether.v1.TaskAssignment.MetadataEntry
+ nil, // 209: aether.v1.TaskAssignment.LaunchParamsEntry
+ nil, // 210: aether.v1.HealthInfo.ChecksEntry
+ nil, // 211: aether.v1.TaskInfo.MetadataEntry
+ nil, // 212: aether.v1.WaitSpec.InputMatchEntry
+ nil, // 213: aether.v1.WorkspaceInfo.MetadataEntry
+ nil, // 214: aether.v1.AgentRegistrationInfo.LaunchParamsEntry
+ nil, // 215: aether.v1.AgentRegistrationInfo.CapabilitiesEntry
+ nil, // 216: aether.v1.AgentLaunchParams.ParamOverridesEntry
+ nil, // 217: aether.v1.ACLAuthorityGrantRequest.MetadataEntry
+ nil, // 218: aether.v1.ACLAuditEntryInfo.MetadataEntry
+ nil, // 219: aether.v1.ACLAuthorityGrantInfo.MetadataEntry
+ nil, // 220: aether.v1.ACLGroupRequest.MetadataEntry
+ nil, // 221: aether.v1.ACLRoleRequest.MetadataEntry
+ nil, // 222: aether.v1.ACLGroupInfo.MetadataEntry
+ nil, // 223: aether.v1.ACLRoleInfo.MetadataEntry
+ nil, // 224: aether.v1.AuthorityGrantExchangeRequest.MetadataEntry
+ nil, // 225: aether.v1.AuthorityGrantDeriveRequest.MetadataEntry
+ nil, // 226: aether.v1.AuthorityRequest.MetadataEntry
+ nil, // 227: aether.v1.CreateAuthorityRequestPayload.MetadataEntry
+ nil, // 228: aether.v1.ProgressReport.MetadataEntry
+ nil, // 229: aether.v1.ProgressUpdate.MetadataEntry
+ nil, // 230: aether.v1.MessageEnvelope.MetadataEntry
+ nil, // 231: aether.v1.SubmitAuditEventRequest.MetadataEntry
+ nil, // 232: aether.v1.ProxyHttpRequest.HeadersEntry
+ nil, // 233: aether.v1.ProxyHttpResponse.HeadersEntry
+ nil, // 234: aether.v1.TunnelOpen.MetadataEntry
+ nil, // 235: aether.v1.TaskProgressEvent.MetadataEntry
}
var file_aether_proto_depIdxs = []int32{
- 38, // 0: aether.v1.UpstreamMessage.init:type_name -> aether.v1.InitConnection
- 53, // 1: aether.v1.UpstreamMessage.send:type_name -> aether.v1.SendMessage
- 56, // 2: aether.v1.UpstreamMessage.switch_workspace:type_name -> aether.v1.SwitchWorkspace
- 57, // 3: aether.v1.UpstreamMessage.kv_op:type_name -> aether.v1.KVOperation
- 65, // 4: aether.v1.UpstreamMessage.create_task:type_name -> aether.v1.CreateTaskRequest
- 68, // 5: aether.v1.UpstreamMessage.checkpoint_op:type_name -> aether.v1.CheckpointOperation
- 70, // 6: aether.v1.UpstreamMessage.admin_query:type_name -> aether.v1.AdminQuery
- 78, // 7: aether.v1.UpstreamMessage.session_op:type_name -> aether.v1.SessionOperation
- 80, // 8: aether.v1.UpstreamMessage.task_query:type_name -> aether.v1.TaskQuery
- 84, // 9: aether.v1.UpstreamMessage.task_op:type_name -> aether.v1.TaskOperation
- 88, // 10: aether.v1.UpstreamMessage.workspace_op:type_name -> aether.v1.WorkspaceOperation
- 95, // 11: aether.v1.UpstreamMessage.agent_op:type_name -> aether.v1.AgentOperation
- 103, // 12: aether.v1.UpstreamMessage.acl_op:type_name -> aether.v1.ACLOperation
- 152, // 13: aether.v1.UpstreamMessage.progress:type_name -> aether.v1.ProgressReport
- 155, // 14: aether.v1.UpstreamMessage.workflow_op:type_name -> aether.v1.WorkflowOperation
- 156, // 15: aether.v1.UpstreamMessage.workflow_response:type_name -> aether.v1.WorkflowResponse
- 147, // 16: aether.v1.UpstreamMessage.token_op:type_name -> aether.v1.TokenOperation
- 158, // 17: aether.v1.UpstreamMessage.audit_query:type_name -> aether.v1.AuditQuery
- 128, // 18: aether.v1.UpstreamMessage.authority_grant_op:type_name -> aether.v1.AuthorityGrantOperation
- 163, // 19: aether.v1.UpstreamMessage.proxy_http_request:type_name -> aether.v1.ProxyHttpRequest
- 165, // 20: aether.v1.UpstreamMessage.proxy_http_body_chunk:type_name -> aether.v1.ProxyHttpBodyChunk
- 167, // 21: aether.v1.UpstreamMessage.tunnel_open:type_name -> aether.v1.TunnelOpen
- 168, // 22: aether.v1.UpstreamMessage.tunnel_data:type_name -> aether.v1.TunnelData
- 169, // 23: aether.v1.UpstreamMessage.tunnel_close:type_name -> aether.v1.TunnelClose
- 164, // 24: aether.v1.UpstreamMessage.proxy_http_response:type_name -> aether.v1.ProxyHttpResponse
- 170, // 25: aether.v1.UpstreamMessage.tunnel_ack:type_name -> aether.v1.TunnelAck
- 171, // 26: aether.v1.UpstreamMessage.resolve_authority_request:type_name -> aether.v1.ResolveAuthorityRequest
- 175, // 27: aether.v1.UpstreamMessage.connection_status_request:type_name -> aether.v1.ConnectionStatusRequest
- 161, // 28: aether.v1.UpstreamMessage.submit_audit_event:type_name -> aether.v1.SubmitAuditEventRequest
- 144, // 29: aether.v1.UpstreamMessage.authority_request_op:type_name -> aether.v1.AuthorityRequestOperation
- 177, // 30: aether.v1.UpstreamMessage.task_subscription_op:type_name -> aether.v1.TaskSubscriptionOperation
- 59, // 31: aether.v1.DownstreamMessage.msg:type_name -> aether.v1.IncomingMessage
- 60, // 32: aether.v1.DownstreamMessage.config:type_name -> aether.v1.ConfigSnapshot
- 61, // 33: aether.v1.DownstreamMessage.signal:type_name -> aether.v1.Signal
- 62, // 34: aether.v1.DownstreamMessage.error:type_name -> aether.v1.ErrorResponse
- 58, // 35: aether.v1.DownstreamMessage.kv:type_name -> aether.v1.KVResponse
- 67, // 36: aether.v1.DownstreamMessage.task_assignment:type_name -> aether.v1.TaskAssignment
- 37, // 37: aether.v1.DownstreamMessage.connection_ack:type_name -> aether.v1.ConnectionAck
- 69, // 38: aether.v1.DownstreamMessage.checkpoint:type_name -> aether.v1.CheckpointResponse
- 73, // 39: aether.v1.DownstreamMessage.admin:type_name -> aether.v1.AdminResponse
- 79, // 40: aether.v1.DownstreamMessage.session_response:type_name -> aether.v1.SessionOperationResponse
- 83, // 41: aether.v1.DownstreamMessage.task_query:type_name -> aether.v1.TaskQueryResponse
- 87, // 42: aether.v1.DownstreamMessage.task_op:type_name -> aether.v1.TaskOperationResponse
- 91, // 43: aether.v1.DownstreamMessage.workspace:type_name -> aether.v1.WorkspaceResponse
- 102, // 44: aether.v1.DownstreamMessage.agent:type_name -> aether.v1.AgentResponse
- 127, // 45: aether.v1.DownstreamMessage.acl:type_name -> aether.v1.ACLResponse
- 154, // 46: aether.v1.DownstreamMessage.progress_update:type_name -> aether.v1.ProgressUpdate
- 156, // 47: aether.v1.DownstreamMessage.workflow_response:type_name -> aether.v1.WorkflowResponse
- 155, // 48: aether.v1.DownstreamMessage.workflow_op:type_name -> aether.v1.WorkflowOperation
- 151, // 49: aether.v1.DownstreamMessage.token:type_name -> aether.v1.TokenResponse
- 159, // 50: aether.v1.DownstreamMessage.audit_response:type_name -> aether.v1.AuditQueryResponse
- 131, // 51: aether.v1.DownstreamMessage.authority_grant:type_name -> aether.v1.AuthorityGrantResponse
- 66, // 52: aether.v1.DownstreamMessage.create_task:type_name -> aether.v1.CreateTaskResponse
- 164, // 53: aether.v1.DownstreamMessage.proxy_http_response:type_name -> aether.v1.ProxyHttpResponse
- 165, // 54: aether.v1.DownstreamMessage.proxy_http_body_chunk:type_name -> aether.v1.ProxyHttpBodyChunk
- 170, // 55: aether.v1.DownstreamMessage.tunnel_ack:type_name -> aether.v1.TunnelAck
- 169, // 56: aether.v1.DownstreamMessage.tunnel_close:type_name -> aether.v1.TunnelClose
- 168, // 57: aether.v1.DownstreamMessage.tunnel_data:type_name -> aether.v1.TunnelData
- 163, // 58: aether.v1.DownstreamMessage.proxy_http_request:type_name -> aether.v1.ProxyHttpRequest
- 172, // 59: aether.v1.DownstreamMessage.resolve_authority_response:type_name -> aether.v1.ResolveAuthorityResponse
- 176, // 60: aether.v1.DownstreamMessage.connection_status_response:type_name -> aether.v1.ConnectionStatusResponse
- 137, // 61: aether.v1.DownstreamMessage.authority_grant_revocation:type_name -> aether.v1.AuthorityGrantRevocation
- 162, // 62: aether.v1.DownstreamMessage.submit_audit_event_response:type_name -> aether.v1.SubmitAuditEventResponse
- 145, // 63: aether.v1.DownstreamMessage.authority_request_response:type_name -> aether.v1.AuthorityRequestOperationResponse
- 146, // 64: aether.v1.DownstreamMessage.authority_request_event:type_name -> aether.v1.AuthorityRequestEvent
- 36, // 65: aether.v1.DownstreamMessage.task_hibernated:type_name -> aether.v1.TaskHibernated
- 178, // 66: aether.v1.DownstreamMessage.task_subscription_response:type_name -> aether.v1.TaskSubscriptionOperationResponse
- 179, // 67: aether.v1.DownstreamMessage.task_event:type_name -> aether.v1.TaskEvent
- 86, // 68: aether.v1.TaskHibernated.descriptor:type_name -> aether.v1.HibernationDescriptor
- 41, // 69: aether.v1.ConnectionAck.negotiated_extensions:type_name -> aether.v1.NegotiatedExtension
- 39, // 70: aether.v1.ConnectionAck.server_build_info:type_name -> aether.v1.BuildInfo
- 47, // 71: aether.v1.InitConnection.agent:type_name -> aether.v1.AgentIdentity
- 48, // 72: aether.v1.InitConnection.task:type_name -> aether.v1.TaskIdentity
- 49, // 73: aether.v1.InitConnection.user:type_name -> aether.v1.UserIdentity
- 44, // 74: aether.v1.InitConnection.orchestrator:type_name -> aether.v1.OrchestratorIdentity
- 42, // 75: aether.v1.InitConnection.workflow_engine:type_name -> aether.v1.WorkflowEngineIdentity
- 43, // 76: aether.v1.InitConnection.metrics_bridge:type_name -> aether.v1.MetricsBridgeIdentity
- 45, // 77: aether.v1.InitConnection.bridge:type_name -> aether.v1.BridgeIdentity
- 46, // 78: aether.v1.InitConnection.service:type_name -> aether.v1.ServiceIdentity
- 184, // 79: aether.v1.InitConnection.credentials:type_name -> aether.v1.InitConnection.CredentialsEntry
- 40, // 80: aether.v1.InitConnection.extensions:type_name -> aether.v1.ExtensionDeclaration
- 39, // 81: aether.v1.InitConnection.client_build_info:type_name -> aether.v1.BuildInfo
- 50, // 82: aether.v1.AuthorizationContext.subject:type_name -> aether.v1.PrincipalRef
- 52, // 83: aether.v1.AuthorizationContext.resolved:type_name -> aether.v1.ResolvedAuthorityInfo
- 50, // 84: aether.v1.ResolvedAuthorityInfo.root_subject:type_name -> aether.v1.PrincipalRef
- 0, // 85: aether.v1.SendMessage.message_type:type_name -> aether.v1.MessageType
- 51, // 86: aether.v1.SendMessage.authorization:type_name -> aether.v1.AuthorizationContext
- 55, // 87: aether.v1.Metric.entries:type_name -> aether.v1.MetricEntry
- 185, // 88: aether.v1.Metric.metadata:type_name -> aether.v1.Metric.MetadataEntry
- 13, // 89: aether.v1.KVOperation.op:type_name -> aether.v1.KVOperation.OpType
- 14, // 90: aether.v1.KVOperation.scope:type_name -> aether.v1.KVOperation.Scope
- 51, // 91: aether.v1.KVOperation.authorization:type_name -> aether.v1.AuthorizationContext
- 186, // 92: aether.v1.KVResponse.kv_map:type_name -> aether.v1.KVResponse.KvMapEntry
- 0, // 93: aether.v1.IncomingMessage.message_type:type_name -> aether.v1.MessageType
- 50, // 94: aether.v1.IncomingMessage.on_behalf_subject:type_name -> aether.v1.PrincipalRef
- 187, // 95: aether.v1.ConfigSnapshot.kv:type_name -> aether.v1.ConfigSnapshot.KvEntry
- 188, // 96: aether.v1.ConfigSnapshot.global_kv:type_name -> aether.v1.ConfigSnapshot.GlobalKvEntry
- 189, // 97: aether.v1.ConfigSnapshot.task_context:type_name -> aether.v1.ConfigSnapshot.TaskContextEntry
- 190, // 98: aether.v1.ConfigSnapshot.workspace_exclusive_kv:type_name -> aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry
- 191, // 99: aether.v1.ConfigSnapshot.global_exclusive_kv:type_name -> aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry
- 15, // 100: aether.v1.Signal.type:type_name -> aether.v1.Signal.SignalType
- 9, // 101: aether.v1.RetryPolicy.backoff:type_name -> aether.v1.BackoffStrategy
- 2, // 102: aether.v1.TaskCompletionEvent.on_statuses:type_name -> aether.v1.TaskStatus
- 6, // 103: aether.v1.CreateTaskRequest.assignment_mode:type_name -> aether.v1.TaskAssignmentMode
- 192, // 104: aether.v1.CreateTaskRequest.launch_param_overrides:type_name -> aether.v1.CreateTaskRequest.LaunchParamOverridesEntry
- 193, // 105: aether.v1.CreateTaskRequest.metadata:type_name -> aether.v1.CreateTaskRequest.MetadataEntry
- 51, // 106: aether.v1.CreateTaskRequest.authorization:type_name -> aether.v1.AuthorizationContext
- 7, // 107: aether.v1.CreateTaskRequest.task_class:type_name -> aether.v1.TaskClass
- 63, // 108: aether.v1.CreateTaskRequest.retry_policy:type_name -> aether.v1.RetryPolicy
- 8, // 109: aether.v1.CreateTaskRequest.priority:type_name -> aether.v1.TaskPriority
- 64, // 110: aether.v1.CreateTaskRequest.completion_event:type_name -> aether.v1.TaskCompletionEvent
- 194, // 111: aether.v1.TaskAssignment.metadata:type_name -> aether.v1.TaskAssignment.MetadataEntry
- 195, // 112: aether.v1.TaskAssignment.launch_params:type_name -> aether.v1.TaskAssignment.LaunchParamsEntry
- 7, // 113: aether.v1.TaskAssignment.task_class:type_name -> aether.v1.TaskClass
- 16, // 114: aether.v1.CheckpointOperation.op:type_name -> aether.v1.CheckpointOperation.OpType
- 17, // 115: aether.v1.AdminQuery.op:type_name -> aether.v1.AdminQuery.OpType
- 71, // 116: aether.v1.AdminQuery.filter:type_name -> aether.v1.ConnectionFilter
- 1, // 117: aether.v1.ConnectionFilter.type:type_name -> aether.v1.PrincipalType
- 1, // 118: aether.v1.ConnectionInfo.type:type_name -> aether.v1.PrincipalType
- 74, // 119: aether.v1.AdminResponse.health:type_name -> aether.v1.HealthInfo
- 76, // 120: aether.v1.AdminResponse.info:type_name -> aether.v1.GatewayInfo
- 77, // 121: aether.v1.AdminResponse.stats:type_name -> aether.v1.GatewayStats
- 72, // 122: aether.v1.AdminResponse.connection:type_name -> aether.v1.ConnectionInfo
- 72, // 123: aether.v1.AdminResponse.connections:type_name -> aether.v1.ConnectionInfo
- 3, // 124: aether.v1.HealthInfo.status:type_name -> aether.v1.HealthStatus
- 196, // 125: aether.v1.HealthInfo.checks:type_name -> aether.v1.HealthInfo.ChecksEntry
- 77, // 126: aether.v1.HealthInfo.stats:type_name -> aether.v1.GatewayStats
- 4, // 127: aether.v1.HealthCheck.status:type_name -> aether.v1.HealthCheckStatus
- 18, // 128: aether.v1.SessionOperation.op:type_name -> aether.v1.SessionOperation.OpType
- 71, // 129: aether.v1.SessionOperation.filter:type_name -> aether.v1.ConnectionFilter
- 51, // 130: aether.v1.SessionOperation.authorization:type_name -> aether.v1.AuthorizationContext
- 72, // 131: aether.v1.SessionOperationResponse.connection:type_name -> aether.v1.ConnectionInfo
- 72, // 132: aether.v1.SessionOperationResponse.connections:type_name -> aether.v1.ConnectionInfo
- 19, // 133: aether.v1.TaskQuery.op:type_name -> aether.v1.TaskQuery.OpType
- 81, // 134: aether.v1.TaskQuery.filter:type_name -> aether.v1.TaskFilter
- 2, // 135: aether.v1.TaskFilter.status:type_name -> aether.v1.TaskStatus
- 2, // 136: aether.v1.TaskFilter.statuses:type_name -> aether.v1.TaskStatus
- 7, // 137: aether.v1.TaskFilter.task_class:type_name -> aether.v1.TaskClass
- 7, // 138: aether.v1.TaskFilter.exclude_task_classes:type_name -> aether.v1.TaskClass
- 2, // 139: aether.v1.TaskFilter.exclude_statuses:type_name -> aether.v1.TaskStatus
- 50, // 140: aether.v1.TaskFilter.creator_actor:type_name -> aether.v1.PrincipalRef
- 8, // 141: aether.v1.TaskFilter.priority:type_name -> aether.v1.TaskPriority
- 8, // 142: aether.v1.TaskFilter.min_priority:type_name -> aether.v1.TaskPriority
- 2, // 143: aether.v1.TaskInfo.status:type_name -> aether.v1.TaskStatus
- 197, // 144: aether.v1.TaskInfo.metadata:type_name -> aether.v1.TaskInfo.MetadataEntry
- 7, // 145: aether.v1.TaskInfo.task_class:type_name -> aether.v1.TaskClass
- 85, // 146: aether.v1.TaskInfo.wait_spec:type_name -> aether.v1.WaitSpec
- 8, // 147: aether.v1.TaskInfo.priority:type_name -> aether.v1.TaskPriority
- 64, // 148: aether.v1.TaskInfo.completion_event:type_name -> aether.v1.TaskCompletionEvent
- 82, // 149: aether.v1.TaskQueryResponse.task:type_name -> aether.v1.TaskInfo
- 82, // 150: aether.v1.TaskQueryResponse.tasks:type_name -> aether.v1.TaskInfo
- 20, // 151: aether.v1.TaskOperation.op:type_name -> aether.v1.TaskOperation.OpType
- 85, // 152: aether.v1.TaskOperation.wait_spec:type_name -> aether.v1.WaitSpec
- 10, // 153: aether.v1.WaitSpec.reason:type_name -> aether.v1.WaitReason
- 198, // 154: aether.v1.WaitSpec.input_match:type_name -> aether.v1.WaitSpec.InputMatchEntry
- 86, // 155: aether.v1.WaitSpec.hibernation:type_name -> aether.v1.HibernationDescriptor
- 82, // 156: aether.v1.TaskOperationResponse.task:type_name -> aether.v1.TaskInfo
- 21, // 157: aether.v1.WorkspaceOperation.op:type_name -> aether.v1.WorkspaceOperation.OpType
- 89, // 158: aether.v1.WorkspaceOperation.filter:type_name -> aether.v1.WorkspaceFilter
- 90, // 159: aether.v1.WorkspaceOperation.workspace:type_name -> aether.v1.WorkspaceInfo
- 199, // 160: aether.v1.WorkspaceInfo.metadata:type_name -> aether.v1.WorkspaceInfo.MetadataEntry
- 90, // 161: aether.v1.WorkspaceResponse.workspace:type_name -> aether.v1.WorkspaceInfo
- 90, // 162: aether.v1.WorkspaceResponse.workspaces:type_name -> aether.v1.WorkspaceInfo
- 92, // 163: aether.v1.WorkspaceResponse.message_flow:type_name -> aether.v1.MessageFlowInfo
- 93, // 164: aether.v1.MessageFlowInfo.nodes:type_name -> aether.v1.FlowNode
- 94, // 165: aether.v1.MessageFlowInfo.edges:type_name -> aether.v1.FlowEdge
- 1, // 166: aether.v1.FlowNode.type:type_name -> aether.v1.PrincipalType
- 22, // 167: aether.v1.AgentOperation.op:type_name -> aether.v1.AgentOperation.OpType
- 96, // 168: aether.v1.AgentOperation.filter:type_name -> aether.v1.AgentFilter
- 97, // 169: aether.v1.AgentOperation.agent:type_name -> aether.v1.AgentRegistrationInfo
- 99, // 170: aether.v1.AgentOperation.launch_params:type_name -> aether.v1.AgentLaunchParams
- 200, // 171: aether.v1.AgentRegistrationInfo.launch_params:type_name -> aether.v1.AgentRegistrationInfo.LaunchParamsEntry
- 98, // 172: aether.v1.AgentRegistrationInfo.resource_schema:type_name -> aether.v1.AgentResourceSchemaEntry
- 201, // 173: aether.v1.AgentRegistrationInfo.capabilities:type_name -> aether.v1.AgentRegistrationInfo.CapabilitiesEntry
- 202, // 174: aether.v1.AgentLaunchParams.param_overrides:type_name -> aether.v1.AgentLaunchParams.ParamOverridesEntry
- 97, // 175: aether.v1.AgentResponse.agent:type_name -> aether.v1.AgentRegistrationInfo
- 97, // 176: aether.v1.AgentResponse.agents:type_name -> aether.v1.AgentRegistrationInfo
- 100, // 177: aether.v1.AgentResponse.orchestrators:type_name -> aether.v1.OrchestratorInfo
- 101, // 178: aether.v1.AgentResponse.launch_result:type_name -> aether.v1.AgentLaunchResult
- 23, // 179: aether.v1.ACLOperation.op:type_name -> aether.v1.ACLOperation.OpType
- 104, // 180: aether.v1.ACLOperation.rule_filter:type_name -> aether.v1.ACLRuleFilter
- 105, // 181: aether.v1.ACLOperation.audit_filter:type_name -> aether.v1.ACLAuditFilter
- 106, // 182: aether.v1.ACLOperation.grant_request:type_name -> aether.v1.ACLGrantRequest
- 107, // 183: aether.v1.ACLOperation.fallback_request:type_name -> aether.v1.ACLSetFallbackRequest
- 50, // 184: aether.v1.ACLOperation.principal:type_name -> aether.v1.PrincipalRef
- 117, // 185: aether.v1.ACLOperation.group_request:type_name -> aether.v1.ACLGroupRequest
- 118, // 186: aether.v1.ACLOperation.role_request:type_name -> aether.v1.ACLRoleRequest
- 119, // 187: aether.v1.ACLOperation.member_request:type_name -> aether.v1.ACLGroupMemberRequest
- 120, // 188: aether.v1.ACLOperation.assignment_request:type_name -> aether.v1.ACLRoleAssignmentRequest
- 51, // 189: aether.v1.ACLOperation.authorization:type_name -> aether.v1.AuthorizationContext
- 50, // 190: aether.v1.ACLAuthorityGrantRequest.subject:type_name -> aether.v1.PrincipalRef
- 50, // 191: aether.v1.ACLAuthorityGrantRequest.delegate:type_name -> aether.v1.PrincipalRef
- 50, // 192: aether.v1.ACLAuthorityGrantRequest.issued_by:type_name -> aether.v1.PrincipalRef
- 50, // 193: aether.v1.ACLAuthorityGrantRequest.root_subject:type_name -> aether.v1.PrincipalRef
- 109, // 194: aether.v1.ACLAuthorityGrantRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
- 203, // 195: aether.v1.ACLAuthorityGrantRequest.metadata:type_name -> aether.v1.ACLAuthorityGrantRequest.MetadataEntry
- 204, // 196: aether.v1.ACLAuditEntryInfo.metadata:type_name -> aether.v1.ACLAuditEntryInfo.MetadataEntry
- 50, // 197: aether.v1.ACLAuthorityGrantInfo.subject:type_name -> aether.v1.PrincipalRef
- 50, // 198: aether.v1.ACLAuthorityGrantInfo.delegate:type_name -> aether.v1.PrincipalRef
- 50, // 199: aether.v1.ACLAuthorityGrantInfo.issued_by:type_name -> aether.v1.PrincipalRef
- 50, // 200: aether.v1.ACLAuthorityGrantInfo.root_subject:type_name -> aether.v1.PrincipalRef
- 109, // 201: aether.v1.ACLAuthorityGrantInfo.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
- 205, // 202: aether.v1.ACLAuthorityGrantInfo.metadata:type_name -> aether.v1.ACLAuthorityGrantInfo.MetadataEntry
- 206, // 203: aether.v1.ACLGroupRequest.metadata:type_name -> aether.v1.ACLGroupRequest.MetadataEntry
- 207, // 204: aether.v1.ACLRoleRequest.metadata:type_name -> aether.v1.ACLRoleRequest.MetadataEntry
- 208, // 205: aether.v1.ACLGroupInfo.metadata:type_name -> aether.v1.ACLGroupInfo.MetadataEntry
- 209, // 206: aether.v1.ACLRoleInfo.metadata:type_name -> aether.v1.ACLRoleInfo.MetadataEntry
- 125, // 207: aether.v1.ACLAccessExplanationInfo.contributions:type_name -> aether.v1.ACLAccessContributionInfo
- 112, // 208: aether.v1.ACLResponse.rule:type_name -> aether.v1.ACLRuleInfo
- 112, // 209: aether.v1.ACLResponse.rules:type_name -> aether.v1.ACLRuleInfo
- 113, // 210: aether.v1.ACLResponse.fallback_policy:type_name -> aether.v1.ACLFallbackPolicyInfo
- 114, // 211: aether.v1.ACLResponse.audit_entries:type_name -> aether.v1.ACLAuditEntryInfo
- 116, // 212: aether.v1.ACLResponse.cleanup_result:type_name -> aether.v1.ACLCleanupResult
- 115, // 213: aether.v1.ACLResponse.authority_grant:type_name -> aether.v1.ACLAuthorityGrantInfo
- 115, // 214: aether.v1.ACLResponse.authority_grants:type_name -> aether.v1.ACLAuthorityGrantInfo
- 121, // 215: aether.v1.ACLResponse.group:type_name -> aether.v1.ACLGroupInfo
- 121, // 216: aether.v1.ACLResponse.groups:type_name -> aether.v1.ACLGroupInfo
- 122, // 217: aether.v1.ACLResponse.role:type_name -> aether.v1.ACLRoleInfo
- 122, // 218: aether.v1.ACLResponse.roles:type_name -> aether.v1.ACLRoleInfo
- 123, // 219: aether.v1.ACLResponse.group_members:type_name -> aether.v1.ACLGroupMemberInfo
- 124, // 220: aether.v1.ACLResponse.role_assignments:type_name -> aether.v1.ACLRoleAssignmentInfo
- 126, // 221: aether.v1.ACLResponse.explanation:type_name -> aether.v1.ACLAccessExplanationInfo
- 24, // 222: aether.v1.AuthorityGrantOperation.op:type_name -> aether.v1.AuthorityGrantOperation.OpType
- 129, // 223: aether.v1.AuthorityGrantOperation.exchange_request:type_name -> aether.v1.AuthorityGrantExchangeRequest
- 130, // 224: aether.v1.AuthorityGrantOperation.derive_request:type_name -> aether.v1.AuthorityGrantDeriveRequest
- 111, // 225: aether.v1.AuthorityGrantOperation.renew_request:type_name -> aether.v1.ACLRenewAuthorityGrantRequest
- 132, // 226: aether.v1.AuthorityGrantOperation.list_request:type_name -> aether.v1.AuthorityGrantListRequest
- 133, // 227: aether.v1.AuthorityGrantOperation.batch_exchange_request:type_name -> aether.v1.AuthorityGrantBatchExchangeRequest
- 134, // 228: aether.v1.AuthorityGrantOperation.derive_for_target_request:type_name -> aether.v1.AuthorityGrantDeriveForTargetRequest
- 109, // 229: aether.v1.AuthorityGrantExchangeRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
- 210, // 230: aether.v1.AuthorityGrantExchangeRequest.metadata:type_name -> aether.v1.AuthorityGrantExchangeRequest.MetadataEntry
- 50, // 231: aether.v1.AuthorityGrantDeriveRequest.delegate:type_name -> aether.v1.PrincipalRef
- 109, // 232: aether.v1.AuthorityGrantDeriveRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
- 211, // 233: aether.v1.AuthorityGrantDeriveRequest.metadata:type_name -> aether.v1.AuthorityGrantDeriveRequest.MetadataEntry
- 115, // 234: aether.v1.AuthorityGrantResponse.grant:type_name -> aether.v1.ACLAuthorityGrantInfo
- 115, // 235: aether.v1.AuthorityGrantResponse.grants:type_name -> aether.v1.ACLAuthorityGrantInfo
- 129, // 236: aether.v1.AuthorityGrantBatchExchangeRequest.requests:type_name -> aether.v1.AuthorityGrantExchangeRequest
- 50, // 237: aether.v1.AuthorityGrantDeriveForTargetRequest.target:type_name -> aether.v1.PrincipalRef
- 50, // 238: aether.v1.AuthorityIdentity.subject:type_name -> aether.v1.PrincipalRef
- 50, // 239: aether.v1.AuthorityIdentity.root_subject:type_name -> aether.v1.PrincipalRef
- 50, // 240: aether.v1.AuthorityIdentity.delegate:type_name -> aether.v1.PrincipalRef
- 50, // 241: aether.v1.AuthorityIdentity.issued_by:type_name -> aether.v1.PrincipalRef
- 50, // 242: aether.v1.AuthorityRequestRoutingTarget.principal:type_name -> aether.v1.PrincipalRef
- 11, // 243: aether.v1.AuthorityRequest.status:type_name -> aether.v1.AuthorityRequestStatus
- 50, // 244: aether.v1.AuthorityRequest.requesting_actor:type_name -> aether.v1.PrincipalRef
- 50, // 245: aether.v1.AuthorityRequest.target_subject:type_name -> aether.v1.PrincipalRef
- 139, // 246: aether.v1.AuthorityRequest.desired_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
- 5, // 247: aether.v1.AuthorityRequest.requested_access_level:type_name -> aether.v1.AccessLevel
- 138, // 248: aether.v1.AuthorityRequest.routing_target:type_name -> aether.v1.AuthorityRequestRoutingTarget
- 212, // 249: aether.v1.AuthorityRequest.metadata:type_name -> aether.v1.AuthorityRequest.MetadataEntry
- 50, // 250: aether.v1.AuthorityRequest.resolved_by:type_name -> aether.v1.PrincipalRef
- 50, // 251: aether.v1.CreateAuthorityRequestPayload.requesting_actor:type_name -> aether.v1.PrincipalRef
- 50, // 252: aether.v1.CreateAuthorityRequestPayload.target_subject:type_name -> aether.v1.PrincipalRef
- 139, // 253: aether.v1.CreateAuthorityRequestPayload.desired_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
- 5, // 254: aether.v1.CreateAuthorityRequestPayload.requested_access_level:type_name -> aether.v1.AccessLevel
- 138, // 255: aether.v1.CreateAuthorityRequestPayload.routing_target:type_name -> aether.v1.AuthorityRequestRoutingTarget
- 213, // 256: aether.v1.CreateAuthorityRequestPayload.metadata:type_name -> aether.v1.CreateAuthorityRequestPayload.MetadataEntry
- 25, // 257: aether.v1.ResolveAuthorityRequestPayload.decision:type_name -> aether.v1.ResolveAuthorityRequestPayload.Decision
- 139, // 258: aether.v1.ResolveAuthorityRequestPayload.granted_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
- 5, // 259: aether.v1.ResolveAuthorityRequestPayload.granted_access_level:type_name -> aether.v1.AccessLevel
- 11, // 260: aether.v1.AuthorityRequestListFilter.status:type_name -> aether.v1.AuthorityRequestStatus
- 26, // 261: aether.v1.AuthorityRequestOperation.op:type_name -> aether.v1.AuthorityRequestOperation.OpType
- 141, // 262: aether.v1.AuthorityRequestOperation.create:type_name -> aether.v1.CreateAuthorityRequestPayload
- 142, // 263: aether.v1.AuthorityRequestOperation.resolve:type_name -> aether.v1.ResolveAuthorityRequestPayload
- 143, // 264: aether.v1.AuthorityRequestOperation.list_filter:type_name -> aether.v1.AuthorityRequestListFilter
- 140, // 265: aether.v1.AuthorityRequestOperationResponse.request:type_name -> aether.v1.AuthorityRequest
- 140, // 266: aether.v1.AuthorityRequestOperationResponse.requests:type_name -> aether.v1.AuthorityRequest
- 27, // 267: aether.v1.AuthorityRequestEvent.event_type:type_name -> aether.v1.AuthorityRequestEvent.EventType
- 140, // 268: aether.v1.AuthorityRequestEvent.request:type_name -> aether.v1.AuthorityRequest
- 28, // 269: aether.v1.TokenOperation.op:type_name -> aether.v1.TokenOperation.OpType
- 148, // 270: aether.v1.TokenOperation.create_request:type_name -> aether.v1.TokenCreateRequest
- 149, // 271: aether.v1.TokenOperation.filter:type_name -> aether.v1.TokenFilter
- 150, // 272: aether.v1.TokenResponse.token:type_name -> aether.v1.TokenInfo
- 150, // 273: aether.v1.TokenResponse.tokens:type_name -> aether.v1.TokenInfo
- 150, // 274: aether.v1.TokenResponse.created_token:type_name -> aether.v1.TokenInfo
- 153, // 275: aether.v1.ProgressReport.step:type_name -> aether.v1.ProgressStep
- 214, // 276: aether.v1.ProgressReport.metadata:type_name -> aether.v1.ProgressReport.MetadataEntry
- 12, // 277: aether.v1.ProgressReport.kind:type_name -> aether.v1.ProgressKind
- 153, // 278: aether.v1.ProgressUpdate.step:type_name -> aether.v1.ProgressStep
- 215, // 279: aether.v1.ProgressUpdate.metadata:type_name -> aether.v1.ProgressUpdate.MetadataEntry
- 12, // 280: aether.v1.ProgressUpdate.kind:type_name -> aether.v1.ProgressKind
- 29, // 281: aether.v1.WorkflowOperation.op:type_name -> aether.v1.WorkflowOperation.OpType
- 0, // 282: aether.v1.MessageEnvelope.message_type:type_name -> aether.v1.MessageType
- 216, // 283: aether.v1.MessageEnvelope.metadata:type_name -> aether.v1.MessageEnvelope.MetadataEntry
- 50, // 284: aether.v1.MessageEnvelope.on_behalf_subject:type_name -> aether.v1.PrincipalRef
- 51, // 285: aether.v1.AuditQuery.authorization:type_name -> aether.v1.AuthorizationContext
- 160, // 286: aether.v1.AuditQueryResponse.entries:type_name -> aether.v1.AuditEntry
- 217, // 287: aether.v1.SubmitAuditEventRequest.metadata:type_name -> aether.v1.SubmitAuditEventRequest.MetadataEntry
- 218, // 288: aether.v1.ProxyHttpRequest.headers:type_name -> aether.v1.ProxyHttpRequest.HeadersEntry
- 51, // 289: aether.v1.ProxyHttpRequest.authorization:type_name -> aether.v1.AuthorizationContext
- 219, // 290: aether.v1.ProxyHttpResponse.headers:type_name -> aether.v1.ProxyHttpResponse.HeadersEntry
- 166, // 291: aether.v1.ProxyHttpResponse.error:type_name -> aether.v1.ProxyError
- 30, // 292: aether.v1.ProxyError.kind:type_name -> aether.v1.ProxyError.Kind
- 31, // 293: aether.v1.TunnelOpen.protocol:type_name -> aether.v1.TunnelOpen.Protocol
- 220, // 294: aether.v1.TunnelOpen.metadata:type_name -> aether.v1.TunnelOpen.MetadataEntry
- 51, // 295: aether.v1.TunnelOpen.authorization:type_name -> aether.v1.AuthorizationContext
- 32, // 296: aether.v1.TunnelClose.reason:type_name -> aether.v1.TunnelClose.Reason
- 50, // 297: aether.v1.ResolveAuthorityRequest.actor:type_name -> aether.v1.PrincipalRef
- 50, // 298: aether.v1.ResolveAuthorityRequest.subject:type_name -> aether.v1.PrincipalRef
- 173, // 299: aether.v1.ResolveAuthorityResponse.authority:type_name -> aether.v1.ResolvedAuthority
- 50, // 300: aether.v1.ResolvedAuthority.actor:type_name -> aether.v1.PrincipalRef
- 50, // 301: aether.v1.ResolvedAuthority.subject:type_name -> aether.v1.PrincipalRef
- 174, // 302: aether.v1.ResolvedAuthority.grant:type_name -> aether.v1.AuthorityGrantInfo
- 50, // 303: aether.v1.ConnectionStatusRequest.principal:type_name -> aether.v1.PrincipalRef
- 33, // 304: aether.v1.TaskSubscriptionOperation.op:type_name -> aether.v1.TaskSubscriptionOperation.OpType
- 180, // 305: aether.v1.TaskEvent.status_changed:type_name -> aether.v1.TaskStatusChangedEvent
- 181, // 306: aether.v1.TaskEvent.progress:type_name -> aether.v1.TaskProgressEvent
- 182, // 307: aether.v1.TaskEvent.child_lifecycle:type_name -> aether.v1.TaskChildLifecycleEvent
- 183, // 308: aether.v1.TaskEvent.authority_request:type_name -> aether.v1.TaskAuthorityRequestEventRelay
- 2, // 309: aether.v1.TaskStatusChangedEvent.from_status:type_name -> aether.v1.TaskStatus
- 2, // 310: aether.v1.TaskStatusChangedEvent.to_status:type_name -> aether.v1.TaskStatus
- 221, // 311: aether.v1.TaskProgressEvent.metadata:type_name -> aether.v1.TaskProgressEvent.MetadataEntry
- 2, // 312: aether.v1.TaskChildLifecycleEvent.child_status:type_name -> aether.v1.TaskStatus
- 146, // 313: aether.v1.TaskAuthorityRequestEventRelay.event:type_name -> aether.v1.AuthorityRequestEvent
- 75, // 314: aether.v1.HealthInfo.ChecksEntry.value:type_name -> aether.v1.HealthCheck
- 34, // 315: aether.v1.AetherGateway.Connect:input_type -> aether.v1.UpstreamMessage
- 35, // 316: aether.v1.AetherGateway.Connect:output_type -> aether.v1.DownstreamMessage
- 316, // [316:317] is the sub-list for method output_type
- 315, // [315:316] is the sub-list for method input_type
- 315, // [315:315] is the sub-list for extension type_name
- 315, // [315:315] is the sub-list for extension extendee
- 0, // [0:315] is the sub-list for field type_name
+ 41, // 0: aether.v1.UpstreamMessage.init:type_name -> aether.v1.InitConnection
+ 56, // 1: aether.v1.UpstreamMessage.send:type_name -> aether.v1.SendMessage
+ 61, // 2: aether.v1.UpstreamMessage.switch_workspace:type_name -> aether.v1.SwitchWorkspace
+ 62, // 3: aether.v1.UpstreamMessage.kv_op:type_name -> aether.v1.KVOperation
+ 71, // 4: aether.v1.UpstreamMessage.create_task:type_name -> aether.v1.CreateTaskRequest
+ 74, // 5: aether.v1.UpstreamMessage.checkpoint_op:type_name -> aether.v1.CheckpointOperation
+ 76, // 6: aether.v1.UpstreamMessage.admin_query:type_name -> aether.v1.AdminQuery
+ 84, // 7: aether.v1.UpstreamMessage.session_op:type_name -> aether.v1.SessionOperation
+ 86, // 8: aether.v1.UpstreamMessage.task_query:type_name -> aether.v1.TaskQuery
+ 90, // 9: aether.v1.UpstreamMessage.task_op:type_name -> aether.v1.TaskOperation
+ 94, // 10: aether.v1.UpstreamMessage.workspace_op:type_name -> aether.v1.WorkspaceOperation
+ 101, // 11: aether.v1.UpstreamMessage.agent_op:type_name -> aether.v1.AgentOperation
+ 109, // 12: aether.v1.UpstreamMessage.acl_op:type_name -> aether.v1.ACLOperation
+ 158, // 13: aether.v1.UpstreamMessage.progress:type_name -> aether.v1.ProgressReport
+ 163, // 14: aether.v1.UpstreamMessage.workflow_op:type_name -> aether.v1.WorkflowOperation
+ 164, // 15: aether.v1.UpstreamMessage.workflow_response:type_name -> aether.v1.WorkflowResponse
+ 153, // 16: aether.v1.UpstreamMessage.token_op:type_name -> aether.v1.TokenOperation
+ 166, // 17: aether.v1.UpstreamMessage.audit_query:type_name -> aether.v1.AuditQuery
+ 134, // 18: aether.v1.UpstreamMessage.authority_grant_op:type_name -> aether.v1.AuthorityGrantOperation
+ 171, // 19: aether.v1.UpstreamMessage.proxy_http_request:type_name -> aether.v1.ProxyHttpRequest
+ 173, // 20: aether.v1.UpstreamMessage.proxy_http_body_chunk:type_name -> aether.v1.ProxyHttpBodyChunk
+ 175, // 21: aether.v1.UpstreamMessage.tunnel_open:type_name -> aether.v1.TunnelOpen
+ 176, // 22: aether.v1.UpstreamMessage.tunnel_data:type_name -> aether.v1.TunnelData
+ 177, // 23: aether.v1.UpstreamMessage.tunnel_close:type_name -> aether.v1.TunnelClose
+ 172, // 24: aether.v1.UpstreamMessage.proxy_http_response:type_name -> aether.v1.ProxyHttpResponse
+ 178, // 25: aether.v1.UpstreamMessage.tunnel_ack:type_name -> aether.v1.TunnelAck
+ 179, // 26: aether.v1.UpstreamMessage.resolve_authority_request:type_name -> aether.v1.ResolveAuthorityRequest
+ 183, // 27: aether.v1.UpstreamMessage.connection_status_request:type_name -> aether.v1.ConnectionStatusRequest
+ 169, // 28: aether.v1.UpstreamMessage.submit_audit_event:type_name -> aether.v1.SubmitAuditEventRequest
+ 150, // 29: aether.v1.UpstreamMessage.authority_request_op:type_name -> aether.v1.AuthorityRequestOperation
+ 185, // 30: aether.v1.UpstreamMessage.task_subscription_op:type_name -> aether.v1.TaskSubscriptionOperation
+ 194, // 31: aether.v1.UpstreamMessage.access_check:type_name -> aether.v1.AccessCheckOperation
+ 196, // 32: aether.v1.UpstreamMessage.batch_access_check:type_name -> aether.v1.BatchAccessCheckOperation
+ 64, // 33: aether.v1.DownstreamMessage.msg:type_name -> aether.v1.IncomingMessage
+ 66, // 34: aether.v1.DownstreamMessage.config:type_name -> aether.v1.ConfigSnapshot
+ 67, // 35: aether.v1.DownstreamMessage.signal:type_name -> aether.v1.Signal
+ 68, // 36: aether.v1.DownstreamMessage.error:type_name -> aether.v1.ErrorResponse
+ 63, // 37: aether.v1.DownstreamMessage.kv:type_name -> aether.v1.KVResponse
+ 73, // 38: aether.v1.DownstreamMessage.task_assignment:type_name -> aether.v1.TaskAssignment
+ 40, // 39: aether.v1.DownstreamMessage.connection_ack:type_name -> aether.v1.ConnectionAck
+ 75, // 40: aether.v1.DownstreamMessage.checkpoint:type_name -> aether.v1.CheckpointResponse
+ 79, // 41: aether.v1.DownstreamMessage.admin:type_name -> aether.v1.AdminResponse
+ 85, // 42: aether.v1.DownstreamMessage.session_response:type_name -> aether.v1.SessionOperationResponse
+ 89, // 43: aether.v1.DownstreamMessage.task_query:type_name -> aether.v1.TaskQueryResponse
+ 93, // 44: aether.v1.DownstreamMessage.task_op:type_name -> aether.v1.TaskOperationResponse
+ 97, // 45: aether.v1.DownstreamMessage.workspace:type_name -> aether.v1.WorkspaceResponse
+ 108, // 46: aether.v1.DownstreamMessage.agent:type_name -> aether.v1.AgentResponse
+ 133, // 47: aether.v1.DownstreamMessage.acl:type_name -> aether.v1.ACLResponse
+ 160, // 48: aether.v1.DownstreamMessage.progress_update:type_name -> aether.v1.ProgressUpdate
+ 164, // 49: aether.v1.DownstreamMessage.workflow_response:type_name -> aether.v1.WorkflowResponse
+ 163, // 50: aether.v1.DownstreamMessage.workflow_op:type_name -> aether.v1.WorkflowOperation
+ 157, // 51: aether.v1.DownstreamMessage.token:type_name -> aether.v1.TokenResponse
+ 167, // 52: aether.v1.DownstreamMessage.audit_response:type_name -> aether.v1.AuditQueryResponse
+ 137, // 53: aether.v1.DownstreamMessage.authority_grant:type_name -> aether.v1.AuthorityGrantResponse
+ 72, // 54: aether.v1.DownstreamMessage.create_task:type_name -> aether.v1.CreateTaskResponse
+ 172, // 55: aether.v1.DownstreamMessage.proxy_http_response:type_name -> aether.v1.ProxyHttpResponse
+ 173, // 56: aether.v1.DownstreamMessage.proxy_http_body_chunk:type_name -> aether.v1.ProxyHttpBodyChunk
+ 178, // 57: aether.v1.DownstreamMessage.tunnel_ack:type_name -> aether.v1.TunnelAck
+ 177, // 58: aether.v1.DownstreamMessage.tunnel_close:type_name -> aether.v1.TunnelClose
+ 176, // 59: aether.v1.DownstreamMessage.tunnel_data:type_name -> aether.v1.TunnelData
+ 171, // 60: aether.v1.DownstreamMessage.proxy_http_request:type_name -> aether.v1.ProxyHttpRequest
+ 180, // 61: aether.v1.DownstreamMessage.resolve_authority_response:type_name -> aether.v1.ResolveAuthorityResponse
+ 184, // 62: aether.v1.DownstreamMessage.connection_status_response:type_name -> aether.v1.ConnectionStatusResponse
+ 143, // 63: aether.v1.DownstreamMessage.authority_grant_revocation:type_name -> aether.v1.AuthorityGrantRevocation
+ 170, // 64: aether.v1.DownstreamMessage.submit_audit_event_response:type_name -> aether.v1.SubmitAuditEventResponse
+ 151, // 65: aether.v1.DownstreamMessage.authority_request_response:type_name -> aether.v1.AuthorityRequestOperationResponse
+ 152, // 66: aether.v1.DownstreamMessage.authority_request_event:type_name -> aether.v1.AuthorityRequestEvent
+ 39, // 67: aether.v1.DownstreamMessage.task_hibernated:type_name -> aether.v1.TaskHibernated
+ 186, // 68: aether.v1.DownstreamMessage.task_subscription_response:type_name -> aether.v1.TaskSubscriptionOperationResponse
+ 187, // 69: aether.v1.DownstreamMessage.task_event:type_name -> aether.v1.TaskEvent
+ 195, // 70: aether.v1.DownstreamMessage.access_check_response:type_name -> aether.v1.AccessCheckResponse
+ 197, // 71: aether.v1.DownstreamMessage.batch_access_check_response:type_name -> aether.v1.BatchAccessCheckResponse
+ 92, // 72: aether.v1.TaskHibernated.descriptor:type_name -> aether.v1.HibernationDescriptor
+ 44, // 73: aether.v1.ConnectionAck.negotiated_extensions:type_name -> aether.v1.NegotiatedExtension
+ 42, // 74: aether.v1.ConnectionAck.server_build_info:type_name -> aether.v1.BuildInfo
+ 50, // 75: aether.v1.InitConnection.agent:type_name -> aether.v1.AgentIdentity
+ 51, // 76: aether.v1.InitConnection.task:type_name -> aether.v1.TaskIdentity
+ 52, // 77: aether.v1.InitConnection.user:type_name -> aether.v1.UserIdentity
+ 47, // 78: aether.v1.InitConnection.orchestrator:type_name -> aether.v1.OrchestratorIdentity
+ 45, // 79: aether.v1.InitConnection.workflow_engine:type_name -> aether.v1.WorkflowEngineIdentity
+ 46, // 80: aether.v1.InitConnection.metrics_bridge:type_name -> aether.v1.MetricsBridgeIdentity
+ 48, // 81: aether.v1.InitConnection.bridge:type_name -> aether.v1.BridgeIdentity
+ 49, // 82: aether.v1.InitConnection.service:type_name -> aether.v1.ServiceIdentity
+ 198, // 83: aether.v1.InitConnection.credentials:type_name -> aether.v1.InitConnection.CredentialsEntry
+ 43, // 84: aether.v1.InitConnection.extensions:type_name -> aether.v1.ExtensionDeclaration
+ 42, // 85: aether.v1.InitConnection.client_build_info:type_name -> aether.v1.BuildInfo
+ 53, // 86: aether.v1.AuthorizationContext.subject:type_name -> aether.v1.PrincipalRef
+ 55, // 87: aether.v1.AuthorizationContext.resolved:type_name -> aether.v1.ResolvedAuthorityInfo
+ 53, // 88: aether.v1.ResolvedAuthorityInfo.root_subject:type_name -> aether.v1.PrincipalRef
+ 0, // 89: aether.v1.SendMessage.message_type:type_name -> aether.v1.MessageType
+ 54, // 90: aether.v1.SendMessage.authorization:type_name -> aether.v1.AuthorizationContext
+ 192, // 91: aether.v1.SendMessage.checked_access:type_name -> aether.v1.ResourceAccessRequest
+ 58, // 92: aether.v1.SendMessage.authority_continuation:type_name -> aether.v1.AuthorityContinuationRequest
+ 115, // 93: aether.v1.AuthorityContinuationScope.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 15, // 94: aether.v1.AuthorityContinuationRequest.scope_mode:type_name -> aether.v1.AuthorityContinuationRequest.ScopeMode
+ 57, // 95: aether.v1.AuthorityContinuationRequest.scope:type_name -> aether.v1.AuthorityContinuationScope
+ 60, // 96: aether.v1.Metric.entries:type_name -> aether.v1.MetricEntry
+ 199, // 97: aether.v1.Metric.metadata:type_name -> aether.v1.Metric.MetadataEntry
+ 16, // 98: aether.v1.KVOperation.op:type_name -> aether.v1.KVOperation.OpType
+ 17, // 99: aether.v1.KVOperation.scope:type_name -> aether.v1.KVOperation.Scope
+ 54, // 100: aether.v1.KVOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 200, // 101: aether.v1.KVResponse.kv_map:type_name -> aether.v1.KVResponse.KvMapEntry
+ 0, // 102: aether.v1.IncomingMessage.message_type:type_name -> aether.v1.MessageType
+ 53, // 103: aether.v1.IncomingMessage.on_behalf_subject:type_name -> aether.v1.PrincipalRef
+ 193, // 104: aether.v1.IncomingMessage.access_receipt:type_name -> aether.v1.AccessDecisionReceipt
+ 65, // 105: aether.v1.IncomingMessage.forwarded_authorization:type_name -> aether.v1.ForwardedAuthorization
+ 54, // 106: aether.v1.ForwardedAuthorization.authorization:type_name -> aether.v1.AuthorizationContext
+ 57, // 107: aether.v1.ForwardedAuthorization.scope:type_name -> aether.v1.AuthorityContinuationScope
+ 201, // 108: aether.v1.ConfigSnapshot.kv:type_name -> aether.v1.ConfigSnapshot.KvEntry
+ 202, // 109: aether.v1.ConfigSnapshot.global_kv:type_name -> aether.v1.ConfigSnapshot.GlobalKvEntry
+ 203, // 110: aether.v1.ConfigSnapshot.task_context:type_name -> aether.v1.ConfigSnapshot.TaskContextEntry
+ 204, // 111: aether.v1.ConfigSnapshot.workspace_exclusive_kv:type_name -> aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry
+ 205, // 112: aether.v1.ConfigSnapshot.global_exclusive_kv:type_name -> aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry
+ 18, // 113: aether.v1.Signal.type:type_name -> aether.v1.Signal.SignalType
+ 9, // 114: aether.v1.RetryPolicy.backoff:type_name -> aether.v1.BackoffStrategy
+ 2, // 115: aether.v1.TaskCompletionEvent.on_statuses:type_name -> aether.v1.TaskStatus
+ 6, // 116: aether.v1.CreateTaskRequest.assignment_mode:type_name -> aether.v1.TaskAssignmentMode
+ 206, // 117: aether.v1.CreateTaskRequest.launch_param_overrides:type_name -> aether.v1.CreateTaskRequest.LaunchParamOverridesEntry
+ 207, // 118: aether.v1.CreateTaskRequest.metadata:type_name -> aether.v1.CreateTaskRequest.MetadataEntry
+ 54, // 119: aether.v1.CreateTaskRequest.authorization:type_name -> aether.v1.AuthorizationContext
+ 7, // 120: aether.v1.CreateTaskRequest.task_class:type_name -> aether.v1.TaskClass
+ 69, // 121: aether.v1.CreateTaskRequest.retry_policy:type_name -> aether.v1.RetryPolicy
+ 8, // 122: aether.v1.CreateTaskRequest.priority:type_name -> aether.v1.TaskPriority
+ 70, // 123: aether.v1.CreateTaskRequest.completion_event:type_name -> aether.v1.TaskCompletionEvent
+ 10, // 124: aether.v1.CreateTaskRequest.target_offline_policy:type_name -> aether.v1.TargetOfflinePolicy
+ 208, // 125: aether.v1.TaskAssignment.metadata:type_name -> aether.v1.TaskAssignment.MetadataEntry
+ 209, // 126: aether.v1.TaskAssignment.launch_params:type_name -> aether.v1.TaskAssignment.LaunchParamsEntry
+ 7, // 127: aether.v1.TaskAssignment.task_class:type_name -> aether.v1.TaskClass
+ 54, // 128: aether.v1.TaskAssignment.authorization:type_name -> aether.v1.AuthorizationContext
+ 19, // 129: aether.v1.CheckpointOperation.op:type_name -> aether.v1.CheckpointOperation.OpType
+ 20, // 130: aether.v1.AdminQuery.op:type_name -> aether.v1.AdminQuery.OpType
+ 77, // 131: aether.v1.AdminQuery.filter:type_name -> aether.v1.ConnectionFilter
+ 1, // 132: aether.v1.ConnectionFilter.type:type_name -> aether.v1.PrincipalType
+ 1, // 133: aether.v1.ConnectionInfo.type:type_name -> aether.v1.PrincipalType
+ 80, // 134: aether.v1.AdminResponse.health:type_name -> aether.v1.HealthInfo
+ 82, // 135: aether.v1.AdminResponse.info:type_name -> aether.v1.GatewayInfo
+ 83, // 136: aether.v1.AdminResponse.stats:type_name -> aether.v1.GatewayStats
+ 78, // 137: aether.v1.AdminResponse.connection:type_name -> aether.v1.ConnectionInfo
+ 78, // 138: aether.v1.AdminResponse.connections:type_name -> aether.v1.ConnectionInfo
+ 3, // 139: aether.v1.HealthInfo.status:type_name -> aether.v1.HealthStatus
+ 210, // 140: aether.v1.HealthInfo.checks:type_name -> aether.v1.HealthInfo.ChecksEntry
+ 83, // 141: aether.v1.HealthInfo.stats:type_name -> aether.v1.GatewayStats
+ 4, // 142: aether.v1.HealthCheck.status:type_name -> aether.v1.HealthCheckStatus
+ 21, // 143: aether.v1.SessionOperation.op:type_name -> aether.v1.SessionOperation.OpType
+ 77, // 144: aether.v1.SessionOperation.filter:type_name -> aether.v1.ConnectionFilter
+ 54, // 145: aether.v1.SessionOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 78, // 146: aether.v1.SessionOperationResponse.connection:type_name -> aether.v1.ConnectionInfo
+ 78, // 147: aether.v1.SessionOperationResponse.connections:type_name -> aether.v1.ConnectionInfo
+ 22, // 148: aether.v1.TaskQuery.op:type_name -> aether.v1.TaskQuery.OpType
+ 87, // 149: aether.v1.TaskQuery.filter:type_name -> aether.v1.TaskFilter
+ 2, // 150: aether.v1.TaskFilter.status:type_name -> aether.v1.TaskStatus
+ 2, // 151: aether.v1.TaskFilter.statuses:type_name -> aether.v1.TaskStatus
+ 7, // 152: aether.v1.TaskFilter.task_class:type_name -> aether.v1.TaskClass
+ 7, // 153: aether.v1.TaskFilter.exclude_task_classes:type_name -> aether.v1.TaskClass
+ 2, // 154: aether.v1.TaskFilter.exclude_statuses:type_name -> aether.v1.TaskStatus
+ 53, // 155: aether.v1.TaskFilter.creator_actor:type_name -> aether.v1.PrincipalRef
+ 8, // 156: aether.v1.TaskFilter.priority:type_name -> aether.v1.TaskPriority
+ 8, // 157: aether.v1.TaskFilter.min_priority:type_name -> aether.v1.TaskPriority
+ 2, // 158: aether.v1.TaskInfo.status:type_name -> aether.v1.TaskStatus
+ 211, // 159: aether.v1.TaskInfo.metadata:type_name -> aether.v1.TaskInfo.MetadataEntry
+ 7, // 160: aether.v1.TaskInfo.task_class:type_name -> aether.v1.TaskClass
+ 91, // 161: aether.v1.TaskInfo.wait_spec:type_name -> aether.v1.WaitSpec
+ 8, // 162: aether.v1.TaskInfo.priority:type_name -> aether.v1.TaskPriority
+ 70, // 163: aether.v1.TaskInfo.completion_event:type_name -> aether.v1.TaskCompletionEvent
+ 88, // 164: aether.v1.TaskQueryResponse.task:type_name -> aether.v1.TaskInfo
+ 88, // 165: aether.v1.TaskQueryResponse.tasks:type_name -> aether.v1.TaskInfo
+ 23, // 166: aether.v1.TaskOperation.op:type_name -> aether.v1.TaskOperation.OpType
+ 91, // 167: aether.v1.TaskOperation.wait_spec:type_name -> aether.v1.WaitSpec
+ 11, // 168: aether.v1.WaitSpec.reason:type_name -> aether.v1.WaitReason
+ 212, // 169: aether.v1.WaitSpec.input_match:type_name -> aether.v1.WaitSpec.InputMatchEntry
+ 92, // 170: aether.v1.WaitSpec.hibernation:type_name -> aether.v1.HibernationDescriptor
+ 88, // 171: aether.v1.TaskOperationResponse.task:type_name -> aether.v1.TaskInfo
+ 24, // 172: aether.v1.WorkspaceOperation.op:type_name -> aether.v1.WorkspaceOperation.OpType
+ 95, // 173: aether.v1.WorkspaceOperation.filter:type_name -> aether.v1.WorkspaceFilter
+ 96, // 174: aether.v1.WorkspaceOperation.workspace:type_name -> aether.v1.WorkspaceInfo
+ 213, // 175: aether.v1.WorkspaceInfo.metadata:type_name -> aether.v1.WorkspaceInfo.MetadataEntry
+ 96, // 176: aether.v1.WorkspaceResponse.workspace:type_name -> aether.v1.WorkspaceInfo
+ 96, // 177: aether.v1.WorkspaceResponse.workspaces:type_name -> aether.v1.WorkspaceInfo
+ 98, // 178: aether.v1.WorkspaceResponse.message_flow:type_name -> aether.v1.MessageFlowInfo
+ 99, // 179: aether.v1.MessageFlowInfo.nodes:type_name -> aether.v1.FlowNode
+ 100, // 180: aether.v1.MessageFlowInfo.edges:type_name -> aether.v1.FlowEdge
+ 1, // 181: aether.v1.FlowNode.type:type_name -> aether.v1.PrincipalType
+ 25, // 182: aether.v1.AgentOperation.op:type_name -> aether.v1.AgentOperation.OpType
+ 102, // 183: aether.v1.AgentOperation.filter:type_name -> aether.v1.AgentFilter
+ 103, // 184: aether.v1.AgentOperation.agent:type_name -> aether.v1.AgentRegistrationInfo
+ 105, // 185: aether.v1.AgentOperation.launch_params:type_name -> aether.v1.AgentLaunchParams
+ 214, // 186: aether.v1.AgentRegistrationInfo.launch_params:type_name -> aether.v1.AgentRegistrationInfo.LaunchParamsEntry
+ 104, // 187: aether.v1.AgentRegistrationInfo.resource_schema:type_name -> aether.v1.AgentResourceSchemaEntry
+ 215, // 188: aether.v1.AgentRegistrationInfo.capabilities:type_name -> aether.v1.AgentRegistrationInfo.CapabilitiesEntry
+ 216, // 189: aether.v1.AgentLaunchParams.param_overrides:type_name -> aether.v1.AgentLaunchParams.ParamOverridesEntry
+ 103, // 190: aether.v1.AgentResponse.agent:type_name -> aether.v1.AgentRegistrationInfo
+ 103, // 191: aether.v1.AgentResponse.agents:type_name -> aether.v1.AgentRegistrationInfo
+ 106, // 192: aether.v1.AgentResponse.orchestrators:type_name -> aether.v1.OrchestratorInfo
+ 107, // 193: aether.v1.AgentResponse.launch_result:type_name -> aether.v1.AgentLaunchResult
+ 26, // 194: aether.v1.ACLOperation.op:type_name -> aether.v1.ACLOperation.OpType
+ 110, // 195: aether.v1.ACLOperation.rule_filter:type_name -> aether.v1.ACLRuleFilter
+ 111, // 196: aether.v1.ACLOperation.audit_filter:type_name -> aether.v1.ACLAuditFilter
+ 112, // 197: aether.v1.ACLOperation.grant_request:type_name -> aether.v1.ACLGrantRequest
+ 113, // 198: aether.v1.ACLOperation.fallback_request:type_name -> aether.v1.ACLSetFallbackRequest
+ 53, // 199: aether.v1.ACLOperation.principal:type_name -> aether.v1.PrincipalRef
+ 123, // 200: aether.v1.ACLOperation.group_request:type_name -> aether.v1.ACLGroupRequest
+ 124, // 201: aether.v1.ACLOperation.role_request:type_name -> aether.v1.ACLRoleRequest
+ 125, // 202: aether.v1.ACLOperation.member_request:type_name -> aether.v1.ACLGroupMemberRequest
+ 126, // 203: aether.v1.ACLOperation.assignment_request:type_name -> aether.v1.ACLRoleAssignmentRequest
+ 54, // 204: aether.v1.ACLOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 53, // 205: aether.v1.ACLAuthorityGrantRequest.subject:type_name -> aether.v1.PrincipalRef
+ 53, // 206: aether.v1.ACLAuthorityGrantRequest.delegate:type_name -> aether.v1.PrincipalRef
+ 53, // 207: aether.v1.ACLAuthorityGrantRequest.issued_by:type_name -> aether.v1.PrincipalRef
+ 53, // 208: aether.v1.ACLAuthorityGrantRequest.root_subject:type_name -> aether.v1.PrincipalRef
+ 115, // 209: aether.v1.ACLAuthorityGrantRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 217, // 210: aether.v1.ACLAuthorityGrantRequest.metadata:type_name -> aether.v1.ACLAuthorityGrantRequest.MetadataEntry
+ 218, // 211: aether.v1.ACLAuditEntryInfo.metadata:type_name -> aether.v1.ACLAuditEntryInfo.MetadataEntry
+ 53, // 212: aether.v1.ACLAuthorityGrantInfo.subject:type_name -> aether.v1.PrincipalRef
+ 53, // 213: aether.v1.ACLAuthorityGrantInfo.delegate:type_name -> aether.v1.PrincipalRef
+ 53, // 214: aether.v1.ACLAuthorityGrantInfo.issued_by:type_name -> aether.v1.PrincipalRef
+ 53, // 215: aether.v1.ACLAuthorityGrantInfo.root_subject:type_name -> aether.v1.PrincipalRef
+ 115, // 216: aether.v1.ACLAuthorityGrantInfo.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 219, // 217: aether.v1.ACLAuthorityGrantInfo.metadata:type_name -> aether.v1.ACLAuthorityGrantInfo.MetadataEntry
+ 220, // 218: aether.v1.ACLGroupRequest.metadata:type_name -> aether.v1.ACLGroupRequest.MetadataEntry
+ 221, // 219: aether.v1.ACLRoleRequest.metadata:type_name -> aether.v1.ACLRoleRequest.MetadataEntry
+ 222, // 220: aether.v1.ACLGroupInfo.metadata:type_name -> aether.v1.ACLGroupInfo.MetadataEntry
+ 223, // 221: aether.v1.ACLRoleInfo.metadata:type_name -> aether.v1.ACLRoleInfo.MetadataEntry
+ 131, // 222: aether.v1.ACLAccessExplanationInfo.contributions:type_name -> aether.v1.ACLAccessContributionInfo
+ 118, // 223: aether.v1.ACLResponse.rule:type_name -> aether.v1.ACLRuleInfo
+ 118, // 224: aether.v1.ACLResponse.rules:type_name -> aether.v1.ACLRuleInfo
+ 119, // 225: aether.v1.ACLResponse.fallback_policy:type_name -> aether.v1.ACLFallbackPolicyInfo
+ 120, // 226: aether.v1.ACLResponse.audit_entries:type_name -> aether.v1.ACLAuditEntryInfo
+ 122, // 227: aether.v1.ACLResponse.cleanup_result:type_name -> aether.v1.ACLCleanupResult
+ 121, // 228: aether.v1.ACLResponse.authority_grant:type_name -> aether.v1.ACLAuthorityGrantInfo
+ 121, // 229: aether.v1.ACLResponse.authority_grants:type_name -> aether.v1.ACLAuthorityGrantInfo
+ 127, // 230: aether.v1.ACLResponse.group:type_name -> aether.v1.ACLGroupInfo
+ 127, // 231: aether.v1.ACLResponse.groups:type_name -> aether.v1.ACLGroupInfo
+ 128, // 232: aether.v1.ACLResponse.role:type_name -> aether.v1.ACLRoleInfo
+ 128, // 233: aether.v1.ACLResponse.roles:type_name -> aether.v1.ACLRoleInfo
+ 129, // 234: aether.v1.ACLResponse.group_members:type_name -> aether.v1.ACLGroupMemberInfo
+ 130, // 235: aether.v1.ACLResponse.role_assignments:type_name -> aether.v1.ACLRoleAssignmentInfo
+ 132, // 236: aether.v1.ACLResponse.explanation:type_name -> aether.v1.ACLAccessExplanationInfo
+ 27, // 237: aether.v1.AuthorityGrantOperation.op:type_name -> aether.v1.AuthorityGrantOperation.OpType
+ 135, // 238: aether.v1.AuthorityGrantOperation.exchange_request:type_name -> aether.v1.AuthorityGrantExchangeRequest
+ 136, // 239: aether.v1.AuthorityGrantOperation.derive_request:type_name -> aether.v1.AuthorityGrantDeriveRequest
+ 117, // 240: aether.v1.AuthorityGrantOperation.renew_request:type_name -> aether.v1.ACLRenewAuthorityGrantRequest
+ 138, // 241: aether.v1.AuthorityGrantOperation.list_request:type_name -> aether.v1.AuthorityGrantListRequest
+ 139, // 242: aether.v1.AuthorityGrantOperation.batch_exchange_request:type_name -> aether.v1.AuthorityGrantBatchExchangeRequest
+ 140, // 243: aether.v1.AuthorityGrantOperation.derive_for_target_request:type_name -> aether.v1.AuthorityGrantDeriveForTargetRequest
+ 115, // 244: aether.v1.AuthorityGrantExchangeRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 224, // 245: aether.v1.AuthorityGrantExchangeRequest.metadata:type_name -> aether.v1.AuthorityGrantExchangeRequest.MetadataEntry
+ 53, // 246: aether.v1.AuthorityGrantDeriveRequest.delegate:type_name -> aether.v1.PrincipalRef
+ 115, // 247: aether.v1.AuthorityGrantDeriveRequest.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 225, // 248: aether.v1.AuthorityGrantDeriveRequest.metadata:type_name -> aether.v1.AuthorityGrantDeriveRequest.MetadataEntry
+ 121, // 249: aether.v1.AuthorityGrantResponse.grant:type_name -> aether.v1.ACLAuthorityGrantInfo
+ 121, // 250: aether.v1.AuthorityGrantResponse.grants:type_name -> aether.v1.ACLAuthorityGrantInfo
+ 135, // 251: aether.v1.AuthorityGrantBatchExchangeRequest.requests:type_name -> aether.v1.AuthorityGrantExchangeRequest
+ 53, // 252: aether.v1.AuthorityGrantDeriveForTargetRequest.target:type_name -> aether.v1.PrincipalRef
+ 53, // 253: aether.v1.AuthorityIdentity.subject:type_name -> aether.v1.PrincipalRef
+ 53, // 254: aether.v1.AuthorityIdentity.root_subject:type_name -> aether.v1.PrincipalRef
+ 53, // 255: aether.v1.AuthorityIdentity.delegate:type_name -> aether.v1.PrincipalRef
+ 53, // 256: aether.v1.AuthorityIdentity.issued_by:type_name -> aether.v1.PrincipalRef
+ 53, // 257: aether.v1.AuthorityRequestRoutingTarget.principal:type_name -> aether.v1.PrincipalRef
+ 12, // 258: aether.v1.AuthorityRequest.status:type_name -> aether.v1.AuthorityRequestStatus
+ 53, // 259: aether.v1.AuthorityRequest.requesting_actor:type_name -> aether.v1.PrincipalRef
+ 53, // 260: aether.v1.AuthorityRequest.target_subject:type_name -> aether.v1.PrincipalRef
+ 145, // 261: aether.v1.AuthorityRequest.desired_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
+ 5, // 262: aether.v1.AuthorityRequest.requested_access_level:type_name -> aether.v1.AccessLevel
+ 144, // 263: aether.v1.AuthorityRequest.routing_target:type_name -> aether.v1.AuthorityRequestRoutingTarget
+ 226, // 264: aether.v1.AuthorityRequest.metadata:type_name -> aether.v1.AuthorityRequest.MetadataEntry
+ 53, // 265: aether.v1.AuthorityRequest.resolved_by:type_name -> aether.v1.PrincipalRef
+ 53, // 266: aether.v1.CreateAuthorityRequestPayload.requesting_actor:type_name -> aether.v1.PrincipalRef
+ 53, // 267: aether.v1.CreateAuthorityRequestPayload.target_subject:type_name -> aether.v1.PrincipalRef
+ 145, // 268: aether.v1.CreateAuthorityRequestPayload.desired_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
+ 5, // 269: aether.v1.CreateAuthorityRequestPayload.requested_access_level:type_name -> aether.v1.AccessLevel
+ 144, // 270: aether.v1.CreateAuthorityRequestPayload.routing_target:type_name -> aether.v1.AuthorityRequestRoutingTarget
+ 227, // 271: aether.v1.CreateAuthorityRequestPayload.metadata:type_name -> aether.v1.CreateAuthorityRequestPayload.MetadataEntry
+ 28, // 272: aether.v1.ResolveAuthorityRequestPayload.decision:type_name -> aether.v1.ResolveAuthorityRequestPayload.Decision
+ 145, // 273: aether.v1.ResolveAuthorityRequestPayload.granted_resource_scope:type_name -> aether.v1.AuthorityRequestResourceScopeEntry
+ 5, // 274: aether.v1.ResolveAuthorityRequestPayload.granted_access_level:type_name -> aether.v1.AccessLevel
+ 12, // 275: aether.v1.AuthorityRequestListFilter.status:type_name -> aether.v1.AuthorityRequestStatus
+ 29, // 276: aether.v1.AuthorityRequestOperation.op:type_name -> aether.v1.AuthorityRequestOperation.OpType
+ 147, // 277: aether.v1.AuthorityRequestOperation.create:type_name -> aether.v1.CreateAuthorityRequestPayload
+ 148, // 278: aether.v1.AuthorityRequestOperation.resolve:type_name -> aether.v1.ResolveAuthorityRequestPayload
+ 149, // 279: aether.v1.AuthorityRequestOperation.list_filter:type_name -> aether.v1.AuthorityRequestListFilter
+ 146, // 280: aether.v1.AuthorityRequestOperationResponse.request:type_name -> aether.v1.AuthorityRequest
+ 146, // 281: aether.v1.AuthorityRequestOperationResponse.requests:type_name -> aether.v1.AuthorityRequest
+ 30, // 282: aether.v1.AuthorityRequestEvent.event_type:type_name -> aether.v1.AuthorityRequestEvent.EventType
+ 146, // 283: aether.v1.AuthorityRequestEvent.request:type_name -> aether.v1.AuthorityRequest
+ 31, // 284: aether.v1.TokenOperation.op:type_name -> aether.v1.TokenOperation.OpType
+ 154, // 285: aether.v1.TokenOperation.create_request:type_name -> aether.v1.TokenCreateRequest
+ 155, // 286: aether.v1.TokenOperation.filter:type_name -> aether.v1.TokenFilter
+ 156, // 287: aether.v1.TokenResponse.token:type_name -> aether.v1.TokenInfo
+ 156, // 288: aether.v1.TokenResponse.tokens:type_name -> aether.v1.TokenInfo
+ 156, // 289: aether.v1.TokenResponse.created_token:type_name -> aether.v1.TokenInfo
+ 159, // 290: aether.v1.ProgressReport.step:type_name -> aether.v1.ProgressStep
+ 228, // 291: aether.v1.ProgressReport.metadata:type_name -> aether.v1.ProgressReport.MetadataEntry
+ 13, // 292: aether.v1.ProgressReport.kind:type_name -> aether.v1.ProgressKind
+ 159, // 293: aether.v1.ProgressUpdate.step:type_name -> aether.v1.ProgressStep
+ 229, // 294: aether.v1.ProgressUpdate.metadata:type_name -> aether.v1.ProgressUpdate.MetadataEntry
+ 13, // 295: aether.v1.ProgressUpdate.kind:type_name -> aether.v1.ProgressKind
+ 115, // 296: aether.v1.WorkflowScheduleAuthorityScope.resource_scope:type_name -> aether.v1.ACLAuthorityGrantResourceScopeEntry
+ 14, // 297: aether.v1.WorkflowScheduleAuthorityScope.lifetime_mode:type_name -> aether.v1.WorkflowAuthorityLifetimeMode
+ 53, // 298: aether.v1.WorkflowRequestContext.actor:type_name -> aether.v1.PrincipalRef
+ 53, // 299: aether.v1.WorkflowRequestContext.subject:type_name -> aether.v1.PrincipalRef
+ 54, // 300: aether.v1.WorkflowRequestContext.schedule_authorization:type_name -> aether.v1.AuthorizationContext
+ 14, // 301: aether.v1.WorkflowRequestContext.lifetime_mode:type_name -> aether.v1.WorkflowAuthorityLifetimeMode
+ 32, // 302: aether.v1.WorkflowOperation.op:type_name -> aether.v1.WorkflowOperation.OpType
+ 54, // 303: aether.v1.WorkflowOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 161, // 304: aether.v1.WorkflowOperation.schedule_authority_scope:type_name -> aether.v1.WorkflowScheduleAuthorityScope
+ 162, // 305: aether.v1.WorkflowOperation.request_context:type_name -> aether.v1.WorkflowRequestContext
+ 0, // 306: aether.v1.MessageEnvelope.message_type:type_name -> aether.v1.MessageType
+ 230, // 307: aether.v1.MessageEnvelope.metadata:type_name -> aether.v1.MessageEnvelope.MetadataEntry
+ 53, // 308: aether.v1.MessageEnvelope.on_behalf_subject:type_name -> aether.v1.PrincipalRef
+ 193, // 309: aether.v1.MessageEnvelope.access_receipt:type_name -> aether.v1.AccessDecisionReceipt
+ 65, // 310: aether.v1.MessageEnvelope.forwarded_authorization:type_name -> aether.v1.ForwardedAuthorization
+ 54, // 311: aether.v1.AuditQuery.authorization:type_name -> aether.v1.AuthorizationContext
+ 168, // 312: aether.v1.AuditQueryResponse.entries:type_name -> aether.v1.AuditEntry
+ 231, // 313: aether.v1.SubmitAuditEventRequest.metadata:type_name -> aether.v1.SubmitAuditEventRequest.MetadataEntry
+ 232, // 314: aether.v1.ProxyHttpRequest.headers:type_name -> aether.v1.ProxyHttpRequest.HeadersEntry
+ 54, // 315: aether.v1.ProxyHttpRequest.authorization:type_name -> aether.v1.AuthorizationContext
+ 192, // 316: aether.v1.ProxyHttpRequest.checked_access:type_name -> aether.v1.ResourceAccessRequest
+ 193, // 317: aether.v1.ProxyHttpRequest.access_receipt:type_name -> aether.v1.AccessDecisionReceipt
+ 233, // 318: aether.v1.ProxyHttpResponse.headers:type_name -> aether.v1.ProxyHttpResponse.HeadersEntry
+ 174, // 319: aether.v1.ProxyHttpResponse.error:type_name -> aether.v1.ProxyError
+ 33, // 320: aether.v1.ProxyError.kind:type_name -> aether.v1.ProxyError.Kind
+ 34, // 321: aether.v1.TunnelOpen.protocol:type_name -> aether.v1.TunnelOpen.Protocol
+ 234, // 322: aether.v1.TunnelOpen.metadata:type_name -> aether.v1.TunnelOpen.MetadataEntry
+ 54, // 323: aether.v1.TunnelOpen.authorization:type_name -> aether.v1.AuthorizationContext
+ 35, // 324: aether.v1.TunnelClose.reason:type_name -> aether.v1.TunnelClose.Reason
+ 53, // 325: aether.v1.ResolveAuthorityRequest.actor:type_name -> aether.v1.PrincipalRef
+ 53, // 326: aether.v1.ResolveAuthorityRequest.subject:type_name -> aether.v1.PrincipalRef
+ 181, // 327: aether.v1.ResolveAuthorityResponse.authority:type_name -> aether.v1.ResolvedAuthority
+ 53, // 328: aether.v1.ResolvedAuthority.actor:type_name -> aether.v1.PrincipalRef
+ 53, // 329: aether.v1.ResolvedAuthority.subject:type_name -> aether.v1.PrincipalRef
+ 182, // 330: aether.v1.ResolvedAuthority.grant:type_name -> aether.v1.AuthorityGrantInfo
+ 53, // 331: aether.v1.ConnectionStatusRequest.principal:type_name -> aether.v1.PrincipalRef
+ 36, // 332: aether.v1.TaskSubscriptionOperation.op:type_name -> aether.v1.TaskSubscriptionOperation.OpType
+ 188, // 333: aether.v1.TaskEvent.status_changed:type_name -> aether.v1.TaskStatusChangedEvent
+ 189, // 334: aether.v1.TaskEvent.progress:type_name -> aether.v1.TaskProgressEvent
+ 190, // 335: aether.v1.TaskEvent.child_lifecycle:type_name -> aether.v1.TaskChildLifecycleEvent
+ 191, // 336: aether.v1.TaskEvent.authority_request:type_name -> aether.v1.TaskAuthorityRequestEventRelay
+ 2, // 337: aether.v1.TaskStatusChangedEvent.from_status:type_name -> aether.v1.TaskStatus
+ 2, // 338: aether.v1.TaskStatusChangedEvent.to_status:type_name -> aether.v1.TaskStatus
+ 235, // 339: aether.v1.TaskProgressEvent.metadata:type_name -> aether.v1.TaskProgressEvent.MetadataEntry
+ 2, // 340: aether.v1.TaskChildLifecycleEvent.child_status:type_name -> aether.v1.TaskStatus
+ 152, // 341: aether.v1.TaskAuthorityRequestEventRelay.event:type_name -> aether.v1.AuthorityRequestEvent
+ 192, // 342: aether.v1.AccessDecisionReceipt.request:type_name -> aether.v1.ResourceAccessRequest
+ 53, // 343: aether.v1.AccessDecisionReceipt.actor:type_name -> aether.v1.PrincipalRef
+ 53, // 344: aether.v1.AccessDecisionReceipt.subject:type_name -> aether.v1.PrincipalRef
+ 53, // 345: aether.v1.AccessDecisionReceipt.root_subject:type_name -> aether.v1.PrincipalRef
+ 192, // 346: aether.v1.AccessCheckOperation.access:type_name -> aether.v1.ResourceAccessRequest
+ 54, // 347: aether.v1.AccessCheckOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 193, // 348: aether.v1.AccessCheckResponse.decision:type_name -> aether.v1.AccessDecisionReceipt
+ 192, // 349: aether.v1.BatchAccessCheckOperation.access:type_name -> aether.v1.ResourceAccessRequest
+ 54, // 350: aether.v1.BatchAccessCheckOperation.authorization:type_name -> aether.v1.AuthorizationContext
+ 193, // 351: aether.v1.BatchAccessCheckResponse.decisions:type_name -> aether.v1.AccessDecisionReceipt
+ 81, // 352: aether.v1.HealthInfo.ChecksEntry.value:type_name -> aether.v1.HealthCheck
+ 37, // 353: aether.v1.AetherGateway.Connect:input_type -> aether.v1.UpstreamMessage
+ 38, // 354: aether.v1.AetherGateway.Connect:output_type -> aether.v1.DownstreamMessage
+ 354, // [354:355] is the sub-list for method output_type
+ 353, // [353:354] is the sub-list for method input_type
+ 353, // [353:353] is the sub-list for extension type_name
+ 353, // [353:353] is the sub-list for extension extendee
+ 0, // [0:353] is the sub-list for field type_name
}
func init() { file_aether_proto_init() }
@@ -20794,6 +22352,8 @@ func file_aether_proto_init() {
(*UpstreamMessage_SubmitAuditEvent)(nil),
(*UpstreamMessage_AuthorityRequestOp)(nil),
(*UpstreamMessage_TaskSubscriptionOp)(nil),
+ (*UpstreamMessage_AccessCheck)(nil),
+ (*UpstreamMessage_BatchAccessCheck)(nil),
}
file_aether_proto_msgTypes[1].OneofWrappers = []any{
(*DownstreamMessage_Msg)(nil),
@@ -20833,6 +22393,8 @@ func file_aether_proto_init() {
(*DownstreamMessage_TaskHibernated)(nil),
(*DownstreamMessage_TaskSubscriptionResponse)(nil),
(*DownstreamMessage_TaskEvent)(nil),
+ (*DownstreamMessage_AccessCheckResponse)(nil),
+ (*DownstreamMessage_BatchAccessCheckResponse)(nil),
}
file_aether_proto_msgTypes[4].OneofWrappers = []any{
(*InitConnection_Agent)(nil),
@@ -20844,7 +22406,7 @@ func file_aether_proto_init() {
(*InitConnection_Bridge)(nil),
(*InitConnection_Service)(nil),
}
- file_aether_proto_msgTypes[145].OneofWrappers = []any{
+ file_aether_proto_msgTypes[150].OneofWrappers = []any{
(*TaskEvent_StatusChanged)(nil),
(*TaskEvent_Progress)(nil),
(*TaskEvent_ChildLifecycle)(nil),
@@ -20855,8 +22417,8 @@ func file_aether_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_aether_proto_rawDesc), len(file_aether_proto_rawDesc)),
- NumEnums: 34,
- NumMessages: 188,
+ NumEnums: 37,
+ NumMessages: 199,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/api/proto/aether.proto b/api/proto/aether.proto
index efd4ac0..28541b6 100644
--- a/api/proto/aether.proto
+++ b/api/proto/aether.proto
@@ -44,6 +44,8 @@ message UpstreamMessage {
SubmitAuditEventRequest submit_audit_event = 29;
AuthorityRequestOperation authority_request_op = 30;
TaskSubscriptionOperation task_subscription_op = 31;
+ AccessCheckOperation access_check = 33;
+ BatchAccessCheckOperation batch_access_check = 34;
}
// Phase 6: URIs of extensions active on this specific message. When the
// receiver does not support a URI listed here and the extension was
@@ -94,6 +96,8 @@ message DownstreamMessage {
TaskHibernated task_hibernated = 35;
TaskSubscriptionOperationResponse task_subscription_response = 36;
TaskEvent task_event = 37;
+ AccessCheckResponse access_check_response = 39;
+ BatchAccessCheckResponse batch_access_check_response = 40;
}
// Phase 6: URIs of extensions active on this specific message. Same
// semantics as UpstreamMessage.active_extensions: receivers reject when
@@ -357,6 +361,49 @@ message SendMessage {
// secondary once authproxy-issued root grants are used as the primary
// scope source.
string app_workspace = 5;
+ // Optional exact logical-resource check evaluated in addition to ordinary
+ // topic-route authorization. On allow, the resulting receipt is attached to
+ // the trusted MessageEnvelope/IncomingMessage metadata; on deny, nothing is
+ // published. Existing sends without this field retain their current path.
+ ResourceAccessRequest checked_access = 6;
+
+ // Explicitly request a gateway-derived, short-lived authorization context
+ // for the resolved recipient. The gateway only honors this when the send is
+ // already operating under a validated OBO grant with delegation capacity.
+ // For sv::{implementation} targets, wildcard resolution happens first and
+ // the child grant is bound to the concrete service instance. Exact agent
+ // targets require an invocation-bound, explicitly attenuated scope. The
+ // recipient receives the result in IncomingMessage.forwarded_authorization;
+ // payload data can never populate that trusted field.
+ AuthorityContinuationRequest authority_continuation = 7;
+}
+
+// Explicit scope ceiling for a derived message authority continuation. Empty
+// axes retain the AuthorityGrant meaning of unrestricted, so an attenuated
+// agent continuation requires every axis to be populated and validated.
+message AuthorityContinuationScope {
+ repeated string workspace_scope = 1;
+ repeated ACLAuthorityGrantResourceScopeEntry resource_scope = 2;
+ repeated string operation_scope = 3;
+ int32 max_access_level = 4;
+}
+
+message AuthorityContinuationRequest {
+ enum ScopeMode {
+ SCOPE_MODE_UNSPECIFIED = 0;
+ // Service-only mode used when a trusted service must evaluate arbitrary
+ // resources within the caller's existing authority ceiling.
+ SCOPE_MODE_INHERIT_PARENT = 1;
+ // Required for agent recipients. The requested scope is validated as a
+ // strict subset of the parent and the child is minted per invocation.
+ SCOPE_MODE_ATTENUATE = 2;
+ }
+ ScopeMode scope_mode = 1;
+ // Opaque invocation identifier. Required for ATTENUATE and matched to the
+ // checked-access correlation ID so the trusted receipt, child, and payload
+ // can be validated as one call by the recipient.
+ string binding_id = 2;
+ AuthorityContinuationScope scope = 3;
}
enum MessageType {
@@ -605,6 +652,31 @@ message IncomingMessage {
// for, distinct from the sending identity in source_topic. Empty for direct
// (non-OBO) sends. See MessageEnvelope.on_behalf_subject.
PrincipalRef on_behalf_subject = 5;
+ // Gateway-authored receipt from SendMessage.checked_access. Never populated
+ // from the application payload.
+ AccessDecisionReceipt access_receipt = 6;
+
+ // Gateway-derived authority continuation for this exact delivery target.
+ // Populated only when SendMessage.authority_continuation was explicitly set
+ // and the sender's resolved grant could delegate. Recipients can pass the
+ // authorization context to CheckAccess / BatchCheckAccess; root_grant_id,
+ // expiry, and delivery_target are trusted binding/audit metadata.
+ ForwardedAuthorization forwarded_authorization = 7;
+}
+
+// Trusted authorization continuation carried outside the application payload.
+// The child grant is non-delegable, scope-attenuated to its parent, short-lived,
+// and linked into the parent's revocation cascade.
+message ForwardedAuthorization {
+ AuthorizationContext authorization = 1;
+ string root_grant_id = 2;
+ int64 expires_at_ms = 3;
+ string delivery_target = 4;
+ // Empty only for a reusable service continuation using INHERIT_PARENT.
+ string binding_id = 5;
+ // Gateway-authored projection of the effective child scope. Recipients use
+ // this to enforce their local, server-owned invocation authority profile.
+ AuthorityContinuationScope scope = 6;
}
message ConfigSnapshot {
@@ -739,6 +811,16 @@ message TaskCompletionEvent {
repeated TaskStatus on_statuses = 3;
}
+// Controls what TARGETED task creation does when the exact target identity is
+// not connected. UNSPECIFIED deliberately preserves the released behavior:
+// validate the implementation and ask an orchestrator to start the worker.
+enum TargetOfflinePolicy {
+ TARGET_OFFLINE_POLICY_UNSPECIFIED = 0;
+ TARGET_OFFLINE_POLICY_ORCHESTRATE = 1;
+ TARGET_OFFLINE_POLICY_QUEUE = 2;
+ TARGET_OFFLINE_POLICY_REJECT = 3;
+}
+
message CreateTaskRequest {
string task_type = 1;
string workspace = 2;
@@ -816,6 +898,31 @@ message CreateTaskRequest {
// Optional "feed B" config: emit a domain event onto event::* when this task
// reaches a (selected) terminal status. Absent/disabled = no emission.
TaskCompletionEvent completion_event = 19;
+
+ // Optional native parent for a nested task created by a long-lived worker.
+ // The gateway accepts an explicit value only when the caller is the active
+ // parent task's assigned execution identity. This is a request-scoped binding:
+ // it may select a different assigned task than the connection's startup/task-
+ // token association. Empty preserves connection-associated parent inference.
+ string parent_task_id = 20;
+
+ // TARGETED mode only. QUEUE persists the task for delivery when the exact
+ // static worker reconnects, without requiring an orchestration registry
+ // entry. REJECT fails task creation while the worker is absent.
+ TargetOfflinePolicy target_offline_policy = 21;
+
+ // Minimum delegation capacity the task's final execution identity must
+ // retain after task-authority setup. Currently 0 or 1. Set to 1 when the
+ // worker must perform one explicit downstream authorization continuation
+ // (for example, Sahara querying the tool catalog under the user's authority).
+ // In POOL mode the gateway reserves the additional anchor-to-assignee hop.
+ uint32 required_downstream_authority_hops = 22;
+
+ // WorkflowEngine-only authority audience binding. The gateway accepts this
+ // field only from the authenticated WorkflowEngine principal and requires it
+ // to match a workflow_schedule audience on authorization. Ordinary task
+ // creators must leave it empty.
+ string originating_schedule_id = 23;
}
// CreateTaskResponse is sent in response to CreateTaskRequest when the
@@ -886,6 +993,12 @@ message TaskAssignment {
// Hibernation rehydration: session id to resume. Empty = fresh session.
string resume_session_id = 14;
+
+ // Task-scoped on-behalf-of authority prepared for the assigned executor.
+ // The grant is audience-bound to this assignee/task and is revoked with the
+ // task lifecycle. It is delivered on the typed execution plane rather than
+ // requiring workers to parse server-enriched metadata.
+ AuthorizationContext authorization = 15;
}
// ============================================================================
@@ -2225,6 +2338,10 @@ message AuthorityGrantOperation {
// For DERIVE_FOR_TARGET
AuthorityGrantDeriveForTargetRequest derive_for_target_request = 9;
+
+ // WorkflowEngine-only audience context for GET/REVOKE of a
+ // workflow_schedule grant. Ignored for other actors and operations.
+ string workflow_schedule_id = 10;
}
// AuthorityGrantExchangeRequest bootstraps a grant for the current actor.
@@ -2781,6 +2898,48 @@ message ProgressUpdate {
// Workflow Management Messages
// ============================================================================
+enum WorkflowAuthorityLifetimeMode {
+ WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND = 0;
+ WORKFLOW_AUTHORITY_LIFETIME_DURABLE = 1;
+}
+
+// Requested ceiling for the private authority attached to one schedule. The
+// gateway validates/attenuates this against the authenticated caller context;
+// the WorkflowEngine never trusts it directly and never stores it in action
+// JSON. Empty resource or operation scope is invalid.
+message WorkflowScheduleAuthorityScope {
+ repeated string workspace_scope = 1;
+ repeated ACLAuthorityGrantResourceScopeEntry resource_scope = 2;
+ repeated string operation_scope = 3;
+ int32 max_access_level = 4;
+ int64 expires_at = 5;
+ int64 renewable_until = 6;
+ uint32 required_task_authority_hops = 7;
+ WorkflowAuthorityLifetimeMode lifetime_mode = 8;
+ // Version of the deterministic schedule-authority policy shape. Callers
+ // currently send 1; unknown versions fail closed instead of being silently
+ // reinterpreted after an upgrade.
+ uint32 policy_version = 9;
+}
+
+// Gateway-authored workflow request identity and schedule authority. The
+// gateway clears any client-supplied value before forwarding. Schedule grant
+// IDs remain outside action JSON, task payload/metadata, and workflow response
+// data. Consumers must treat this object as trusted only on the authenticated
+// WorkflowEngine connection from the gateway.
+message WorkflowRequestContext {
+ PrincipalRef actor = 1;
+ PrincipalRef subject = 2;
+ string actor_session_id = 3;
+ AuthorizationContext schedule_authorization = 4;
+ string root_grant_id = 5;
+ string source_grant_id = 6;
+ int64 expires_at_ms = 7;
+ string policy_digest = 8;
+ WorkflowAuthorityLifetimeMode lifetime_mode = 9;
+ uint32 policy_version = 10;
+}
+
// WorkflowOperation allows clients to manage workflow rules, definitions,
// schedules, executions, and state machines through the gRPC streaming interface.
// Operations are forwarded by the gateway to the connected workflow engine and
@@ -2830,6 +2989,18 @@ message WorkflowOperation {
bytes data = 5; // JSON payload for CREATE/UPDATE ops
string request_id = 6; // Correlation ID
string status_filter = 7; // For LIST_EXECUTIONS
+
+ // Optional caller OBO authority. Resolved by the gateway before the request
+ // is authorized and forwarded.
+ AuthorizationContext authorization = 8;
+
+ // Optional requested authority for CREATE_SCHEDULE / UPSERT_SCHEDULE. The
+ // gateway derives or mints the exact WorkflowEngine schedule grant and
+ // forwards only the resulting trusted request_context.
+ WorkflowScheduleAuthorityScope schedule_authority_scope = 9;
+
+ // Gateway-authored; caller values are always discarded.
+ WorkflowRequestContext request_context = 10;
}
// WorkflowResponse is sent in response to WorkflowOperation.
@@ -2896,6 +3067,12 @@ message MessageEnvelope {
// need to *act for* the subject use the task authority-grant path
// (CreateTaskResponse.authority_grant_id), not this field.
PrincipalRef on_behalf_subject = 7;
+ // Gateway-authored exact-resource decision propagated to the recipient.
+ AccessDecisionReceipt access_receipt = 8;
+
+ // Gateway-authored authority continuation. This internal envelope field is
+ // copied to IncomingMessage and is never accepted from application payloads.
+ ForwardedAuthorization forwarded_authorization = 9;
}
// =========================================================================
@@ -3040,6 +3217,14 @@ message ProxyHttpRequest {
// upward by the sidecar relay floor (sandboxes can bump higher but never
// lower than the inbound chain depth they observed).
uint32 proxy_chain_depth = 16;
+ // Optional exact logical-resource authorization evaluated by the gateway
+ // after route authorization and wildcard target resolution. A denied or
+ // unavailable check prevents delivery to the terminator.
+ ResourceAccessRequest checked_access = 17;
+ // Gateway-authored result of checked_access. The gateway always clears any
+ // caller-supplied value before evaluation; terminators must trust this only
+ // as transport metadata on the delivered envelope.
+ AccessDecisionReceipt access_receipt = 18;
}
// ProxyHttpResponse is sent in reply to a ProxyHttpRequest. Errors are
@@ -3318,3 +3503,72 @@ message TaskChildLifecycleEvent {
message TaskAuthorityRequestEventRelay {
AuthorityRequestEvent event = 1;
}
+
+// =============================================================================
+// Portable runtime logical-resource authorization
+// =============================================================================
+
+// ResourceAccessRequest is the portable runtime authorization tuple evaluated
+// by the gateway. It is intentionally independent of any tool protocol: the
+// same primitive gates workspace views, catalog providers/entries, and future
+// logical resources. All string fields are required except workspace.
+message ResourceAccessRequest {
+ string resource_type = 1;
+ string resource_id = 2;
+ string operation = 3;
+ string workspace = 4;
+ int32 required_access_level = 5;
+ // Caller-generated correlation binding for a single logical action. A
+ // recipient compares this value with its application payload/request.
+ string correlation_id = 6;
+}
+
+// AccessDecisionReceipt is gateway-authored transport metadata. Receivers may
+// trust it only when it arrived in the Aether envelope, never when an
+// equivalent object appears inside an application payload.
+message AccessDecisionReceipt {
+ string decision_id = 1;
+ ResourceAccessRequest request = 2;
+ bool allowed = 3;
+ string decision = 4; // "ALLOW" or "DENY"
+ int32 effective_access_level = 5;
+ PrincipalRef actor = 6; // authenticated connected principal
+ PrincipalRef subject = 7; // populated for on-behalf-of checks
+ PrincipalRef root_subject = 8; // populated when the grant records one
+ string authority_mode = 9; // "direct" or "on_behalf_of"
+ string grant_id = 10;
+ string root_grant_id = 11;
+ int64 evaluated_at_ms = 12;
+ int64 expires_at_ms = 13;
+ string denial_code = 14; // stable code; empty for allowed checks
+ // Populated for checked SendMessage and ProxyHTTP delivery. This binds the
+ // receipt to the concrete post-wildcard-resolution target that received it.
+ string delivery_target = 15;
+}
+
+message AccessCheckOperation {
+ string request_id = 1;
+ ResourceAccessRequest access = 2;
+ AuthorizationContext authorization = 3;
+}
+
+message AccessCheckResponse {
+ string request_id = 1;
+ bool success = 2; // evaluation completed; denial is success
+ string error = 3;
+ AccessDecisionReceipt decision = 4;
+}
+
+message BatchAccessCheckOperation {
+ string request_id = 1;
+ repeated ResourceAccessRequest access = 2;
+ AuthorizationContext authorization = 3;
+}
+
+message BatchAccessCheckResponse {
+ string request_id = 1;
+ bool success = 2;
+ string error = 3;
+ // Same order and cardinality as BatchAccessCheckOperation.access.
+ repeated AccessDecisionReceipt decisions = 4;
+}
diff --git a/docs/aetherlite.md b/docs/aetherlite.md
index 326b03b..7fa5eaf 100644
--- a/docs/aetherlite.md
+++ b/docs/aetherlite.md
@@ -2,6 +2,12 @@
AetherLite is a deployment mode for Aether that replaces all external services with embedded in-process alternatives. There is no Redis, no RabbitMQ, and no PostgreSQL to install or manage. Everything runs inside a single process backed by [Badger](https://github.com/dgraph-io/badger) (KV and messaging) and [SQLite](https://sqlite.org) (relational data).
+Scheduled task actions can optionally retain private, bounded OBO authority;
+see [Workflow schedule authority](workflow-schedule-authority.md). Production
+mode requires explicit `workflow/schedule` ACL grants. `--dev` enables the
+permissive user and agent fallbacks needed for local clients and worker-owned
+schedule reconciliation.
+
## When to Use AetherLite
| Scenario | AetherLite | Full Aether |
@@ -55,6 +61,28 @@ AETHER_ALLOW_DEV_MODE=true ./aetherlite --data-dir /var/lib/aether-lite --insecu
> `--lite` (or `mode: lite`) is set. Use `cmd/aetherlite` for embedded
> single-binary deployments.
+### Option 2: Container images
+
+The normal image selects the `aetherlite` entrypoint but does not weaken its
+security defaults. Supply production configuration and secrets explicitly:
+
+```bash
+docker run --rm ghcr.io/scitrera/aetherlite:latest --help
+```
+
+For loopback-only local development, the `dev-*` tags additionally set
+`AETHER_ALLOW_DEV_MODE=true`, `AETHER_DEV=true`, and
+`AETHER_INSECURE_ADMIN=true`:
+
+```bash
+docker run --rm -p 127.0.0.1:50051:50051 \
+ -p 127.0.0.1:31880:31880 \
+ ghcr.io/scitrera/aetherlite:dev-latest
+```
+
+The development tags deliberately enable unauthenticated administration and
+must not be exposed to an untrusted network or used in production.
+
## Data Directory Layout
AetherLite stores all persistent state under a single directory:
diff --git a/docs/agent-acl-integration.md b/docs/agent-acl-integration.md
index adcdaec..32defec 100644
--- a/docs/agent-acl-integration.md
+++ b/docs/agent-acl-integration.md
@@ -370,6 +370,8 @@ behavior.
## See also
+- [runtime-access-checks.md](runtime-access-checks.md) — portable streaming
+ access checks, ordered batches, checked sends, and trusted decision receipts.
- [aetherlite.md](aetherlite.md) — single-binary deployment mode; all ACL and
audit features described here are fully supported in AetherLite.
- [on-behalf-of-acl-design.md](on-behalf-of-acl-design.md) — authority grant
diff --git a/docs/runtime-access-checks.md b/docs/runtime-access-checks.md
new file mode 100644
index 0000000..70f2404
--- /dev/null
+++ b/docs/runtime-access-checks.md
@@ -0,0 +1,181 @@
+# Runtime logical-resource access checks
+
+Aether's streaming API can authorize exact logical resources without turning
+every resource protocol into an Aether-specific payload. The same primitive can
+gate a workspace execution view, a tool-catalog provider, a provider-qualified
+tool entry, or another application-defined resource.
+
+This is enforcement, not the administrative `EXPLAIN_ACCESS` operation.
+Every evaluation uses the ordinary ACL engine, emits the ordinary ACL audit
+decision, and returns a short-lived gateway-authored receipt.
+
+## Resource request
+
+`ResourceAccessRequest` contains:
+
+| Field | Meaning |
+|---|---|
+| `resource_type` | ACL resource family |
+| `resource_id` | Exact stable logical resource ID |
+| `operation` | Requested verb |
+| `workspace` | Optional logical workspace scope |
+| `required_access_level` | One of `10`, `20`, `30`, `40`, or `50` |
+| `correlation_id` | Caller-generated binding to one logical action |
+
+All required strings must be non-empty, canonical (no surrounding
+whitespace), and within the protocol limits. Batch requests contain 1-100
+items, preserve input order, and are fully validated before any item is
+evaluated.
+
+The shared resource families currently defined by Aether are:
+
+- `workspace-execution/view`
+- `tool-catalog/provider`
+- `tool-catalog/entry`
+
+Applications may use other resource families supported by their ACL policy.
+
+## Direct and on-behalf-of checks
+
+Without an `AuthorizationContext`, Aether evaluates the connected actor. With a
+validated `on_behalf_of` context, Aether evaluates the subject's live ACL
+intersected with the authority grant's access, workspace, resource, operation,
+audience, and expiry constraints.
+
+An OBO exact-resource check never falls back to the actor's own resource
+permission. The grant is the delegate's authority to ask for the subject
+decision; it is not a reason to silently substitute the actor after a denial.
+
+## Decision receipts
+
+`AccessDecisionReceipt` records the normalized request, decision and effective
+level, actor, optional subject and grant lineage, evaluation/expiry times, a
+stable denial code, and a unique decision ID. A checked send also binds the
+receipt to the concrete post-wildcard-resolution `delivery_target`.
+
+Receipts are trusted only when they arrive as Aether transport metadata on
+`IncomingMessage.access_receipt`. An equivalent object embedded in an opaque
+application payload is untrusted. Before acting, a recipient should compare
+the receipt's correlation ID, resource tuple, delivery target, and expiry with
+the application request it is processing.
+
+Default receipt lifetime is 30 seconds. A `workspace-execution/view` `bind`
+receipt lasts 120 seconds. Lifetimes are capped at five minutes and clamped to
+the authority grant expiry.
+
+## Checked sends
+
+Set `SendMessage.checked_access` to make exact-resource authorization additive
+to normal topic-route authorization. Aether first authorizes the concrete
+route, then evaluates the exact logical resource:
+
+- allow: publish with a gateway-authored receipt;
+- deny, invalid request, or unavailable ACL service: publish nothing;
+- omitted `checked_access`: retain ordinary message behavior.
+
+The Go SDK exposes this through `SendMessageOptions.CheckedAccess`; Python has
+`send_checked_message`; TypeScript accepts `checkedAccess` on
+`OutgoingMessage`.
+
+## Authority continuations
+
+`SendMessage.authority_continuation` can attach a gateway-derived OBO child to
+the trusted delivery metadata. The child preserves the caller subject and root
+grant lineage, is short-lived and non-delegable, and remains subject to normal
+ACL checks when the recipient uses it downstream. The sender's own bearer grant
+is never copied into the application payload.
+
+Two scope modes are deliberately distinct:
+
+- `SCOPE_MODE_INHERIT_PARENT` is accepted only for an exact concrete service.
+ It carries no binding or requested scope and may reuse an active service leaf.
+ This supports trusted policy services that must evaluate arbitrary resources
+ within the caller's existing ceiling.
+- `SCOPE_MODE_ATTENUATE` accepts an exact service or agent. It requires an
+ allowed `checked_access` receipt, a binding ID equal to that check's
+ correlation ID, one exact checked workspace, and explicit non-empty resource
+ and operation scopes plus a maximum access level. The request must fit within
+ the parent grant and a fresh leaf is minted for every invocation.
+
+The recipient receives `binding_id` and the gateway-authored effective `scope`
+beside the child `AuthorizationContext`. An agent tool host should compare the
+binding with the application call ID and access receipt, validate the receipt's
+exact tool resource, compare the effective scope with its local invocation
+policy, and install the authorization only in that call's context.
+
+Go service example:
+
+```go
+opts.AuthorityContinuation = &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT,
+}
+```
+
+Go invocation-bound agent example:
+
+```go
+opts.CheckedAccess = &pb.ResourceAccessRequest{
+ ResourceType: "tool-catalog/entry", ResourceId: resourceID,
+ Operation: "tool.invoke.read", Workspace: "workspace-1",
+ RequiredAccessLevel: 10, CorrelationId: "call-1",
+}
+opts.AuthorityContinuation = &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_ATTENUATE,
+ BindingId: "call-1",
+ Scope: &pb.AuthorityContinuationScope{
+ WorkspaceScope: []string{"workspace-1"},
+ ResourceScope: []*pb.ACLAuthorityGrantResourceScopeEntry{
+ {ResourceType: "vfs", Patterns: []string{"workspace-1/*"}},
+ },
+ OperationScope: []string{"read"},
+ MaxAccessLevel: 10,
+ },
+}
+```
+
+## SDK examples
+
+Go:
+
+```go
+request := &pb.ResourceAccessRequest{
+ ResourceType: "tool-catalog/entry",
+ ResourceId: "provider-1/tool-1",
+ Operation: "invoke",
+ Workspace: "workspace-1",
+ RequiredAccessLevel: 20,
+ CorrelationId: "call-1",
+}
+receipt, err := client.CheckAccess(ctx, request, authorization)
+decisions, err := client.BatchCheckAccess(ctx, []*pb.ResourceAccessRequest{request}, authorization)
+```
+
+Python:
+
+```python
+request = aether_pb2.ResourceAccessRequest(
+ resource_type="tool-catalog/entry",
+ resource_id="provider-1/tool-1",
+ operation="invoke",
+ workspace="workspace-1",
+ required_access_level=20,
+ correlation_id="call-1",
+)
+receipt = client.check_access(request, authorization=authorization)
+receipts = client.batch_check_access([request], authorization=authorization)
+```
+
+TypeScript:
+
+```ts
+const request = {
+ resourceType: "tool-catalog/entry",
+ resourceId: "provider-1/tool-1",
+ operation: "invoke",
+ workspace: "workspace-1",
+ requiredAccessLevel: 20,
+ correlationId: "call-1",
+};
+const receipt = await client.checkAccess(request, authorization);
+const receipts = await client.batchCheckAccess([request], authorization);
+```
diff --git a/docs/workflow-schedule-authority.md b/docs/workflow-schedule-authority.md
new file mode 100644
index 0000000..a406df2
--- /dev/null
+++ b/docs/workflow-schedule-authority.md
@@ -0,0 +1,64 @@
+# Workflow schedule authority
+
+Aether can attach a private, bounded authority grant to a WorkflowEngine
+schedule. This is intended for scheduled `create_task` actions that must run on
+behalf of a user after the operation that created the schedule has returned.
+
+The schedule definition stays portable. Schedule-authority grant IDs are not written to action
+JSON, task payloads, schedule responses, or ecosystem messages. The gateway
+mints the grant from an authenticated `WorkflowOperation`; WorkflowEngine
+stores it in private schedule columns and supplies it only on the later
+`CreateTaskRequest` transport envelope.
+
+## Authorization boundary
+
+Schedule list/create/upsert/delete operations require an exact workspace.
+Create/upsert/delete also require an exact schedule ID matching the JSON
+definition. The gateway checks the canonical resource
+`workflow/schedule:workspaces/{workspace}/schedules/{schedule}` at read or
+manage level. Production deployments must grant this resource explicitly.
+AetherLite and the full gateway grant user and agent schedule management only
+when their explicit `--dev` mode is enabled, supporting local user clients and
+worker-owned reconciliation without weakening production defaults.
+
+Clients may send:
+
+- `WorkflowOperation.authorization`: optional direct/OBO authority used to
+ authorize schedule management;
+- `WorkflowOperation.schedule_authority_scope`: requested workspace, resource,
+ operation, access, expiry, hop, lifetime, and policy-version ceiling; and
+- action fields `require_task_authority` and
+ `required_downstream_authority_hops`.
+
+The current `policy_version` is `1`. Unknown versions fail closed. The gateway
+records a deterministic SHA-256 digest of the complete scope. A targeted task
+requires one derivation edge after the schedule grant; a pooled task requires
+two because the pool task anchor must derive to the selected worker.
+
+## Lifetime modes
+
+`WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND` derives under the caller's source
+grant. Source expiry/revocation cascades normally, and a source grant bound to a
+live session or task is rechecked on every scheduled task creation.
+
+`WORKFLOW_AUTHORITY_LIFETIME_DURABLE` creates a new bounded root. A direct user
+may request it; an OBO intermediary additionally needs manage access to
+`capability/schedule_authority`, and the requested scope/hops must attenuate the
+source. Durable authority has a fixed expiry of at most 90 days, cannot be
+auto-renewed by WorkflowEngine, and must be replaced by authenticated upsert.
+
+## Failure and replacement
+
+Upsert stores the replacement, revokes the prior grant cascade, and restores
+the prior row if revocation fails. Delete revokes before deleting. Gateway
+forwarding failures, negative WorkflowEngine responses, request timeouts, and
+shutdown revoke provisional grants.
+
+A transient task-creation error leaves the occurrence due for its existing
+idempotent retry. Expiry, revocation, missing authority, or authority denial
+records a no-task `authority_invalid` skip and blocks the schedule until an
+authenticated upsert installs fresh authority.
+
+Direct schedules without `require_task_authority` remain supported. This keeps
+standalone/system scheduling available while allowing enterprise compositions
+to fail closed for user-authorized scheduled work.
diff --git a/scripts/tag-release.sh b/scripts/tag-release.sh
deleted file mode 100755
index f0b8415..0000000
--- a/scripts/tag-release.sh
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/env bash
-#
-# Create git tags for all Go modules in the Aether monorepo.
-# Reads the version from versions.yaml (aether-gateway key).
-#
-# Usage:
-# ./scripts/tag-release.sh # dry-run (default)
-# ./scripts/tag-release.sh --push # create tags and push to origin
-# ./scripts/tag-release.sh --dry-run # explicit dry-run
-
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(dirname "$SCRIPT_DIR")"
-VERSIONS_FILE="$REPO_ROOT/versions.yaml"
-
-# Defaults
-DRY_RUN=true
-PUSH=false
-
-# Parse arguments
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --push)
- DRY_RUN=false
- PUSH=true
- shift
- ;;
- --dry-run)
- DRY_RUN=true
- shift
- ;;
- -h|--help)
- echo "Usage: $0 [--push | --dry-run]"
- echo ""
- echo " --dry-run Show what would be done (default)"
- echo " --push Create tags and push to origin"
- exit 0
- ;;
- *)
- echo "Unknown option: $1" >&2
- exit 1
- ;;
- esac
-done
-
-# Read version from versions.yaml
-if [[ ! -f "$VERSIONS_FILE" ]]; then
- echo "ERROR: versions.yaml not found at $VERSIONS_FILE" >&2
- exit 1
-fi
-
-VERSION=$(grep '^aether-gateway:' "$VERSIONS_FILE" | sed 's/^aether-gateway:\s*//' | sed 's/\s*#.*//' | tr -d '[:space:]')
-
-if [[ -z "$VERSION" ]]; then
- echo "ERROR: Could not read aether-gateway version from $VERSIONS_FILE" >&2
- exit 1
-fi
-
-# Validate semver
-if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
- echo "ERROR: Invalid semver: $VERSION" >&2
- exit 1
-fi
-
-echo "Version: $VERSION"
-echo ""
-
-# Check for clean working directory
-cd "$REPO_ROOT"
-if [[ -n "$(git status --porcelain)" ]]; then
- echo "WARNING: Working directory is not clean." >&2
- echo "Commit or stash changes before tagging a release." >&2
- if [[ "$DRY_RUN" == false ]]; then
- echo "Aborting." >&2
- exit 1
- fi
- echo "(Continuing in dry-run mode...)"
- echo ""
-fi
-
-# Define tags for Go multi-module repo
-# Go resolves a nested module by its directory, so each module's tag is
-# "
/vX.Y.Z". The bare "vX.Y.Z" tag is the release signal the CI workflows
-# trigger on — it is not itself a Go module tag.
-TAGS=(
- "v${VERSION}" # release signal (all workflows trigger on this)
- "api/v${VERSION}" # api module: github.com/scitrera/aether/api
- "sdk/go/v${VERSION}" # go sdk module: github.com/scitrera/aether/sdk/go
- "server/v${VERSION}" # server module: github.com/scitrera/aether/server
-)
-
-# Check for existing tags
-EXISTING=()
-for tag in "${TAGS[@]}"; do
- if git rev-parse "$tag" &>/dev/null; then
- EXISTING+=("$tag")
- fi
-done
-
-if [[ ${#EXISTING[@]} -gt 0 ]]; then
- echo "WARNING: The following tags already exist:" >&2
- for tag in "${EXISTING[@]}"; do
- echo " $tag" >&2
- done
- if [[ "$DRY_RUN" == false ]]; then
- echo "Aborting. Delete existing tags first if you want to re-tag." >&2
- exit 1
- fi
- echo ""
-fi
-
-# Create/display tags
-if [[ "$DRY_RUN" == true ]]; then
- echo "DRY RUN -- would create the following tags:"
- for tag in "${TAGS[@]}"; do
- echo " git tag -a $tag -m \"Release $tag\""
- done
- if [[ "$PUSH" == true ]]; then
- echo ""
- echo " git push origin ${TAGS[*]}"
- fi
- echo ""
- echo "Run with --push to create and push tags."
-else
- echo "Creating tags..."
- for tag in "${TAGS[@]}"; do
- echo " $tag"
- git tag -a "$tag" -m "Release $tag"
- done
-
- if [[ "$PUSH" == true ]]; then
- echo ""
- echo "Pushing tags to origin..."
- git push origin "${TAGS[@]}"
- echo "Done."
- else
- echo ""
- echo "Tags created locally. Push with: git push origin ${TAGS[*]}"
- fi
-fi
diff --git a/sdk/go/README.md b/sdk/go/README.md
index d8f7a45..0ceaa3c 100644
--- a/sdk/go/README.md
+++ b/sdk/go/README.md
@@ -582,6 +582,23 @@ client.CreateTask(aether.CreateTaskOptions{
})
```
+A long-lived worker can request native hierarchy for work spawned while it is
+executing an Aether task:
+
+```go
+client.CreateTask(aether.CreateTaskOptions{
+ TaskType: "nested-work",
+ Workspace: "default",
+ AssignmentMode: aether.TaskAssignmentSelfAssign,
+ ParentTaskID: activeParentTaskID,
+})
+```
+
+The gateway accepts `ParentTaskID` only when the parent is in the same workspace,
+is assigned to the calling identity, and is still assigned or running. The
+binding applies only to this request and does not change the connection's
+startup task association.
+
## Connection Configuration
All clients support configurable connection behavior:
diff --git a/sdk/go/aether/access_check_ops.go b/sdk/go/aether/access_check_ops.go
new file mode 100644
index 0000000..0fbe0b1
--- /dev/null
+++ b/sdk/go/aether/access_check_ops.go
@@ -0,0 +1,77 @@
+package aether
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ pb "github.com/scitrera/aether/api/proto"
+)
+
+// DefaultAccessCheckTimeout bounds synchronous runtime authorization checks.
+const DefaultAccessCheckTimeout = 10 * time.Second
+
+// CheckAccess asks the gateway to evaluate one exact logical resource. A deny
+// is a successful response with receipt.Allowed=false, not an SDK error.
+func (c *BaseClient) CheckAccess(ctx context.Context, access *pb.ResourceAccessRequest, authorization *pb.AuthorizationContext) (*pb.AccessDecisionReceipt, error) {
+ requestID := c.NextRequestID()
+ ch := c.pendingAccessCheckRequests.Register(requestID)
+ defer c.pendingAccessCheckRequests.Delete(requestID)
+
+ if err := c.Send(&pb.UpstreamMessage{Payload: &pb.UpstreamMessage_AccessCheck{
+ AccessCheck: &pb.AccessCheckOperation{
+ RequestId: requestID,
+ Access: access,
+ Authorization: authorization,
+ },
+ }}); err != nil {
+ return nil, err
+ }
+
+ timer := time.NewTimer(DefaultAccessCheckTimeout)
+ defer timer.Stop()
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-timer.C:
+ return nil, NewTimeoutError("access check timed out", DefaultAccessCheckTimeout.Seconds())
+ case response := <-ch:
+ if !response.GetSuccess() {
+ return nil, fmt.Errorf("access check failed: %s", response.GetError())
+ }
+ return response.GetDecision(), nil
+ }
+}
+
+// BatchCheckAccess evaluates 1-100 requests under one authorization context.
+// Results preserve input order and cardinality. The gateway rejects an invalid
+// batch as a whole before evaluating any item.
+func (c *BaseClient) BatchCheckAccess(ctx context.Context, access []*pb.ResourceAccessRequest, authorization *pb.AuthorizationContext) ([]*pb.AccessDecisionReceipt, error) {
+ requestID := c.NextRequestID()
+ ch := c.pendingBatchAccessCheckRequests.Register(requestID)
+ defer c.pendingBatchAccessCheckRequests.Delete(requestID)
+
+ if err := c.Send(&pb.UpstreamMessage{Payload: &pb.UpstreamMessage_BatchAccessCheck{
+ BatchAccessCheck: &pb.BatchAccessCheckOperation{
+ RequestId: requestID,
+ Access: access,
+ Authorization: authorization,
+ },
+ }}); err != nil {
+ return nil, err
+ }
+
+ timer := time.NewTimer(DefaultAccessCheckTimeout)
+ defer timer.Stop()
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-timer.C:
+ return nil, NewTimeoutError("batch access check timed out", DefaultAccessCheckTimeout.Seconds())
+ case response := <-ch:
+ if !response.GetSuccess() {
+ return nil, fmt.Errorf("batch access check failed: %s", response.GetError())
+ }
+ return response.GetDecisions(), nil
+ }
+}
diff --git a/sdk/go/aether/agent.go b/sdk/go/aether/agent.go
index 749d715..cab20f2 100644
--- a/sdk/go/aether/agent.go
+++ b/sdk/go/aether/agent.go
@@ -428,15 +428,27 @@ func (c *AgentClient) CreateTask(opts CreateTaskOptions) error {
msg := &pb.UpstreamMessage{
Payload: &pb.UpstreamMessage_CreateTask{
CreateTask: &pb.CreateTaskRequest{
- TaskType: opts.TaskType,
- Workspace: workspace,
- AssignmentMode: pbMode,
- TargetAgentId: opts.TargetAgentID,
- TargetImplementation: opts.TargetImplementation,
- LaunchParamOverrides: opts.LaunchParamOverrides,
- Metadata: opts.Metadata,
- Payload: opts.Payload,
- Priority: opts.Priority,
+ TaskType: opts.TaskType,
+ Workspace: workspace,
+ AssignmentMode: pbMode,
+ TargetAgentId: opts.TargetAgentID,
+ TargetOfflinePolicy: opts.TargetOfflinePolicy,
+ TargetImplementation: opts.TargetImplementation,
+ LaunchParamOverrides: opts.LaunchParamOverrides,
+ Metadata: opts.Metadata,
+ Payload: opts.Payload,
+ TargetIdentity: opts.TargetIdentity,
+ Authorization: opts.Authorization,
+ TaskClass: opts.TaskClass,
+ ContextId: opts.ContextID,
+ RetryPolicy: opts.RetryPolicy,
+ Priority: opts.Priority,
+ IdempotencyKey: opts.IdempotencyKey,
+ CorrelationId: opts.CorrelationID,
+ RootTaskId: opts.RootTaskID,
+ CompletionEvent: opts.CompletionEvent,
+ ParentTaskId: opts.ParentTaskID,
+ RequiredDownstreamAuthorityHops: opts.RequiredDownstreamAuthorityHops,
},
},
}
diff --git a/sdk/go/aether/authority_grant_ops.go b/sdk/go/aether/authority_grant_ops.go
index 5ff92cf..8917c20 100644
--- a/sdk/go/aether/authority_grant_ops.go
+++ b/sdk/go/aether/authority_grant_ops.go
@@ -211,6 +211,16 @@ func (a *AuthorityGrantOps) Revoke(ctx context.Context, grantID string) (*pb.Aut
}, 0)
}
+// RevokeForWorkflowSchedule revokes a private workflow_schedule grant. The
+// gateway accepts the audience context only from an authenticated
+// WorkflowEngine and requires it to match the grant exactly.
+func (a *AuthorityGrantOps) RevokeForWorkflowSchedule(ctx context.Context, grantID, scheduleID string) (*pb.AuthorityGrantResponse, error) {
+ return a.SendOpSync(ctx, &pb.AuthorityGrantOperation{
+ Op: pb.AuthorityGrantOperation_REVOKE, GrantId: grantID,
+ WorkflowScheduleId: scheduleID,
+ }, 0)
+}
+
// ListOpts paginates and filters list operations.
type ListOpts struct {
AudienceType string
diff --git a/sdk/go/aether/client.go b/sdk/go/aether/client.go
index 88f0da4..bccca43 100644
--- a/sdk/go/aether/client.go
+++ b/sdk/go/aether/client.go
@@ -26,6 +26,7 @@ import (
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
+ "google.golang.org/protobuf/proto"
)
// =============================================================================
@@ -122,6 +123,8 @@ type BaseClient struct {
pendingSessionRequests pendingRequests[*SessionOperationResponse]
pendingAuditSubmitRequests pendingRequests[*pb.SubmitAuditEventResponse]
pendingAuditQueryRequests pendingRequests[*pb.AuditQueryResponse]
+ pendingAccessCheckRequests pendingRequests[*pb.AccessCheckResponse]
+ pendingBatchAccessCheckRequests pendingRequests[*pb.BatchAccessCheckResponse]
requestIDCounter atomic.Uint64
// rawDownstreamTap, when non-nil, is invoked for every downstream
@@ -151,28 +154,30 @@ type BaseClient struct {
authorityCaches []*AuthorityGrantCache
// Cached KV, Checkpoint, and Workflow helpers (for sync mutex to work across calls)
- kvOnce sync.Once
- kvInstance *KV
- cpOnce sync.Once
- cpInstance *Checkpoint
- workflowOnce sync.Once
- workflowInstance *WorkflowOps
- workspaceOnce sync.Once
- workspaceInstance *WorkspaceOps
- agentOnce sync.Once
- agentInstance *AgentOps
- aclOnce sync.Once
- aclInstance *ACLOps
- tokenOnce sync.Once
- tokenInstance *TokenOps
- authorityOnce sync.Once
- authorityInstance *AuthorityGrantOps
- adminOnce sync.Once
- adminInstance *AdminOps
- sessionOnce sync.Once
- sessionInstance *SessionOps
- connectionOnce sync.Once
- connectionInstance *ConnectionOps
+ kvOnce sync.Once
+ kvInstance *KV
+ cpOnce sync.Once
+ cpInstance *Checkpoint
+ workflowOnce sync.Once
+ workflowInstance *WorkflowOps
+ workflowHandlerOrderMu sync.Mutex
+ workflowHandlerTail chan struct{}
+ workspaceOnce sync.Once
+ workspaceInstance *WorkspaceOps
+ agentOnce sync.Once
+ agentInstance *AgentOps
+ aclOnce sync.Once
+ aclInstance *ACLOps
+ tokenOnce sync.Once
+ tokenInstance *TokenOps
+ authorityOnce sync.Once
+ authorityInstance *AuthorityGrantOps
+ adminOnce sync.Once
+ adminInstance *AdminOps
+ sessionOnce sync.Once
+ sessionInstance *SessionOps
+ connectionOnce sync.Once
+ connectionInstance *ConnectionOps
// InitConnection message builder (set by specific client types)
initMsgBuilder func() *pb.InitConnection
@@ -982,6 +987,12 @@ func (c *BaseClient) SendWithOptions(opts SendMessageOptions) error {
if opts.Authorization != nil {
send.Authorization = opts.Authorization
}
+ if opts.CheckedAccess != nil {
+ send.CheckedAccess = opts.CheckedAccess
+ }
+ if opts.AuthorityContinuation != nil {
+ send.AuthorityContinuation = opts.AuthorityContinuation
+ }
return c.Send(&pb.UpstreamMessage{
Payload: &pb.UpstreamMessage_Send{Send: send},
})
@@ -1757,7 +1768,13 @@ func (c *BaseClient) receiveLoop(ctx context.Context) error {
// Receive the next message from the stream
response, err := stream.Recv()
if err != nil {
- return c.handleReceiveError(ctx, err)
+ if err := c.handleReceiveError(ctx, err); err != nil {
+ return err
+ }
+ // A nil result means handleReceiveError successfully established a
+ // replacement stream. Keep this receive loop alive so Run continues
+ // servicing that stream instead of returning a false graceful exit.
+ continue
}
// Dispatch the response to the appropriate handler
@@ -1768,7 +1785,10 @@ func (c *BaseClient) receiveLoop(ctx context.Context) error {
}
// For recoverable dispatch errors (e.g., graceful disconnect),
// use the same reconnection path as receive errors.
- return c.handleReceiveError(ctx, err)
+ if err := c.handleReceiveError(ctx, err); err != nil {
+ return err
+ }
+ continue
}
}
}
@@ -1908,6 +1928,14 @@ func (c *BaseClient) dispatchResponse(ctx context.Context, response *pb.Downstre
case *pb.DownstreamMessage_AuditResponse:
return c.handleAuditQueryResponse(ctx, payload.AuditResponse)
+ case *pb.DownstreamMessage_AccessCheckResponse:
+ c.pendingAccessCheckRequests.Resolve(payload.AccessCheckResponse.GetRequestId(), payload.AccessCheckResponse)
+ return nil
+
+ case *pb.DownstreamMessage_BatchAccessCheckResponse:
+ c.pendingBatchAccessCheckRequests.Resolve(payload.BatchAccessCheckResponse.GetRequestId(), payload.BatchAccessCheckResponse)
+ return nil
+
case *pb.DownstreamMessage_CreateTask:
return c.handleCreateTaskResponse(ctx, payload.CreateTask)
@@ -1979,11 +2007,14 @@ func (c *BaseClient) dispatchResponse(ctx context.Context, response *pb.Downstre
func (c *BaseClient) handleIncomingMessage(ctx context.Context, msg *pb.IncomingMessage) error {
// Convert to high-level Message type
message := &Message{
- SourceTopic: msg.GetSourceTopic(),
- Payload: msg.GetPayload(),
- MessageType: msg.GetMessageType(),
- OnBehalfSubject: msg.GetOnBehalfSubject(),
- ReceivedAt: time.Now(),
+ SourceTopic: msg.GetSourceTopic(),
+ Payload: msg.GetPayload(),
+ MessageType: msg.GetMessageType(),
+ Workspace: msg.GetWorkspace(),
+ AccessReceipt: msg.GetAccessReceipt(),
+ OnBehalfSubject: msg.GetOnBehalfSubject(),
+ ForwardedAuthorization: msg.GetForwardedAuthorization(),
+ ReceivedAt: time.Now(),
}
// Dispatch to generic message handler
@@ -2195,6 +2226,7 @@ func (c *BaseClient) handleTaskAssignment(ctx context.Context, ta *pb.TaskAssign
Workspace: ta.GetWorkspace(),
Specifier: ta.GetSpecifier(),
Payload: ta.GetPayload(),
+ Authorization: ta.GetAuthorization(),
}
// Convert Unix timestamp if present
@@ -2319,9 +2351,10 @@ func (c *BaseClient) handleProgressUpdate(ctx context.Context, pu *pb.ProgressUp
// handleTaskQueryResponse processes a task query response from the server.
func (c *BaseClient) handleTaskQueryResponse(ctx context.Context, resp *pb.TaskQueryResponse) error {
tqr := &TaskQueryResponse{
- Success: resp.GetSuccess(),
- Error: resp.GetError(),
- TotalCount: resp.GetTotalCount(),
+ Success: resp.GetSuccess(),
+ Error: resp.GetError(),
+ TotalCount: resp.GetTotalCount(),
+ NextPageToken: resp.GetNextPageToken(),
}
if t := resp.GetTask(); t != nil {
tqr.Task = protoTaskInfoToSDK(t)
@@ -2372,14 +2405,15 @@ func (c *BaseClient) handleTaskOperationResponse(ctx context.Context, resp *pb.T
// handleCreateTaskResponse processes a CreateTaskResponse from the server.
func (c *BaseClient) handleCreateTaskResponse(ctx context.Context, resp *pb.CreateTaskResponse) error {
ctr := &CreateTaskResponse{
- Success: resp.GetSuccess(),
- TaskID: resp.GetTaskId(),
- Status: resp.GetStatus(),
- ErrorCode: resp.GetErrorCode(),
- ErrorMessage: resp.GetErrorMessage(),
- RequestId: resp.GetRequestId(),
- AssignedTo: resp.GetAssignedTo(),
- TaskToken: resp.GetTaskToken(),
+ Success: resp.GetSuccess(),
+ TaskID: resp.GetTaskId(),
+ Status: resp.GetStatus(),
+ ErrorCode: resp.GetErrorCode(),
+ ErrorMessage: resp.GetErrorMessage(),
+ RequestId: resp.GetRequestId(),
+ AssignedTo: resp.GetAssignedTo(),
+ TaskToken: resp.GetTaskToken(),
+ AuthorityGrantID: resp.GetAuthorityGrantId(),
}
// Route to correlated pending request if available.
@@ -2397,18 +2431,28 @@ func (c *BaseClient) handleCreateTaskResponse(ctx context.Context, resp *pb.Crea
// The server will not send a response; use CreateTaskSync when you need the task_id.
func (c *BaseClient) CreateTask(taskType, workspace string, opts CreateTaskOptions) error {
req := &pb.CreateTaskRequest{
- TaskType: taskType,
- Workspace: workspace,
- AssignmentMode: pb.TaskAssignmentMode(pb.TaskAssignmentMode_value[string(opts.AssignmentMode)]),
- TargetAgentId: opts.TargetAgentID,
- TargetIdentity: opts.TargetIdentity,
- TargetImplementation: opts.TargetImplementation,
- LaunchParamOverrides: opts.LaunchParamOverrides,
- Metadata: opts.Metadata,
- Payload: opts.Payload,
- RetryPolicy: opts.RetryPolicy,
- Priority: opts.Priority,
- Authorization: opts.Authorization,
+ TaskType: taskType,
+ Workspace: workspace,
+ AssignmentMode: pb.TaskAssignmentMode(pb.TaskAssignmentMode_value[string(opts.AssignmentMode)]),
+ TargetAgentId: opts.TargetAgentID,
+ TargetOfflinePolicy: opts.TargetOfflinePolicy,
+ TargetIdentity: opts.TargetIdentity,
+ TargetImplementation: opts.TargetImplementation,
+ LaunchParamOverrides: opts.LaunchParamOverrides,
+ Metadata: opts.Metadata,
+ Payload: opts.Payload,
+ TaskClass: opts.TaskClass,
+ ContextId: opts.ContextID,
+ RetryPolicy: opts.RetryPolicy,
+ Priority: opts.Priority,
+ IdempotencyKey: opts.IdempotencyKey,
+ CorrelationId: opts.CorrelationID,
+ RootTaskId: opts.RootTaskID,
+ CompletionEvent: opts.CompletionEvent,
+ ParentTaskId: opts.ParentTaskID,
+ RequiredDownstreamAuthorityHops: opts.RequiredDownstreamAuthorityHops,
+ OriginatingScheduleId: opts.OriginatingScheduleID,
+ Authorization: opts.Authorization,
}
return c.Send(&pb.UpstreamMessage{
Payload: &pb.UpstreamMessage_CreateTask{CreateTask: req},
@@ -2429,19 +2473,29 @@ func (c *BaseClient) CreateTaskSync(ctx context.Context, taskType, workspace str
defer c.pendingCreateTaskRequests.Delete(requestID)
req := &pb.CreateTaskRequest{
- TaskType: taskType,
- Workspace: workspace,
- AssignmentMode: pb.TaskAssignmentMode(pb.TaskAssignmentMode_value[string(opts.AssignmentMode)]),
- TargetAgentId: opts.TargetAgentID,
- TargetIdentity: opts.TargetIdentity,
- TargetImplementation: opts.TargetImplementation,
- LaunchParamOverrides: opts.LaunchParamOverrides,
- Metadata: opts.Metadata,
- Payload: opts.Payload,
- RetryPolicy: opts.RetryPolicy,
- Priority: opts.Priority,
- Authorization: opts.Authorization,
- RequestId: requestID,
+ TaskType: taskType,
+ Workspace: workspace,
+ AssignmentMode: pb.TaskAssignmentMode(pb.TaskAssignmentMode_value[string(opts.AssignmentMode)]),
+ TargetAgentId: opts.TargetAgentID,
+ TargetOfflinePolicy: opts.TargetOfflinePolicy,
+ TargetIdentity: opts.TargetIdentity,
+ TargetImplementation: opts.TargetImplementation,
+ LaunchParamOverrides: opts.LaunchParamOverrides,
+ Metadata: opts.Metadata,
+ Payload: opts.Payload,
+ TaskClass: opts.TaskClass,
+ ContextId: opts.ContextID,
+ RetryPolicy: opts.RetryPolicy,
+ Priority: opts.Priority,
+ IdempotencyKey: opts.IdempotencyKey,
+ CorrelationId: opts.CorrelationID,
+ RootTaskId: opts.RootTaskID,
+ CompletionEvent: opts.CompletionEvent,
+ ParentTaskId: opts.ParentTaskID,
+ RequiredDownstreamAuthorityHops: opts.RequiredDownstreamAuthorityHops,
+ OriginatingScheduleId: opts.OriginatingScheduleID,
+ Authorization: opts.Authorization,
+ RequestId: requestID,
}
if err := c.Send(&pb.UpstreamMessage{
Payload: &pb.UpstreamMessage_CreateTask{CreateTask: req},
@@ -2464,19 +2518,36 @@ func (c *BaseClient) CreateTaskSync(ctx context.Context, taskType, workspace str
// protoTaskInfoToSDK converts a protobuf TaskInfo to the SDK TaskInfo type.
func protoTaskInfoToSDK(t *pb.TaskInfo) *TaskInfo {
return &TaskInfo{
- TaskID: t.GetTaskId(),
- TaskType: t.GetTaskType(),
- Status: t.GetStatus().String(),
- Workspace: t.GetWorkspace(),
- TargetTopic: t.GetTargetTopic(),
- AssignedTo: t.GetAssignedTo(),
- CreatedAt: t.GetCreatedAt(),
- StartedAt: t.GetStartedAt(),
- CompletedAt: t.GetCompletedAt(),
- Attempt: t.GetAttempt(),
- MaxAttempts: t.GetMaxAttempts(),
- Error: t.GetError(),
- Metadata: t.GetMetadata(),
+ TaskID: t.GetTaskId(),
+ TaskType: t.GetTaskType(),
+ Status: t.GetStatus().String(),
+ Workspace: t.GetWorkspace(),
+ TargetTopic: t.GetTargetTopic(),
+ AssignedTo: t.GetAssignedTo(),
+ CreatedAt: t.GetCreatedAt(),
+ StartedAt: t.GetStartedAt(),
+ CompletedAt: t.GetCompletedAt(),
+ Attempt: t.GetAttempt(),
+ MaxAttempts: t.GetMaxAttempts(),
+ Error: t.GetError(),
+ Metadata: t.GetMetadata(),
+ AuthorityMode: t.GetAuthorityMode(),
+ SubjectType: t.GetSubjectType(),
+ SubjectID: t.GetSubjectId(),
+ RootSubjectType: t.GetRootSubjectType(),
+ RootSubjectID: t.GetRootSubjectId(),
+ AuthorityGrantID: t.GetAuthorityGrantId(),
+ RootAuthorityGrantID: t.GetRootAuthorityGrantId(),
+ ParentAuthorityGrantID: t.GetParentAuthorityGrantId(),
+ CreatorActorID: t.GetCreatorActorId(),
+ ParentTaskID: t.GetParentTaskId(),
+ TaskClass: t.GetTaskClass().String(),
+ ContextID: t.GetContextId(),
+ Priority: t.GetPriority().String(),
+ CorrelationID: t.GetCorrelationId(),
+ RootTaskID: t.GetRootTaskId(),
+ DisconnectedAt: t.GetDisconnectedAt(),
+ GraceWindowMS: t.GetGraceWindowMs(),
}
}
@@ -2596,6 +2667,34 @@ func (c *BaseClient) handleWorkflowOperation(ctx context.Context, op *pb.Workflo
if c.handlers.OnWorkflowOperation == nil {
return nil
}
+ // Workflow handlers may make correlated synchronous calls back through this
+ // same client (notably revoking a replaced schedule authority). Detach them
+ // from the single receive loop so that loop remains free to deliver the
+ // nested response.
+ cloned := proto.Clone(op).(*pb.WorkflowOperation)
+ done := make(chan struct{})
+ c.workflowHandlerOrderMu.Lock()
+ previous := c.workflowHandlerTail
+ c.workflowHandlerTail = done
+ c.workflowHandlerOrderMu.Unlock()
+ go func() {
+ defer func() {
+ close(done)
+ c.workflowHandlerOrderMu.Lock()
+ if c.workflowHandlerTail == done {
+ c.workflowHandlerTail = nil
+ }
+ c.workflowHandlerOrderMu.Unlock()
+ }()
+ if previous != nil {
+ <-previous
+ }
+ _ = c.processWorkflowOperation(context.WithoutCancel(ctx), cloned)
+ }()
+ return nil
+}
+
+func (c *BaseClient) processWorkflowOperation(ctx context.Context, op *pb.WorkflowOperation) error {
resp, err := c.handlers.OnWorkflowOperation(ctx, op)
if err != nil {
resp = &pb.WorkflowResponse{
diff --git a/sdk/go/aether/client_test.go b/sdk/go/aether/client_test.go
index c5d2f31..76e7856 100644
--- a/sdk/go/aether/client_test.go
+++ b/sdk/go/aether/client_test.go
@@ -52,6 +52,59 @@ func TestNewBaseClient_DefaultValues(t *testing.T) {
}
}
+func TestWorkflowOperationHandlersRunOffReceiveLoopInArrivalOrder(t *testing.T) {
+ client := &BaseClient{handlers: NewHandlers()}
+ firstStarted := make(chan struct{})
+ releaseFirst := make(chan struct{})
+ secondStarted := make(chan struct{})
+ client.handlers.OnWorkflowOperation = func(_ context.Context, op *pb.WorkflowOperation) (*pb.WorkflowResponse, error) {
+ switch op.GetRequestId() {
+ case "first":
+ close(firstStarted)
+ <-releaseFirst
+ case "second":
+ close(secondStarted)
+ }
+ return nil, nil
+ }
+
+ if err := client.handleWorkflowOperation(context.Background(), &pb.WorkflowOperation{RequestId: "first"}); err != nil {
+ t.Fatal(err)
+ }
+ select {
+ case <-firstStarted:
+ case <-time.After(time.Second):
+ t.Fatal("first workflow handler did not start asynchronously")
+ }
+ if err := client.handleWorkflowOperation(context.Background(), &pb.WorkflowOperation{RequestId: "second"}); err != nil {
+ t.Fatal(err)
+ }
+ select {
+ case <-secondStarted:
+ t.Fatal("second workflow handler overtook the first")
+ case <-time.After(25 * time.Millisecond):
+ }
+ close(releaseFirst)
+ select {
+ case <-secondStarted:
+ case <-time.After(time.Second):
+ t.Fatal("second workflow handler did not run after the first completed")
+ }
+}
+
+func TestWorkflowScheduleOperationsRequireExactWorkspace(t *testing.T) {
+ ops := &WorkflowOps{}
+ if _, err := ops.ListSchedules(context.Background(), "*"); err == nil {
+ t.Fatal("ListSchedules accepted wildcard workspace")
+ }
+ if _, err := ops.DeleteSchedule(context.Background(), "", "schedule-a"); err == nil {
+ t.Fatal("DeleteSchedule accepted empty workspace")
+ }
+ if _, err := ops.CreateSchedule(context.Background(), []byte(`{"id":"schedule-a","workspace":"*"}`)); err == nil {
+ t.Fatal("CreateSchedule accepted wildcard workspace")
+ }
+}
+
func TestNewBaseClient_CustomValues(t *testing.T) {
cfg := BaseClientConfig{
ServerAddr: TestServerAddr,
@@ -480,12 +533,25 @@ func TestSendWithOptions_ThreadsAuthorization(t *testing.T) {
Subject: &pb.PrincipalRef{PrincipalType: "user", PrincipalId: "alice@example.com"},
GrantId: "grant-123",
}
+ checked := &pb.ResourceAccessRequest{
+ ResourceType: "tool-catalog/entry",
+ ResourceId: "provider-1/tool-1",
+ Operation: "invoke",
+ Workspace: "workspace-1",
+ RequiredAccessLevel: 20,
+ CorrelationId: "call-1",
+ }
+ continuation := &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT,
+ }
c := newRunningClient()
if err := c.SendWithOptions(SendMessageOptions{
- TargetTopic: "test.topic",
- Payload: []byte("hi"),
- MessageType: MessageTypeChat,
- Authorization: authz,
+ TargetTopic: "test.topic",
+ Payload: []byte("hi"),
+ MessageType: MessageTypeChat,
+ Authorization: authz,
+ CheckedAccess: checked,
+ AuthorityContinuation: continuation,
}); err != nil {
t.Fatalf("SendWithOptions() error = %v", err)
}
@@ -496,6 +562,12 @@ func TestSendWithOptions_ThreadsAuthorization(t *testing.T) {
if got := send.GetAuthorization().GetSubject().GetPrincipalId(); got != "alice@example.com" {
t.Errorf("authorization subject = %q, want alice@example.com", got)
}
+ if got := send.GetCheckedAccess().GetCorrelationId(); got != "call-1" {
+ t.Errorf("checked access correlation = %q, want call-1", got)
+ }
+ if send.GetAuthorityContinuation().GetScopeMode() != pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT {
+ t.Error("expected authority_continuation on SendMessage")
+ }
// Bare send (no authorization) stays nil.
c2 := newRunningClient()
@@ -506,8 +578,8 @@ func TestSendWithOptions_ThreadsAuthorization(t *testing.T) {
}); err != nil {
t.Fatalf("SendWithOptions() error = %v", err)
}
- if send := dequeueSend(c2); send.GetAuthorization() != nil {
- t.Error("bare send must not assume an OBO authorization")
+ if send := dequeueSend(c2); send.GetAuthorization() != nil || send.GetCheckedAccess() != nil || send.GetAuthorityContinuation() != nil {
+ t.Error("bare send must not assume authorization or an exact resource check")
}
}
@@ -785,6 +857,17 @@ func TestBaseClient_DispatchResponse_IncomingMessage(t *testing.T) {
ctx := context.Background()
response := newMockIncomingMessage("ag.test.impl.spec", testPayload())
+ incoming := response.GetMsg()
+ incoming.Workspace = "workspace-1"
+ incoming.AccessReceipt = &pb.AccessDecisionReceipt{
+ DecisionId: "decision-1",
+ Allowed: true,
+ Request: &pb.ResourceAccessRequest{CorrelationId: "call-1"},
+ }
+ incoming.ForwardedAuthorization = &pb.ForwardedAuthorization{
+ Authorization: &pb.AuthorizationContext{GrantId: "child-grant-1"},
+ RootGrantId: "root-grant-1", DeliveryTarget: "sv::tools::one",
+ }
err = client.dispatchResponse(ctx, response)
if err != nil {
@@ -794,6 +877,15 @@ func TestBaseClient_DispatchResponse_IncomingMessage(t *testing.T) {
if tracker.MessageCount() != 1 {
t.Errorf("Message handler called %d times, want 1", tracker.MessageCount())
}
+ if got := tracker.messages[0].Workspace; got != "workspace-1" {
+ t.Errorf("Message.Workspace = %q, want workspace-1", got)
+ }
+ if got := tracker.messages[0].AccessReceipt.GetRequest().GetCorrelationId(); got != "call-1" {
+ t.Errorf("Message.AccessReceipt correlation = %q, want call-1", got)
+ }
+ if got := tracker.messages[0].ForwardedAuthorization.GetRootGrantId(); got != "root-grant-1" {
+ t.Errorf("Message.ForwardedAuthorization root = %q, want root-grant-1", got)
+ }
}
func TestBaseClient_DispatchResponse_ConfigSnapshot(t *testing.T) {
@@ -991,6 +1083,11 @@ func TestBaseClient_DispatchResponse_TaskAssignment(t *testing.T) {
ctx := context.Background()
response := newMockTaskAssignment("task-123", "process", "ag.test.worker.inst")
+ response.GetTaskAssignment().Authorization = &pb.AuthorizationContext{
+ AuthorityMode: "on_behalf_of",
+ GrantId: "grant-task-123",
+ Subject: &pb.PrincipalRef{PrincipalType: "user", PrincipalId: "alice"},
+ }
err = client.dispatchResponse(ctx, response)
if err != nil {
@@ -1014,16 +1111,46 @@ func TestBaseClient_DispatchResponse_TaskAssignment(t *testing.T) {
}
tracker.mu.Lock()
got := len(tracker.tasks)
+ var assignment *TaskAssignment
+ if got == 1 {
+ assignment = tracker.tasks[0]
+ }
tracker.mu.Unlock()
if got != 1 {
t.Errorf("Task assignment handler called %d times, want 1", got)
}
+ if assignment == nil || assignment.Authorization == nil ||
+ assignment.Authorization.GetGrantId() != "grant-task-123" ||
+ assignment.Authorization.GetSubject().GetPrincipalId() != "alice" {
+ t.Errorf("Task assignment authorization = %#v", assignment)
+ }
}
// =============================================================================
// Task Lifecycle Tests
// =============================================================================
+func TestBaseClient_CreateTaskResponseMapsAuthorityGrantID(t *testing.T) {
+ client, err := NewBaseClient(BaseClientConfig{ServerAddr: TestServerAddr})
+ if err != nil {
+ t.Fatalf("NewBaseClient() error = %v", err)
+ }
+ responses := client.RegisterPendingCreateTaskRequest("create-authority")
+ if err := client.handleCreateTaskResponse(context.Background(), &pb.CreateTaskResponse{
+ Success: true, TaskId: "task-123", RequestId: "create-authority", AuthorityGrantId: "grant-task-123",
+ }); err != nil {
+ t.Fatalf("handleCreateTaskResponse() error = %v", err)
+ }
+ select {
+ case response := <-responses:
+ if response.AuthorityGrantID != "grant-task-123" {
+ t.Fatalf("AuthorityGrantID = %q, want grant-task-123", response.AuthorityGrantID)
+ }
+ case <-time.After(time.Second):
+ t.Fatal("timed out waiting for create-task response")
+ }
+}
+
func TestBaseClient_QueryTasks(t *testing.T) {
cfg := BaseClientConfig{ServerAddr: TestServerAddr}
client, err := NewBaseClient(cfg)
@@ -1106,6 +1233,95 @@ func TestBaseClient_GetTask(t *testing.T) {
}
}
+func TestBaseClient_CreateTaskForwardsDurableCoordinationFields(t *testing.T) {
+ client, err := NewBaseClient(BaseClientConfig{ServerAddr: TestServerAddr})
+ if err != nil {
+ t.Fatal(err)
+ }
+ client.running.Store(true)
+ completion := &pb.TaskCompletionEvent{Enabled: true, EventName: "child.done"}
+ if err := client.CreateTask("child", "routing", CreateTaskOptions{
+ AssignmentMode: TaskAssignmentTargeted,
+ TargetAgentID: "ag::routing::worker::static-1",
+ TargetOfflinePolicy: pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE,
+ TaskClass: pb.TaskClass_TASK_CLASS_BACKGROUND,
+ ContextID: "session-1",
+ RetryPolicy: &pb.RetryPolicy{MaxAttempts: 1},
+ Priority: pb.TaskPriority_TASK_PRIORITY_HIGH,
+ IdempotencyKey: "invocation-1",
+ CorrelationID: "fanout-1",
+ RootTaskID: "root-1",
+ CompletionEvent: completion,
+ ParentTaskID: "parent-1",
+ RequiredDownstreamAuthorityHops: 1,
+ }); err != nil {
+ t.Fatal(err)
+ }
+ message := <-client.RequestQueue()
+ request := message.GetCreateTask()
+ if request == nil {
+ t.Fatal("missing CreateTaskRequest")
+ }
+ if request.GetTaskClass() != pb.TaskClass_TASK_CLASS_BACKGROUND || request.GetContextId() != "session-1" || request.GetIdempotencyKey() != "invocation-1" {
+ t.Fatalf("durable identity fields = class:%s context:%q idempotency:%q", request.GetTaskClass(), request.GetContextId(), request.GetIdempotencyKey())
+ }
+ if request.GetCorrelationId() != "fanout-1" || request.GetRootTaskId() != "root-1" {
+ t.Fatalf("coordination fields = correlation:%q root:%q", request.GetCorrelationId(), request.GetRootTaskId())
+ }
+ if request.GetParentTaskId() != "parent-1" {
+ t.Fatalf("parent task id = %q", request.GetParentTaskId())
+ }
+ if request.GetRequiredDownstreamAuthorityHops() != 1 {
+ t.Fatalf("required downstream authority hops = %d", request.GetRequiredDownstreamAuthorityHops())
+ }
+ if request.GetTargetOfflinePolicy() != pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE {
+ t.Fatalf("target offline policy = %s", request.GetTargetOfflinePolicy())
+ }
+ if request.GetRetryPolicy().GetMaxAttempts() != 1 || request.GetPriority() != pb.TaskPriority_TASK_PRIORITY_HIGH {
+ t.Fatalf("execution policy = retry:%+v priority:%s", request.GetRetryPolicy(), request.GetPriority())
+ }
+ if request.GetCompletionEvent().GetEventName() != "child.done" {
+ t.Fatalf("completion event = %+v", request.GetCompletionEvent())
+ }
+}
+
+func TestProtoTaskInfoToSDKIncludesCoordinationIdentity(t *testing.T) {
+ got := protoTaskInfoToSDK(&pb.TaskInfo{
+ TaskId: "child-1",
+ ParentTaskId: "parent-1",
+ TaskClass: pb.TaskClass_TASK_CLASS_BACKGROUND,
+ ContextId: "session-1",
+ Priority: pb.TaskPriority_TASK_PRIORITY_HIGH,
+ CorrelationId: "fanout-1",
+ RootTaskId: "root-1",
+ AuthorityMode: "on_behalf_of",
+ SubjectType: "user",
+ SubjectId: "alice",
+ RootSubjectType: "user",
+ RootSubjectId: "alice",
+ AuthorityGrantId: "grant-task",
+ RootAuthorityGrantId: "grant-root",
+ ParentAuthorityGrantId: "grant-parent",
+ CreatorActorId: "agent-parent",
+ DisconnectedAt: 1234,
+ GraceWindowMs: 45000,
+ })
+ if got.ParentTaskID != "parent-1" || got.TaskClass != pb.TaskClass_TASK_CLASS_BACKGROUND.String() || got.ContextID != "session-1" {
+ t.Fatalf("task identity projection = %+v", got)
+ }
+ if got.Priority != pb.TaskPriority_TASK_PRIORITY_HIGH.String() || got.CorrelationID != "fanout-1" || got.RootTaskID != "root-1" {
+ t.Fatalf("task coordination projection = %+v", got)
+ }
+ if got.AuthorityMode != "on_behalf_of" || got.SubjectType != "user" || got.SubjectID != "alice" ||
+ got.RootSubjectType != "user" || got.RootSubjectID != "alice" || got.AuthorityGrantID != "grant-task" ||
+ got.RootAuthorityGrantID != "grant-root" || got.ParentAuthorityGrantID != "grant-parent" || got.CreatorActorID != "agent-parent" {
+ t.Fatalf("task authority projection = %+v", got)
+ }
+ if got.DisconnectedAt != 1234 || got.GraceWindowMS != 45000 {
+ t.Fatalf("task disconnect projection = %+v", got)
+ }
+}
+
func TestBaseClient_CancelTask(t *testing.T) {
cfg := BaseClientConfig{ServerAddr: TestServerAddr}
client, err := NewBaseClient(cfg)
@@ -1349,7 +1565,7 @@ func TestBaseClient_DispatchResponse_TaskQueryResponse_RequestID(t *testing.T) {
ctx := context.Background()
response := &pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_TaskQuery{
- TaskQuery: &pb.TaskQueryResponse{Success: true, TotalCount: 3},
+ TaskQuery: &pb.TaskQueryResponse{Success: true, TotalCount: 3, NextPageToken: "opaque-next-page"},
},
}
@@ -1366,6 +1582,9 @@ func TestBaseClient_DispatchResponse_TaskQueryResponse_RequestID(t *testing.T) {
if resp.TotalCount != 3 {
t.Errorf("TotalCount = %d, want 3", resp.TotalCount)
}
+ if resp.NextPageToken != "opaque-next-page" {
+ t.Errorf("NextPageToken = %q, want opaque-next-page", resp.NextPageToken)
+ }
default:
t.Error("Pending task query request should have been resolved")
}
diff --git a/sdk/go/aether/handlers.go b/sdk/go/aether/handlers.go
index 5bf3b78..412ea1e 100644
--- a/sdk/go/aether/handlers.go
+++ b/sdk/go/aether/handlers.go
@@ -33,6 +33,15 @@ type Message struct {
// MessageType is the type of the message (CHAT, CONTROL, TOOL_CALL, EVENT, METRIC).
MessageType pb.MessageType
+ // Workspace is the gateway-verified logical workspace context for this
+ // message, when one applies.
+ Workspace string
+
+ // AccessReceipt is gateway-authored metadata for a checked send. Nil for
+ // ordinary sends. Consumers should validate its correlation ID, resource,
+ // delivery target, and expiry before acting on it.
+ AccessReceipt *pb.AccessDecisionReceipt
+
// OnBehalfSubject is the gateway-resolved on-behalf-of subject the message
// was sent for, when the sender supplied an OBO AuthorizationContext.
// Gateway-set and spoof-proof (like SourceTopic). Nil for direct (non-OBO)
@@ -40,6 +49,11 @@ type Message struct {
// from the sending identity in SourceTopic.
OnBehalfSubject *pb.PrincipalRef
+ // ForwardedAuthorization is a gateway-derived, target-bound leaf authority
+ // grant for this recipient. Nil for sends that did not explicitly request
+ // continuation. Use Authorization with CheckAccess or BatchCheckAccess.
+ ForwardedAuthorization *pb.ForwardedAuthorization
+
// ReceivedAt is the local time when the message was received.
ReceivedAt time.Time
}
@@ -157,6 +171,11 @@ type TaskAssignment struct {
// Payload is optional binary data carried from the task creator.
Payload []byte
+
+ // Authorization is the task-scoped on-behalf-of authority prepared by the
+ // gateway for this assignee. The grant is audience-bound to the assigned
+ // executor/task and is revoked with the task lifecycle.
+ Authorization *pb.AuthorizationContext
}
// CheckpointResponse represents a response to a checkpoint operation.
@@ -250,6 +269,10 @@ type TaskQueryResponse struct {
// TotalCount is the total number of tasks matching the filter.
TotalCount int32
+
+ // NextPageToken is the opaque cursor for the next LIST page. Empty means
+ // the server did not report another page. Clients must not interpret it.
+ NextPageToken string
}
// TaskInfo represents a task's information.
@@ -292,6 +315,43 @@ type TaskInfo struct {
// Metadata contains task-specific metadata.
Metadata map[string]string
+
+ // AuthorityMode is the persisted task authority mode (direct or
+ // on_behalf_of). The remaining authority fields are public-safe lineage
+ // projections already present on the wire TaskInfo.
+ AuthorityMode string
+ SubjectType string
+ SubjectID string
+ RootSubjectType string
+ RootSubjectID string
+ AuthorityGrantID string
+ RootAuthorityGrantID string
+ ParentAuthorityGrantID string
+ CreatorActorID string
+
+ // ParentTaskID is populated for native tasks created by a task principal.
+ ParentTaskID string
+
+ // TaskClass is the protobuf enum name for the task's UI presentation hint.
+ TaskClass string
+
+ // ContextID groups tasks within one logical session or conversation.
+ ContextID string
+
+ // Priority is the protobuf enum name for the persisted dispatch priority.
+ Priority string
+
+ // CorrelationID groups fan-out tasks for joins and queries.
+ CorrelationID string
+
+ // RootTaskID identifies the top of the task tree or fan-out run.
+ RootTaskID string
+
+ // DisconnectedAt and GraceWindowMS expose the task's persisted
+ // connection-as-heartbeat state. DisconnectedAt is Unix seconds; zero means
+ // the assigned worker is currently connected.
+ DisconnectedAt int64
+ GraceWindowMS int64
}
// TaskOperationResponse represents a response to a task operation.
@@ -588,6 +648,11 @@ type CreateTaskResponse struct {
// connect as TargetIdentity. Empty when the task did not request a
// token (no TargetIdentity) or the issue-token check denied it.
TaskToken string
+
+ // AuthorityGrantID is the task-scoped grant derived from the creator's OBO
+ // authorization. Forward it when delivering the task's work envelope so the
+ // assignee acts with the exact task scope instead of a long-lived grant.
+ AuthorityGrantID string
}
// CreateTaskResponseHandler handles create task responses.
diff --git a/sdk/go/aether/options.go b/sdk/go/aether/options.go
index 0f7fbb9..3680abd 100644
--- a/sdk/go/aether/options.go
+++ b/sdk/go/aether/options.go
@@ -704,6 +704,11 @@ type CreateTaskOptions struct {
// Required for TaskAssignmentTargeted mode.
TargetAgentID string
+ // TargetOfflinePolicy controls TARGETED creation while the exact target is
+ // disconnected. UNSPECIFIED preserves orchestration; QUEUE waits for a
+ // static worker reconnect; REJECT fails creation.
+ TargetOfflinePolicy pb.TargetOfflinePolicy
+
// TargetImplementation is the agent implementation type for pool assignment.
// Required for TaskAssignmentPool mode. When set and AssignmentMode is
// not explicitly specified, the mode is automatically set to POOL.
@@ -723,6 +728,46 @@ type CreateTaskOptions struct {
// Default: TaskAssignmentSelfAssign.
AssignmentMode TaskAssignmentMode
+ // TaskClass is an optional UI presentation hint. It does not affect task
+ // scheduling or authorization.
+ TaskClass pb.TaskClass
+
+ // ContextID groups tasks within one logical session or conversation.
+ ContextID string
+
+ // IdempotencyKey makes task creation exactly-once under request retries. A
+ // duplicate create returns the existing task identity.
+ IdempotencyKey string
+
+ // CorrelationID groups fan-out tasks for joins and queries.
+ CorrelationID string
+
+ // RootTaskID identifies the top of a fan-out task tree. Empty lets the server
+ // derive the root from task identity and nesting.
+ RootTaskID string
+
+ // CompletionEvent optionally publishes terminal task state to the event
+ // plane for workflow joins and other consumers.
+ CompletionEvent *pb.TaskCompletionEvent
+
+ // ParentTaskID requests native parentage when a long-lived worker creates a
+ // nested task for a parent it is currently executing. The gateway validates
+ // that the caller is the active parent's assigned identity. It is a
+ // request-scoped binding and may select a different assigned task than the
+ // connection's startup/task-token association. Empty preserves
+ // connection-associated parent inference.
+ ParentTaskID string
+
+ // RequiredDownstreamAuthorityHops asks the gateway to preserve this many
+ // delegation hops on the task's final execution identity. Currently 0 or 1.
+ // Set to 1 when the worker must explicitly forward authority to one service.
+ RequiredDownstreamAuthorityHops uint32
+
+ // OriginatingScheduleID is reserved for the authenticated WorkflowEngine.
+ // It binds a workflow_schedule authority grant to the exact schedule that
+ // caused this task. Ordinary clients must leave it empty.
+ OriginatingScheduleID string
+
// TargetIdentity is an arbitrary principal address (e.g.
// "sv::sandbox-sidecar::") that the gateway treats as the assignee
// when AssignmentMode is TARGETED and the destination is not an Agent.
@@ -837,6 +882,20 @@ type SendMessageOptions struct {
// context set via WithOBOAuthorization, or build the *pb.AuthorizationContext
// directly. Nil ⇒ direct (non-OBO) send.
Authorization *pb.AuthorizationContext
+
+ // CheckedAccess optionally asks the gateway to authorize an exact logical
+ // resource in addition to the destination topic. An allowed decision is
+ // delivered to the recipient as gateway-authored AccessReceipt metadata;
+ // a denied decision prevents publication.
+ CheckedAccess *pb.ResourceAccessRequest
+
+ // AuthorityContinuation asks the gateway to derive a short-lived,
+ // non-delegable child grant for the concrete recipient and attach it as
+ // trusted ForwardedAuthorization metadata. Service recipients may inherit the
+ // parent ceiling; agent recipients require an invocation-bound, explicitly
+ // attenuated scope. It requires resolved OBO authority with at least one
+ // remaining delegation hop.
+ AuthorityContinuation *pb.AuthorityContinuationRequest
}
// =============================================================================
diff --git a/sdk/go/aether/proxy.go b/sdk/go/aether/proxy.go
index ee5fc37..46e773c 100644
--- a/sdk/go/aether/proxy.go
+++ b/sdk/go/aether/proxy.go
@@ -17,6 +17,7 @@ import (
"time"
pb "github.com/scitrera/aether/api/proto"
+ "google.golang.org/protobuf/proto"
)
// proxyChunkSize is the maximum body size sent inline. Bodies larger than this
@@ -276,6 +277,7 @@ type proxyOptions struct {
streamResponse bool
streamIdleMs int64
streamMaxBytes int64
+ checkedAccess *pb.ResourceAccessRequest
}
// ProxyOpt configures a ProxyHTTP call.
@@ -289,6 +291,19 @@ func WithBackend(name string) ProxyOpt {
return func(o *proxyOptions) { o.backend = name }
}
+// WithCheckedAccess asks the gateway to authorize one exact logical resource
+// before delivering this HTTP request. The access descriptor is cloned so the
+// SDK can supply a missing correlation ID without mutating caller-owned state.
+func WithCheckedAccess(access *pb.ResourceAccessRequest) ProxyOpt {
+ return func(o *proxyOptions) {
+ if access == nil {
+ o.checkedAccess = nil
+ return
+ }
+ o.checkedAccess = proto.Clone(access).(*pb.ResourceAccessRequest)
+ }
+}
+
// WithStreamResponse opts into unbounded response streaming (SSE / log tails
// / model token streams). When enabled, the request's context deadline is
// the time-to-first-byte deadline only; subsequent body bytes are governed
@@ -388,6 +403,12 @@ func (c *BaseClient) ProxyHTTP(ctx context.Context, target string, req *http.Req
StreamIdleTimeoutMs: o.streamIdleMs,
MaxResponseBodyBytes: o.streamMaxBytes,
}
+ if o.checkedAccess != nil {
+ if o.checkedAccess.GetCorrelationId() == "" {
+ o.checkedAccess.CorrelationId = requestID
+ }
+ proxyReq.CheckedAccess = o.checkedAccess
+ }
if !chunked {
proxyReq.Body = body
diff --git a/sdk/go/aether/proxy_test.go b/sdk/go/aether/proxy_test.go
index 1fe6a6c..7c6bf00 100644
--- a/sdk/go/aether/proxy_test.go
+++ b/sdk/go/aether/proxy_test.go
@@ -700,6 +700,55 @@ func TestProxyHTTP_WithBackend(t *testing.T) {
<-done
}
+func TestProxyHTTP_WithCheckedAccessClonesAndBindsCorrelation(t *testing.T) {
+ client := newConnectedBaseClient(t)
+ req := fakeHTTPRequest(t, "GET", "http://ignored/v1/vfs/entry-1", nil)
+ access := &pb.ResourceAccessRequest{
+ ResourceType: "vfs",
+ ResourceId: "workspaces/ws-1/entries/entry-1",
+ Operation: "read",
+ Workspace: "ws-1",
+ RequiredAccessLevel: 10,
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
+ defer cancel()
+ done := make(chan error, 1)
+ go func() {
+ _, err := client.ProxyHTTP(ctx, "sv::data-connectors", req, WithCheckedAccess(access))
+ done <- err
+ }()
+
+ var pr *pb.ProxyHttpRequest
+ deadline := time.Now().Add(150 * time.Millisecond)
+ for time.Now().Before(deadline) && pr == nil {
+ select {
+ case msg := <-client.RequestQueue():
+ pr = msg.GetProxyHttpRequest()
+ default:
+ time.Sleep(2 * time.Millisecond)
+ }
+ }
+ if pr == nil {
+ <-done
+ t.Fatal("no ProxyHttpRequest in queue")
+ }
+ if pr.GetCheckedAccess().GetCorrelationId() != pr.GetRequestId() {
+ t.Fatalf("correlation_id = %q, request_id = %q", pr.GetCheckedAccess().GetCorrelationId(), pr.GetRequestId())
+ }
+ if access.GetCorrelationId() != "" {
+ t.Fatalf("WithCheckedAccess mutated caller-owned request: %+v", access)
+ }
+ if pr.GetCheckedAccess() == access {
+ t.Fatal("checked access was not cloned")
+ }
+
+ client.resolveProxyResponse(pr.GetRequestId(), &pb.ProxyHttpResponse{
+ RequestId: pr.GetRequestId(), StatusCode: 200,
+ })
+ <-done
+}
+
// TestProxyHTTP_NoBackendOption verifies that omitting WithBackend leaves
// the BackendName field empty (legacy behaviour).
func TestProxyHTTP_NoBackendOption(t *testing.T) {
diff --git a/sdk/go/aether/reconnect_run_test.go b/sdk/go/aether/reconnect_run_test.go
new file mode 100644
index 0000000..24142af
--- /dev/null
+++ b/sdk/go/aether/reconnect_run_test.go
@@ -0,0 +1,134 @@
+package aether
+
+import (
+ "context"
+ "net"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ pb "github.com/scitrera/aether/api/proto"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+type reconnectRunGateway struct {
+ pb.UnimplementedAetherGatewayServer
+
+ disconnectWithSignal bool
+ connections atomic.Int32
+}
+
+func (g *reconnectRunGateway) Connect(stream grpc.BidiStreamingServer[pb.UpstreamMessage, pb.DownstreamMessage]) error {
+ connection := g.connections.Add(1)
+ if _, err := stream.Recv(); err != nil {
+ return err
+ }
+ if err := stream.Send(newMockConnectionAck("reconnect-run", connection > 1)); err != nil {
+ return err
+ }
+
+ if connection == 1 {
+ if g.disconnectWithSignal {
+ if err := stream.Send(&pb.DownstreamMessage{Payload: &pb.DownstreamMessage_Signal{
+ Signal: &pb.Signal{Type: pb.Signal_GRACEFUL_DISCONNECT, Reason: "cycle connection"},
+ }}); err != nil {
+ return err
+ }
+ <-stream.Context().Done()
+ return nil
+ }
+ return status.Error(codes.Unavailable, "cycle connection")
+ }
+
+ if err := stream.Send(newMockIncomingMessage("test-source", []byte("after reconnect"))); err != nil {
+ return err
+ }
+ <-stream.Context().Done()
+ return nil
+}
+
+func TestBaseClientRunContinuesAfterSuccessfulReconnect(t *testing.T) {
+ tests := []struct {
+ name string
+ disconnectWithSignal bool
+ }{
+ {name: "receive error"},
+ {name: "graceful disconnect signal", disconnectWithSignal: true},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("listen: %v", err)
+ }
+ defer listener.Close()
+
+ gateway := &reconnectRunGateway{disconnectWithSignal: tt.disconnectWithSignal}
+ server := grpc.NewServer()
+ pb.RegisterAetherGatewayServer(server, gateway)
+ go func() { _ = server.Serve(listener) }()
+ defer server.Stop()
+
+ client, err := NewAgentClient(AgentOptions{
+ ClientOptions: ClientOptions{
+ ServerAddr: listener.Addr().String(),
+ Connection: ConnectionOptions{
+ AutoReconnect: true,
+ MaxRetries: 3,
+ InitialBackoff: time.Millisecond,
+ MaxBackoff: 5 * time.Millisecond,
+ BackoffMultiplier: 1,
+ ConnectTimeout: time.Second,
+ },
+ },
+ Workspace: "test-workspace",
+ Implementation: "reconnect-run",
+ Specifier: tt.name,
+ })
+ if err != nil {
+ t.Fatalf("NewAgentClient: %v", err)
+ }
+ defer client.Close()
+
+ received := make(chan string, 1)
+ client.OnMessage(func(_ context.Context, message *Message) error {
+ received <- string(message.Payload)
+ return nil
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := client.Connect(ctx); err != nil {
+ t.Fatalf("Connect: %v", err)
+ }
+
+ done := make(chan error, 1)
+ go func() { done <- client.Run(ctx) }()
+
+ select {
+ case payload := <-received:
+ if payload != "after reconnect" {
+ t.Fatalf("payload = %q, want after reconnect", payload)
+ }
+ case err := <-done:
+ t.Fatalf("Run exited before receiving from reconnected stream: %v", err)
+ case <-ctx.Done():
+ t.Fatal("timed out waiting for message after reconnect")
+ }
+
+ if got := gateway.connections.Load(); got < 2 {
+ t.Fatalf("connections = %d, want at least 2", got)
+ }
+ cancel()
+ select {
+ case <-done:
+ case <-time.After(time.Second):
+ t.Fatal("Run did not exit after context cancellation")
+ }
+ })
+ }
+}
diff --git a/sdk/go/aether/version.go b/sdk/go/aether/version.go
index 932e27f..2f98c4c 100644
--- a/sdk/go/aether/version.go
+++ b/sdk/go/aether/version.go
@@ -2,4 +2,4 @@ package aether
// Version is the current release version of the Aether Go SDK.
// This is updated automatically by scripts/update-versions.py.
-const Version = "0.2.2"
+const Version = "0.2.3"
diff --git a/sdk/go/aether/workflow_ops.go b/sdk/go/aether/workflow_ops.go
index 05eaccd..4cda104 100644
--- a/sdk/go/aether/workflow_ops.go
+++ b/sdk/go/aether/workflow_ops.go
@@ -12,6 +12,8 @@ package aether
import (
"context"
+ "encoding/json"
+ "fmt"
"sync"
"time"
@@ -21,12 +23,39 @@ import (
// DefaultWorkflowTimeout is the default timeout for synchronous workflow operations.
const DefaultWorkflowTimeout = 10 * time.Second
+// WorkflowScheduleAuthorityPolicyVersion is the policy shape understood by
+// the current gateway and WorkflowEngine private store.
+const WorkflowScheduleAuthorityPolicyVersion uint32 = 1
+
// WorkflowOps provides workflow management operations on a client.
type WorkflowOps struct {
client *BaseClient
syncMu sync.Mutex // serializes synchronous workflow operations
}
+// WorkflowScheduleOperationOptions carries transport-owned schedule authority.
+// Authority never enters the schedule JSON payload.
+type WorkflowScheduleOperationOptions struct {
+ Authorization *pb.AuthorizationContext
+ AuthorityScope *pb.WorkflowScheduleAuthorityScope
+}
+
+type workflowScheduleIdentity struct {
+ ID string `json:"id"`
+ Workspace string `json:"workspace"`
+}
+
+func decodeWorkflowScheduleIdentity(data []byte) (workflowScheduleIdentity, error) {
+ var identity workflowScheduleIdentity
+ if err := json.Unmarshal(data, &identity); err != nil {
+ return identity, fmt.Errorf("decode workflow schedule identity: %w", err)
+ }
+ if identity.ID == "" || identity.Workspace == "" || identity.Workspace == "*" {
+ return identity, fmt.Errorf("workflow schedule id and exact workspace are required")
+ }
+ return identity, nil
+}
+
// newWorkflowOps creates a new WorkflowOps helper for a client.
func newWorkflowOps(client *BaseClient) *WorkflowOps {
return &WorkflowOps{client: client}
@@ -166,35 +195,75 @@ func (w *WorkflowOps) DeleteWorkflow(ctx context.Context, id string) (*WorkflowR
// ListSchedules lists all schedules for a workspace.
func (w *WorkflowOps) ListSchedules(ctx context.Context, workspace string) (*WorkflowResponse, error) {
+ return w.ListSchedulesAuthorized(ctx, workspace, nil)
+}
+
+// ListSchedulesAuthorized lists schedules using an optional OBO context.
+func (w *WorkflowOps) ListSchedulesAuthorized(ctx context.Context, workspace string, authorization *pb.AuthorizationContext) (*WorkflowResponse, error) {
+ if workspace == "" || workspace == "*" {
+ return nil, fmt.Errorf("an exact workflow schedule workspace is required")
+ }
return w.SendOpSync(ctx, &pb.WorkflowOperation{
- Op: pb.WorkflowOperation_LIST_SCHEDULES,
- Workspace: workspace,
+ Op: pb.WorkflowOperation_LIST_SCHEDULES,
+ Workspace: workspace,
+ Authorization: authorization,
}, 0)
}
// CreateSchedule creates a new schedule from JSON data.
func (w *WorkflowOps) CreateSchedule(ctx context.Context, data []byte) (*WorkflowResponse, error) {
+ return w.CreateScheduleWithOptions(ctx, data, WorkflowScheduleOperationOptions{})
+}
+
+// CreateScheduleWithOptions creates a schedule with transport-owned authority.
+func (w *WorkflowOps) CreateScheduleWithOptions(ctx context.Context, data []byte, opts WorkflowScheduleOperationOptions) (*WorkflowResponse, error) {
+ identity, err := decodeWorkflowScheduleIdentity(data)
+ if err != nil {
+ return nil, err
+ }
return w.SendOpSync(ctx, &pb.WorkflowOperation{
- Op: pb.WorkflowOperation_CREATE_SCHEDULE,
- Data: data,
+ Op: pb.WorkflowOperation_CREATE_SCHEDULE, Id: identity.ID,
+ Workspace: identity.Workspace, Data: data,
+ Authorization: opts.Authorization, ScheduleAuthorityScope: opts.AuthorityScope,
}, 0)
}
// UpsertSchedule creates or updates a schedule idempotently from JSON data.
-// If a schedule with the given ID exists, its configuration is updated but
-// next_fire_at and last_fired_at are preserved.
+// If a schedule with the given ID exists, its configuration and last_fired_at
+// are preserved. next_fire_at is preserved for payload-only changes and
+// recomputed when schedule_type or schedule_expr changes.
func (w *WorkflowOps) UpsertSchedule(ctx context.Context, data []byte) (*WorkflowResponse, error) {
+ return w.UpsertScheduleWithOptions(ctx, data, WorkflowScheduleOperationOptions{})
+}
+
+// UpsertScheduleWithOptions idempotently updates configuration and replaces
+// its private schedule authority in the same authenticated operation.
+func (w *WorkflowOps) UpsertScheduleWithOptions(ctx context.Context, data []byte, opts WorkflowScheduleOperationOptions) (*WorkflowResponse, error) {
+ identity, err := decodeWorkflowScheduleIdentity(data)
+ if err != nil {
+ return nil, err
+ }
return w.SendOpSync(ctx, &pb.WorkflowOperation{
- Op: pb.WorkflowOperation_UPSERT_SCHEDULE,
- Data: data,
+ Op: pb.WorkflowOperation_UPSERT_SCHEDULE, Id: identity.ID,
+ Workspace: identity.Workspace, Data: data,
+ Authorization: opts.Authorization, ScheduleAuthorityScope: opts.AuthorityScope,
}, 0)
}
-// DeleteSchedule deletes a schedule by ID.
-func (w *WorkflowOps) DeleteSchedule(ctx context.Context, id string) (*WorkflowResponse, error) {
+// DeleteSchedule deletes a schedule by exact workspace and ID.
+func (w *WorkflowOps) DeleteSchedule(ctx context.Context, workspace, id string) (*WorkflowResponse, error) {
+ return w.DeleteScheduleAuthorized(ctx, workspace, id, nil)
+}
+
+// DeleteScheduleAuthorized deletes an exact workspace schedule under an
+// optional OBO authority context.
+func (w *WorkflowOps) DeleteScheduleAuthorized(ctx context.Context, workspace, id string, authorization *pb.AuthorizationContext) (*WorkflowResponse, error) {
+ if workspace == "" || workspace == "*" || id == "" {
+ return nil, fmt.Errorf("workflow schedule id and exact workspace are required")
+ }
return w.SendOpSync(ctx, &pb.WorkflowOperation{
- Op: pb.WorkflowOperation_DELETE_SCHEDULE,
- Id: id,
+ Op: pb.WorkflowOperation_DELETE_SCHEDULE, Id: id,
+ Workspace: workspace, Authorization: authorization,
}, 0)
}
diff --git a/sdk/go/go.mod b/sdk/go/go.mod
index 987114c..8ded915 100644
--- a/sdk/go/go.mod
+++ b/sdk/go/go.mod
@@ -1,12 +1,13 @@
module github.com/scitrera/aether/sdk/go
-go 1.25.12
+go 1.25.14
require (
+ github.com/containerd/errdefs v1.0.0
github.com/docker/docker v28.5.2+incompatible
- github.com/scitrera/aether/api v0.2.2
+ github.com/scitrera/aether/api v0.2.3
github.com/scitrera/go-backpressure v0.1.1
- google.golang.org/grpc v1.81.1
+ google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
)
@@ -14,7 +15,6 @@ require (
github.com/Microsoft/go-winio v0.4.21 // indirect
github.com/bradenaw/juniper v0.10.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
- github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
@@ -37,11 +37,11 @@ require (
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43 // indirect
- golang.org/x/net v0.54.0 // indirect
- golang.org/x/sys v0.44.0 // indirect
+ golang.org/x/net v0.55.0 // indirect
+ golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.14.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gotest.tools/v3 v3.5.2 // indirect
)
diff --git a/sdk/go/go.sum b/sdk/go/go.sum
index c69d363..cd17e2d 100644
--- a/sdk/go/go.sum
+++ b/sdk/go/go.sum
@@ -87,26 +87,26 @@ go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjce
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43 h1:Xo03zeNci09uW1tocp7+8X7YizAdkD/BKNkl9lsqKHQ=
golang.org/x/exp v0.0.0-20220217172124-1812c5b45e43/go.mod h1:lRnflEfy7nRvpQCcpkwaSP1nkrSyjkyFNcqXKfSXLMc=
-golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
-golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
-golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
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-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
-google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
-google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
-google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
+google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
+google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
+google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/sdk/python-ag2/pyproject.toml b/sdk/python-ag2/pyproject.toml
index da2cd74..9cf6919 100644
--- a/sdk/python-ag2/pyproject.toml
+++ b/sdk/python-ag2/pyproject.toml
@@ -36,7 +36,7 @@ classifiers = [
]
dependencies = [
"scitrera-aether-client>=0.2.0",
- "ag2>=0.10",
+ "autogen>=0.14.1,<1",
"pydantic>=2.0",
]
diff --git a/sdk/python-client/pyproject.toml b/sdk/python-client/pyproject.toml
index d37db69..36bfb5d 100644
--- a/sdk/python-client/pyproject.toml
+++ b/sdk/python-client/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "scitrera-aether-client"
-version = "0.2.2"
+version = "0.2.3"
description = "Python client SDK for Scitrera Aether distributed control plane"
readme = "README.md"
license = "Apache-2.0"
diff --git a/sdk/python-client/scitrera_aether_client/__init__.py b/sdk/python-client/scitrera_aether_client/__init__.py
index c3840dc..411774b 100644
--- a/sdk/python-client/scitrera_aether_client/__init__.py
+++ b/sdk/python-client/scitrera_aether_client/__init__.py
@@ -1,4 +1,7 @@
-__version__ = "0.2.2"
+__version__ = "0.2.3"
+
+# Current deterministic WorkflowScheduleAuthorityScope policy shape.
+WORKFLOW_SCHEDULE_AUTHORITY_POLICY_VERSION = 1
# Import the proxy module for its side effect: installs the
# ``ProxyHttpResponse`` / ``ProxyHttpBodyChunk`` dispatcher hook on
@@ -24,6 +27,10 @@
SELF_ASSIGN,
TARGETED,
POOL,
+ TARGET_OFFLINE_UNSPECIFIED,
+ TARGET_OFFLINE_ORCHESTRATE,
+ TARGET_OFFLINE_QUEUE,
+ TARGET_OFFLINE_REJECT,
# KV operation type constants
KV_GET,
diff --git a/sdk/python-client/scitrera_aether_client/_common.py b/sdk/python-client/scitrera_aether_client/_common.py
index 5ee2b3d..91b3e0e 100644
--- a/sdk/python-client/scitrera_aether_client/_common.py
+++ b/sdk/python-client/scitrera_aether_client/_common.py
@@ -353,6 +353,12 @@ def create_topic_global_users(workspace: str) -> str:
TARGETED = aether_pb2.TARGETED
POOL = aether_pb2.POOL
+# Exact-target behavior while the target identity is disconnected.
+TARGET_OFFLINE_UNSPECIFIED = aether_pb2.TARGET_OFFLINE_POLICY_UNSPECIFIED
+TARGET_OFFLINE_ORCHESTRATE = aether_pb2.TARGET_OFFLINE_POLICY_ORCHESTRATE
+TARGET_OFFLINE_QUEUE = aether_pb2.TARGET_OFFLINE_POLICY_QUEUE
+TARGET_OFFLINE_REJECT = aether_pb2.TARGET_OFFLINE_POLICY_REJECT
+
# KV operation types
KV_GET = aether_pb2.KVOperation.GET
KV_PUT = aether_pb2.KVOperation.PUT
diff --git a/sdk/python-client/scitrera_aether_client/admin.py b/sdk/python-client/scitrera_aether_client/admin.py
index a006a9f..16e28f0 100644
--- a/sdk/python-client/scitrera_aether_client/admin.py
+++ b/sdk/python-client/scitrera_aether_client/admin.py
@@ -27,6 +27,7 @@
from typing import Dict, List, Optional
from .client import BaseAetherClient
+from .admin_async import _build_agent_registration_info
from .proto import aether_pb2
@@ -647,6 +648,53 @@ def get_agent(self, implementation: str, timeout: float = 10.0):
)
return self._client.agent_op(op, timeout=timeout)
+ def register_agent(self,
+ implementation: str,
+ orchestrator_profile: str = "",
+ description: str = "",
+ launch_params: Optional[dict] = None,
+ capabilities: Optional[dict] = None,
+ extensions: Optional[list] = None,
+ resource_schema: Optional[list] = None,
+ timeout: float = 10.0):
+ """Register a new agent implementation. See :meth:`AsyncAdminClient.register_agent`."""
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.REGISTER,
+ agent=_build_agent_registration_info(
+ implementation, orchestrator_profile, description,
+ launch_params, capabilities, extensions, resource_schema,
+ ),
+ )
+ return self._client.agent_op(op, timeout=timeout)
+
+ def update_agent(self,
+ implementation: str,
+ orchestrator_profile: str = "",
+ description: str = "",
+ launch_params: Optional[dict] = None,
+ capabilities: Optional[dict] = None,
+ extensions: Optional[list] = None,
+ resource_schema: Optional[list] = None,
+ timeout: float = 10.0):
+ """Update (upsert) an agent registration. See :meth:`AsyncAdminClient.update_agent`."""
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.UPDATE,
+ implementation=implementation,
+ agent=_build_agent_registration_info(
+ implementation, orchestrator_profile, description,
+ launch_params, capabilities, extensions, resource_schema,
+ ),
+ )
+ return self._client.agent_op(op, timeout=timeout)
+
+ def delete_agent(self, implementation: str, timeout: float = 10.0):
+ """Remove an agent implementation. See :meth:`AsyncAdminClient.delete_agent`."""
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.DELETE,
+ implementation=implementation,
+ )
+ return self._client.agent_op(op, timeout=timeout)
+
# ------------------------------------------------------------------
# Workflow Operations (admin-flavored)
# ------------------------------------------------------------------
diff --git a/sdk/python-client/scitrera_aether_client/admin_async.py b/sdk/python-client/scitrera_aether_client/admin_async.py
index 916eca3..195c3b8 100644
--- a/sdk/python-client/scitrera_aether_client/admin_async.py
+++ b/sdk/python-client/scitrera_aether_client/admin_async.py
@@ -20,6 +20,43 @@
from .proto import aether_pb2
+def _build_agent_registration_info(
+ implementation: str,
+ orchestrator_profile: str = "",
+ description: str = "",
+ launch_params: Optional[dict] = None,
+ capabilities: Optional[dict] = None,
+ extensions: Optional[list] = None,
+ resource_schema: Optional[list] = None,
+) -> "aether_pb2.AgentRegistrationInfo":
+ """Build an ``AgentRegistrationInfo`` proto for register/update.
+
+ ``registered_at`` / ``updated_at`` are server-owned and never set here.
+ ``resource_schema`` accepts a list of dicts
+ ``{resource_type_prefix, permission_verbs: [...], resource_id_schema}`` or
+ pre-built :class:`aether_pb2.AgentResourceSchemaEntry` protos.
+ """
+ schema_entries = []
+ for entry in (resource_schema or []):
+ if isinstance(entry, aether_pb2.AgentResourceSchemaEntry):
+ schema_entries.append(entry)
+ else:
+ schema_entries.append(aether_pb2.AgentResourceSchemaEntry(
+ resource_type_prefix=entry.get("resource_type_prefix", ""),
+ permission_verbs=list(entry.get("permission_verbs", []) or []),
+ resource_id_schema=entry.get("resource_id_schema", "") or "",
+ ))
+ return aether_pb2.AgentRegistrationInfo(
+ implementation=implementation,
+ orchestrator_profile=orchestrator_profile or "",
+ description=description or "",
+ launch_params={str(k): str(v) for k, v in (launch_params or {}).items()},
+ capabilities={str(k): bool(v) for k, v in (capabilities or {}).items()},
+ extensions=list(extensions or []),
+ resource_schema=schema_entries,
+ )
+
+
class AsyncAdminClient:
"""Asynchronous administrative client.
@@ -599,6 +636,57 @@ async def get_agent(self, implementation: str, timeout: float = 10.0):
)
return await self._client.agent_op(op, timeout=timeout)
+ async def register_agent(self,
+ implementation: str,
+ orchestrator_profile: str = "",
+ description: str = "",
+ launch_params: Optional[dict] = None,
+ capabilities: Optional[dict] = None,
+ extensions: Optional[list] = None,
+ resource_schema: Optional[list] = None,
+ timeout: float = 10.0):
+ """Register a new agent implementation in the orchestration registry.
+
+ ``implementation`` is the unique registry key. See
+ :func:`_build_agent_registration_info` for the remaining fields.
+ """
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.REGISTER,
+ agent=_build_agent_registration_info(
+ implementation, orchestrator_profile, description,
+ launch_params, capabilities, extensions, resource_schema,
+ ),
+ )
+ return await self._client.agent_op(op, timeout=timeout)
+
+ async def update_agent(self,
+ implementation: str,
+ orchestrator_profile: str = "",
+ description: str = "",
+ launch_params: Optional[dict] = None,
+ capabilities: Optional[dict] = None,
+ extensions: Optional[list] = None,
+ resource_schema: Optional[list] = None,
+ timeout: float = 10.0):
+ """Update (upsert) an existing agent registration by ``implementation``."""
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.UPDATE,
+ implementation=implementation,
+ agent=_build_agent_registration_info(
+ implementation, orchestrator_profile, description,
+ launch_params, capabilities, extensions, resource_schema,
+ ),
+ )
+ return await self._client.agent_op(op, timeout=timeout)
+
+ async def delete_agent(self, implementation: str, timeout: float = 10.0):
+ """Remove an agent implementation from the orchestration registry."""
+ op = aether_pb2.AgentOperation(
+ op=aether_pb2.AgentOperation.DELETE,
+ implementation=implementation,
+ )
+ return await self._client.agent_op(op, timeout=timeout)
+
# ------------------------------------------------------------------
# Workflow Operations (admin-flavored)
# ------------------------------------------------------------------
diff --git a/sdk/python-client/scitrera_aether_client/client.py b/sdk/python-client/scitrera_aether_client/client.py
index c232434..9a5786d 100644
--- a/sdk/python-client/scitrera_aether_client/client.py
+++ b/sdk/python-client/scitrera_aether_client/client.py
@@ -29,6 +29,7 @@
SELF_ASSIGN,
TARGETED,
POOL,
+ TARGET_OFFLINE_UNSPECIFIED,
_scope_to_proto,
_env_tls_kwargs_filter,
)
@@ -684,6 +685,13 @@ def _listen_loop(self, responses):
pending = self._pending_requests.pop(req_id, None) if req_id else None
if pending:
pending.put(resp)
+ elif payload_type in ("access_check_response", "batch_access_check_response"):
+ resp = getattr(response, payload_type)
+ req_id = resp.request_id
+ with self._pending_requests_lock:
+ pending = self._pending_requests.pop(req_id, None) if req_id else None
+ if pending:
+ pending.put(resp)
elif payload_type == "progress_update":
if self.on_progress:
self.on_progress(response.progress_update)
@@ -990,7 +998,10 @@ def _send_sync_op(self, message: aether_pb2.UpstreamMessage, request_id: str,
self._pending_requests.pop(request_id, None)
def _send_message(self, target_topic: str, payload: bytes, message_type: int = aether_pb2.OPAQUE,
- app_workspace: str = ""):
+ app_workspace: str = "",
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ checked_access: Optional[aether_pb2.ResourceAccessRequest] = None,
+ authority_continuation: Optional[aether_pb2.AuthorityContinuationRequest] = None):
"""Send a message to a target topic.
``app_workspace`` is an optional hint carrying the user's active app
@@ -1005,8 +1016,56 @@ def _send_message(self, target_topic: str, payload: bytes, message_type: int = a
message_type=message_type, # type: ignore[arg-type]
app_workspace=app_workspace,
)
+ if authorization is not None:
+ msg.authorization.CopyFrom(authorization)
+ if checked_access is not None:
+ msg.checked_access.CopyFrom(checked_access)
+ if authority_continuation is not None:
+ msg.authority_continuation.CopyFrom(authority_continuation)
self.request_queue.put(aether_pb2.UpstreamMessage(send=msg))
+ def send_checked_message(self, target_topic: str, payload: bytes,
+ checked_access: aether_pb2.ResourceAccessRequest,
+ message_type: int = aether_pb2.OPAQUE,
+ app_workspace: str = "",
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ authority_continuation: Optional[aether_pb2.AuthorityContinuationRequest] = None) -> None:
+ """Send only when the gateway allows ``checked_access``."""
+ self._send_message(target_topic, payload, message_type, app_workspace,
+ authorization, checked_access, authority_continuation)
+
+ def check_access(self, access: aether_pb2.ResourceAccessRequest,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ timeout: float = 10.0):
+ """Evaluate one logical resource; denial returns an allowed=false receipt."""
+ request_id = str(uuid.uuid4())
+ op = aether_pb2.AccessCheckOperation(request_id=request_id, access=access)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ response = self._send_sync_op(
+ aether_pb2.UpstreamMessage(access_check=op), request_id, timeout)
+ if response is None:
+ return None
+ if not response.success:
+ raise InvalidArgumentError(response.error, code="ACCESS_CHECK_FAILED")
+ return response.decision
+
+ def batch_check_access(self, access: List[aether_pb2.ResourceAccessRequest],
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ timeout: float = 10.0):
+ """Evaluate 1-100 resources and return ordered decision receipts."""
+ request_id = str(uuid.uuid4())
+ op = aether_pb2.BatchAccessCheckOperation(request_id=request_id, access=access)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ response = self._send_sync_op(
+ aether_pb2.UpstreamMessage(batch_access_check=op), request_id, timeout)
+ if response is None:
+ return None
+ if not response.success:
+ raise InvalidArgumentError(response.error, code="BATCH_ACCESS_CHECK_FAILED")
+ return list(response.decisions)
+
def _switch_workspace(self, new_workspace_id: str):
"""Switch to a different workspace."""
sw = aether_pb2.SwitchWorkspace(new_workspace_id=new_workspace_id)
@@ -1209,7 +1268,10 @@ def create_task(self, task_type: str, workspace: str,
assignment_mode: int = SELF_ASSIGN,
context_id: str = "",
priority: int = 0,
- retry_policy: Optional[aether_pb2.RetryPolicy] = None) -> None:
+ retry_policy: Optional[aether_pb2.RetryPolicy] = None,
+ parent_task_id: str = "",
+ required_downstream_authority_hops: int = 0,
+ target_offline_policy: int = TARGET_OFFLINE_UNSPECIFIED) -> None:
"""
Create a new task.
@@ -1227,6 +1289,10 @@ def create_task(self, task_type: str, workspace: str,
priority: Optional dispatch priority (TaskPriority enum value). Higher
priority pending tasks are delivered before lower ones. 0 (UNSPECIFIED)
is normalized to NORMAL by the server.
+ parent_task_id: Optional active parent assigned to this calling identity.
+ The gateway validates and applies the binding only to this request.
+ target_offline_policy: TARGETED behavior while the exact target is
+ disconnected. Defaults to orchestration-compatible UNSPECIFIED.
"""
if target_agent_id and assignment_mode == SELF_ASSIGN:
assignment_mode = TARGETED
@@ -1244,6 +1310,9 @@ def create_task(self, task_type: str, workspace: str,
context_id=context_id,
priority=priority, # type: ignore[arg-type]
retry_policy=retry_policy,
+ parent_task_id=parent_task_id,
+ required_downstream_authority_hops=required_downstream_authority_hops,
+ target_offline_policy=target_offline_policy, # type: ignore[arg-type]
)
self.request_queue.put(aether_pb2.UpstreamMessage(create_task=req))
@@ -1258,7 +1327,10 @@ def create_task_sync(self, task_type: str, workspace: str,
context_id: str = "",
priority: int = 0,
retry_policy: Optional[aether_pb2.RetryPolicy] = None,
- timeout: float = 10.0) -> Optional[aether_pb2.CreateTaskResponse]:
+ timeout: float = 10.0,
+ parent_task_id: str = "",
+ required_downstream_authority_hops: int = 0,
+ target_offline_policy: int = TARGET_OFFLINE_UNSPECIFIED) -> Optional[aether_pb2.CreateTaskResponse]:
"""
Create a new task and wait for the server's response containing the task_id.
@@ -1282,6 +1354,10 @@ def create_task_sync(self, task_type: str, workspace: str,
context_id: Optional client-minted session identifier (A2A contextId). Tasks
sharing a context_id are groupable via TaskFilter.context_id.
timeout: Timeout in seconds (default 10.0)
+ parent_task_id: Optional active parent assigned to this calling identity.
+ The gateway validates and applies the binding only to this request.
+ target_offline_policy: TARGETED behavior while the exact target is
+ disconnected. Defaults to orchestration-compatible UNSPECIFIED.
Returns:
CreateTaskResponse with task_id, status, etc., or None on timeout
@@ -1305,6 +1381,9 @@ def create_task_sync(self, task_type: str, workspace: str,
request_id=request_id,
priority=priority, # type: ignore[arg-type]
retry_policy=retry_policy,
+ parent_task_id=parent_task_id,
+ required_downstream_authority_hops=required_downstream_authority_hops,
+ target_offline_policy=target_offline_policy, # type: ignore[arg-type]
)
return self._send_sync_op(
aether_pb2.UpstreamMessage(create_task=req), request_id, timeout,
@@ -3185,12 +3264,14 @@ def audit_query_sync(self,
def create_schedule_sync(self, schedule_id: str, name: str,
schedule_type: str, schedule_expr: str,
+ workspace: str,
action: Optional[dict] = None,
workflow_id: str = "",
- workspace: str = "*",
miss_policy: str = "skip",
max_concurrent: int = 0,
- timeout: float = 10.0):
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ authority_scope: Optional[aether_pb2.WorkflowScheduleAuthorityScope] = None):
"""Create a new schedule (blocking).
Args:
@@ -3200,7 +3281,7 @@ def create_schedule_sync(self, schedule_id: str, name: str,
schedule_expr: Cron expression, Go duration (e.g. "21600s"), or RFC3339 timestamp.
action: Action definition dict (required if no workflow_id).
workflow_id: Workflow ID to trigger (required if no action).
- workspace: Workspace scope (default "*").
+ workspace: Exact workspace scope.
miss_policy: "skip", "fire_once", or "fire_all" (default "skip").
max_concurrent: Max concurrent executions; 0=unlimited, 1=no overlap (default 0).
timeout: RPC timeout in seconds.
@@ -3209,6 +3290,8 @@ def create_schedule_sync(self, schedule_id: str, name: str,
WorkflowResponse protobuf or None on timeout.
"""
import json as _json
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
data = {
"id": schedule_id,
"name": name,
@@ -3225,18 +3308,26 @@ def create_schedule_sync(self, schedule_id: str, name: str,
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.CREATE_SCHEDULE,
+ id=schedule_id,
+ workspace=workspace,
data=_json.dumps(data).encode(),
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ if authority_scope is not None:
+ op.schedule_authority_scope.CopyFrom(authority_scope)
return self.workflow_op(op, timeout=timeout)
def upsert_schedule_sync(self, schedule_id: str, name: str,
schedule_type: str, schedule_expr: str,
+ workspace: str,
action: Optional[dict] = None,
workflow_id: str = "",
- workspace: str = "*",
miss_policy: str = "skip",
max_concurrent: int = 0,
- timeout: float = 10.0):
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ authority_scope: Optional[aether_pb2.WorkflowScheduleAuthorityScope] = None):
"""Create or update a schedule idempotently (blocking).
Same parameters as create_schedule_sync. If a schedule with the given ID
@@ -3247,6 +3338,8 @@ def upsert_schedule_sync(self, schedule_id: str, name: str,
WorkflowResponse protobuf or None on timeout.
"""
import json as _json
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
data = {
"id": schedule_id,
"name": name,
@@ -3263,32 +3356,50 @@ def upsert_schedule_sync(self, schedule_id: str, name: str,
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.UPSERT_SCHEDULE,
+ id=schedule_id,
+ workspace=workspace,
data=_json.dumps(data).encode(),
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ if authority_scope is not None:
+ op.schedule_authority_scope.CopyFrom(authority_scope)
return self.workflow_op(op, timeout=timeout)
- def delete_schedule_sync(self, schedule_id: str, timeout: float = 10.0):
+ def delete_schedule_sync(self, schedule_id: str, workspace: str,
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None):
"""Delete a schedule by ID (blocking).
Returns:
WorkflowResponse protobuf or None on timeout.
"""
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.DELETE_SCHEDULE,
id=schedule_id,
+ workspace=workspace,
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
return self.workflow_op(op, timeout=timeout)
- def list_schedules_sync(self, workspace: str = "*", timeout: float = 10.0):
+ def list_schedules_sync(self, workspace: str, timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None):
"""List all schedules for a workspace (blocking).
Returns:
WorkflowResponse protobuf or None on timeout.
"""
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.LIST_SCHEDULES,
workspace=workspace,
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
return self.workflow_op(op, timeout=timeout)
# ------------------------------------------------------------------
diff --git a/sdk/python-client/scitrera_aether_client/client_async.py b/sdk/python-client/scitrera_aether_client/client_async.py
index 07a4d2b..5aebf2a 100644
--- a/sdk/python-client/scitrera_aether_client/client_async.py
+++ b/sdk/python-client/scitrera_aether_client/client_async.py
@@ -37,6 +37,7 @@
SELF_ASSIGN,
TARGETED,
POOL,
+ TARGET_OFFLINE_UNSPECIFIED,
_scope_to_proto,
_env_tls_kwargs_filter,
)
@@ -798,6 +799,12 @@ async def _listen_loop(self):
pending = self._pending_requests.pop(req_id, None) if req_id else None
if pending and not pending.done():
pending.set_result(resp)
+ elif payload_type in ("access_check_response", "batch_access_check_response"):
+ resp = getattr(response, payload_type)
+ req_id = resp.request_id
+ pending = self._pending_requests.pop(req_id, None) if req_id else None
+ if pending and not pending.done():
+ pending.set_result(resp)
elif payload_type == "resolve_authority_response":
resp = response.resolve_authority_response
req_id = resp.request_id
@@ -1226,7 +1233,9 @@ async def _send_sync_op(self, message: aether_pb2.UpstreamMessage, request_id: s
async def _send_message(self, target_topic: str, payload: bytes,
message_type: int = aether_pb2.OPAQUE,
authorization: Optional[aether_pb2.AuthorizationContext] = None,
- app_workspace: str = ""):
+ app_workspace: str = "",
+ checked_access: Optional[aether_pb2.ResourceAccessRequest] = None,
+ authority_continuation: Optional[aether_pb2.AuthorityContinuationRequest] = None):
"""Send a message to a target topic.
If ``authorization`` is provided, the message is authorized against the
@@ -1242,11 +1251,59 @@ async def _send_message(self, target_topic: str, payload: bytes,
target_topic=target_topic,
payload=payload,
message_type=message_type, # type: ignore[arg-type]
- authorization=authorization,
app_workspace=app_workspace,
)
+ if authorization is not None:
+ msg.authorization.CopyFrom(authorization)
+ if checked_access is not None:
+ msg.checked_access.CopyFrom(checked_access)
+ if authority_continuation is not None:
+ msg.authority_continuation.CopyFrom(authority_continuation)
await self._request_queue.put(aether_pb2.UpstreamMessage(send=msg))
+ async def send_checked_message(self, target_topic: str, payload: bytes,
+ checked_access: aether_pb2.ResourceAccessRequest,
+ message_type: int = aether_pb2.OPAQUE,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ app_workspace: str = "",
+ authority_continuation: Optional[aether_pb2.AuthorityContinuationRequest] = None) -> None:
+ """Send only when the gateway allows ``checked_access``."""
+ await self._send_message(target_topic, payload, message_type,
+ authorization, app_workspace, checked_access,
+ authority_continuation)
+
+ async def check_access(self, access: aether_pb2.ResourceAccessRequest,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ timeout: float = 10.0):
+ """Evaluate one logical resource; denial returns an allowed=false receipt."""
+ request_id = str(uuid.uuid4())
+ op = aether_pb2.AccessCheckOperation(request_id=request_id, access=access)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ response = await self._send_sync_op(
+ aether_pb2.UpstreamMessage(access_check=op), request_id, timeout)
+ if response is None:
+ return None
+ if not response.success:
+ raise InvalidArgumentError(response.error, code="ACCESS_CHECK_FAILED")
+ return response.decision
+
+ async def batch_check_access(self, access: List[aether_pb2.ResourceAccessRequest],
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ timeout: float = 10.0):
+ """Evaluate 1-100 resources and return ordered decision receipts."""
+ request_id = str(uuid.uuid4())
+ op = aether_pb2.BatchAccessCheckOperation(request_id=request_id, access=access)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ response = await self._send_sync_op(
+ aether_pb2.UpstreamMessage(batch_access_check=op), request_id, timeout)
+ if response is None:
+ return None
+ if not response.success:
+ raise InvalidArgumentError(response.error, code="BATCH_ACCESS_CHECK_FAILED")
+ return list(response.decisions)
+
async def _switch_workspace(self, new_workspace_id: str):
"""Switch to a different workspace."""
sw = aether_pb2.SwitchWorkspace(new_workspace_id=new_workspace_id)
@@ -1619,7 +1676,10 @@ async def create_task(self, task_type: str, workspace: str,
task_class: int = 0,
context_id: str = "",
priority: int = 0,
- retry_policy: Optional[aether_pb2.RetryPolicy] = None) -> None:
+ retry_policy: Optional[aether_pb2.RetryPolicy] = None,
+ parent_task_id: str = "",
+ required_downstream_authority_hops: int = 0,
+ target_offline_policy: int = TARGET_OFFLINE_UNSPECIFIED) -> None:
"""
Create a new task.
@@ -1637,6 +1697,10 @@ async def create_task(self, task_type: str, workspace: str,
automatic child grants are minted for assigned workers.
context_id: Optional client-minted session identifier (A2A contextId). Tasks
sharing a context_id are groupable via TaskFilter.context_id.
+ parent_task_id: Optional active parent assigned to this calling identity.
+ The gateway validates and applies the binding only to this request.
+ target_offline_policy: TARGETED behavior while the exact target is
+ disconnected. Defaults to orchestration-compatible UNSPECIFIED.
"""
if target_agent_id and assignment_mode == SELF_ASSIGN:
assignment_mode = TARGETED
@@ -1656,6 +1720,9 @@ async def create_task(self, task_type: str, workspace: str,
context_id=context_id,
priority=priority, # type: ignore[arg-type]
retry_policy=retry_policy,
+ parent_task_id=parent_task_id,
+ required_downstream_authority_hops=required_downstream_authority_hops,
+ target_offline_policy=target_offline_policy, # type: ignore[arg-type]
)
await self._request_queue.put(aether_pb2.UpstreamMessage(create_task=req))
@@ -1672,7 +1739,10 @@ async def create_task_sync(self, task_type: str, workspace: str,
context_id: str = "",
priority: int = 0,
retry_policy: Optional[aether_pb2.RetryPolicy] = None,
- timeout: float = 10.0) -> Optional[aether_pb2.CreateTaskResponse]:
+ timeout: float = 10.0,
+ parent_task_id: str = "",
+ required_downstream_authority_hops: int = 0,
+ target_offline_policy: int = TARGET_OFFLINE_UNSPECIFIED) -> Optional[aether_pb2.CreateTaskResponse]:
"""
Create a new task and wait for the server's response containing the task_id.
@@ -1698,6 +1768,10 @@ async def create_task_sync(self, task_type: str, workspace: str,
context_id: Optional client-minted session identifier (A2A contextId). Tasks
sharing a context_id are groupable via TaskFilter.context_id.
timeout: Timeout in seconds (default 10.0)
+ parent_task_id: Optional active parent assigned to this calling identity.
+ The gateway validates and applies the binding only to this request.
+ target_offline_policy: TARGETED behavior while the exact target is
+ disconnected. Defaults to orchestration-compatible UNSPECIFIED.
Returns:
CreateTaskResponse with task_id, status, etc., or None on timeout
@@ -1723,6 +1797,9 @@ async def create_task_sync(self, task_type: str, workspace: str,
request_id=request_id,
priority=priority, # type: ignore[arg-type]
retry_policy=retry_policy,
+ parent_task_id=parent_task_id,
+ required_downstream_authority_hops=required_downstream_authority_hops,
+ target_offline_policy=target_offline_policy, # type: ignore[arg-type]
)
return await self._send_sync_op(
aether_pb2.UpstreamMessage(create_task=req), request_id, timeout,
@@ -3802,12 +3879,14 @@ async def acl_cleanup_audit_logs(self, retention_days: int = 90,
async def create_schedule(self, schedule_id: str, name: str,
schedule_type: str, schedule_expr: str,
+ workspace: str,
action: Optional[dict] = None,
workflow_id: str = "",
- workspace: str = "*",
miss_policy: str = "skip",
max_concurrent: int = 0,
- timeout: float = 10.0):
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ authority_scope: Optional[aether_pb2.WorkflowScheduleAuthorityScope] = None):
"""Create a new schedule.
Args:
@@ -3817,7 +3896,7 @@ async def create_schedule(self, schedule_id: str, name: str,
schedule_expr: Cron expression, Go duration (e.g. "21600s"), or RFC3339 timestamp.
action: Action definition dict (required if no workflow_id).
workflow_id: Workflow ID to trigger (required if no action).
- workspace: Workspace scope (default "*").
+ workspace: Exact workspace scope.
miss_policy: "skip", "fire_once", or "fire_all" (default "skip").
max_concurrent: Max concurrent executions; 0=unlimited, 1=no overlap (default 0).
timeout: RPC timeout in seconds.
@@ -3826,6 +3905,8 @@ async def create_schedule(self, schedule_id: str, name: str,
WorkflowResponse protobuf or None on timeout.
"""
import json as _json
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
data = {
"id": schedule_id,
"name": name,
@@ -3842,18 +3923,26 @@ async def create_schedule(self, schedule_id: str, name: str,
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.CREATE_SCHEDULE,
+ id=schedule_id,
+ workspace=workspace,
data=_json.dumps(data).encode(),
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ if authority_scope is not None:
+ op.schedule_authority_scope.CopyFrom(authority_scope)
return await self.workflow_op(op, timeout=timeout)
async def upsert_schedule(self, schedule_id: str, name: str,
schedule_type: str, schedule_expr: str,
+ workspace: str,
action: Optional[dict] = None,
workflow_id: str = "",
- workspace: str = "*",
miss_policy: str = "skip",
max_concurrent: int = 0,
- timeout: float = 10.0):
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ authority_scope: Optional[aether_pb2.WorkflowScheduleAuthorityScope] = None):
"""Create or update a schedule idempotently.
Same parameters as create_schedule. If a schedule with the given ID
@@ -3864,6 +3953,8 @@ async def upsert_schedule(self, schedule_id: str, name: str,
WorkflowResponse protobuf or None on timeout.
"""
import json as _json
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
data = {
"id": schedule_id,
"name": name,
@@ -3880,32 +3971,50 @@ async def upsert_schedule(self, schedule_id: str, name: str,
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.UPSERT_SCHEDULE,
+ id=schedule_id,
+ workspace=workspace,
data=_json.dumps(data).encode(),
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
+ if authority_scope is not None:
+ op.schedule_authority_scope.CopyFrom(authority_scope)
return await self.workflow_op(op, timeout=timeout)
- async def delete_schedule(self, schedule_id: str, timeout: float = 10.0):
+ async def delete_schedule(self, schedule_id: str, workspace: str,
+ timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None):
"""Delete a schedule by ID.
Returns:
WorkflowResponse protobuf or None on timeout.
"""
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.DELETE_SCHEDULE,
id=schedule_id,
+ workspace=workspace,
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
return await self.workflow_op(op, timeout=timeout)
- async def list_schedules(self, workspace: str = "*", timeout: float = 10.0):
+ async def list_schedules(self, workspace: str, timeout: float = 10.0,
+ authorization: Optional[aether_pb2.AuthorizationContext] = None):
"""List all schedules for a workspace.
Returns:
WorkflowResponse protobuf or None on timeout.
"""
+ if not workspace or workspace == "*":
+ raise ValueError("an exact workflow schedule workspace is required")
op = aether_pb2.WorkflowOperation(
op=aether_pb2.WorkflowOperation.LIST_SCHEDULES,
workspace=workspace,
)
+ if authorization is not None:
+ op.authorization.CopyFrom(authorization)
return await self.workflow_op(op, timeout=timeout)
async def close(self):
diff --git a/sdk/python-client/scitrera_aether_client/proto/aether_pb2.py b/sdk/python-client/scitrera_aether_client/proto/aether_pb2.py
index 974ac04..590ee51 100644
--- a/sdk/python-client/scitrera_aether_client/proto/aether_pb2.py
+++ b/sdk/python-client/scitrera_aether_client/proto/aether_pb2.py
@@ -24,7 +24,7 @@
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61\x65ther.proto\x12\taether.v1\"\xb1\r\n\x0fUpstreamMessage\x12)\n\x04init\x18\x01 \x01(\x0b\x32\x19.aether.v1.InitConnectionH\x00\x12&\n\x04send\x18\x02 \x01(\x0b\x32\x16.aether.v1.SendMessageH\x00\x12\x36\n\x10switch_workspace\x18\x03 \x01(\x0b\x32\x1a.aether.v1.SwitchWorkspaceH\x00\x12\'\n\x05kv_op\x18\x04 \x01(\x0b\x32\x16.aether.v1.KVOperationH\x00\x12\x33\n\x0b\x63reate_task\x18\x05 \x01(\x0b\x32\x1c.aether.v1.CreateTaskRequestH\x00\x12\x37\n\rcheckpoint_op\x18\x06 \x01(\x0b\x32\x1e.aether.v1.CheckpointOperationH\x00\x12,\n\x0b\x61\x64min_query\x18\x07 \x01(\x0b\x32\x15.aether.v1.AdminQueryH\x00\x12\x31\n\nsession_op\x18\x08 \x01(\x0b\x32\x1b.aether.v1.SessionOperationH\x00\x12*\n\ntask_query\x18\t \x01(\x0b\x32\x14.aether.v1.TaskQueryH\x00\x12+\n\x07task_op\x18\n \x01(\x0b\x32\x18.aether.v1.TaskOperationH\x00\x12\x35\n\x0cworkspace_op\x18\x0b \x01(\x0b\x32\x1d.aether.v1.WorkspaceOperationH\x00\x12-\n\x08\x61gent_op\x18\x0c \x01(\x0b\x32\x19.aether.v1.AgentOperationH\x00\x12)\n\x06\x61\x63l_op\x18\r \x01(\x0b\x32\x17.aether.v1.ACLOperationH\x00\x12-\n\x08progress\x18\x0e \x01(\x0b\x32\x19.aether.v1.ProgressReportH\x00\x12\x33\n\x0bworkflow_op\x18\x0f \x01(\x0b\x32\x1c.aether.v1.WorkflowOperationH\x00\x12\x38\n\x11workflow_response\x18\x10 \x01(\x0b\x32\x1b.aether.v1.WorkflowResponseH\x00\x12-\n\x08token_op\x18\x11 \x01(\x0b\x32\x19.aether.v1.TokenOperationH\x00\x12,\n\x0b\x61udit_query\x18\x12 \x01(\x0b\x32\x15.aether.v1.AuditQueryH\x00\x12@\n\x12\x61uthority_grant_op\x18\x13 \x01(\x0b\x32\".aether.v1.AuthorityGrantOperationH\x00\x12\x39\n\x12proxy_http_request\x18\x14 \x01(\x0b\x32\x1b.aether.v1.ProxyHttpRequestH\x00\x12>\n\x15proxy_http_body_chunk\x18\x15 \x01(\x0b\x32\x1d.aether.v1.ProxyHttpBodyChunkH\x00\x12,\n\x0btunnel_open\x18\x16 \x01(\x0b\x32\x15.aether.v1.TunnelOpenH\x00\x12,\n\x0btunnel_data\x18\x17 \x01(\x0b\x32\x15.aether.v1.TunnelDataH\x00\x12.\n\x0ctunnel_close\x18\x18 \x01(\x0b\x32\x16.aether.v1.TunnelCloseH\x00\x12;\n\x13proxy_http_response\x18\x19 \x01(\x0b\x32\x1c.aether.v1.ProxyHttpResponseH\x00\x12*\n\ntunnel_ack\x18\x1a \x01(\x0b\x32\x14.aether.v1.TunnelAckH\x00\x12G\n\x19resolve_authority_request\x18\x1b \x01(\x0b\x32\".aether.v1.ResolveAuthorityRequestH\x00\x12G\n\x19\x63onnection_status_request\x18\x1c \x01(\x0b\x32\".aether.v1.ConnectionStatusRequestH\x00\x12@\n\x12submit_audit_event\x18\x1d \x01(\x0b\x32\".aether.v1.SubmitAuditEventRequestH\x00\x12\x44\n\x14\x61uthority_request_op\x18\x1e \x01(\x0b\x32$.aether.v1.AuthorityRequestOperationH\x00\x12\x44\n\x14task_subscription_op\x18\x1f \x01(\x0b\x32$.aether.v1.TaskSubscriptionOperationH\x00\x12\x19\n\x11\x61\x63tive_extensions\x18 \x03(\tB\t\n\x07payload\"\xba\x10\n\x11\x44ownstreamMessage\x12)\n\x03msg\x18\x01 \x01(\x0b\x32\x1a.aether.v1.IncomingMessageH\x00\x12+\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x19.aether.v1.ConfigSnapshotH\x00\x12#\n\x06signal\x18\x03 \x01(\x0b\x32\x11.aether.v1.SignalH\x00\x12)\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x18.aether.v1.ErrorResponseH\x00\x12#\n\x02kv\x18\x05 \x01(\x0b\x32\x15.aether.v1.KVResponseH\x00\x12\x34\n\x0ftask_assignment\x18\x06 \x01(\x0b\x32\x19.aether.v1.TaskAssignmentH\x00\x12\x32\n\x0e\x63onnection_ack\x18\x07 \x01(\x0b\x32\x18.aether.v1.ConnectionAckH\x00\x12\x33\n\ncheckpoint\x18\x08 \x01(\x0b\x32\x1d.aether.v1.CheckpointResponseH\x00\x12)\n\x05\x61\x64min\x18\t \x01(\x0b\x32\x18.aether.v1.AdminResponseH\x00\x12?\n\x10session_response\x18\n \x01(\x0b\x32#.aether.v1.SessionOperationResponseH\x00\x12\x32\n\ntask_query\x18\x0b \x01(\x0b\x32\x1c.aether.v1.TaskQueryResponseH\x00\x12\x33\n\x07task_op\x18\x0c \x01(\x0b\x32 .aether.v1.TaskOperationResponseH\x00\x12\x31\n\tworkspace\x18\r \x01(\x0b\x32\x1c.aether.v1.WorkspaceResponseH\x00\x12)\n\x05\x61gent\x18\x0e \x01(\x0b\x32\x18.aether.v1.AgentResponseH\x00\x12%\n\x03\x61\x63l\x18\x0f \x01(\x0b\x32\x16.aether.v1.ACLResponseH\x00\x12\x34\n\x0fprogress_update\x18\x10 \x01(\x0b\x32\x19.aether.v1.ProgressUpdateH\x00\x12\x38\n\x11workflow_response\x18\x11 \x01(\x0b\x32\x1b.aether.v1.WorkflowResponseH\x00\x12\x33\n\x0bworkflow_op\x18\x12 \x01(\x0b\x32\x1c.aether.v1.WorkflowOperationH\x00\x12)\n\x05token\x18\x13 \x01(\x0b\x32\x18.aether.v1.TokenResponseH\x00\x12\x37\n\x0e\x61udit_response\x18\x14 \x01(\x0b\x32\x1d.aether.v1.AuditQueryResponseH\x00\x12<\n\x0f\x61uthority_grant\x18\x15 \x01(\x0b\x32!.aether.v1.AuthorityGrantResponseH\x00\x12\x34\n\x0b\x63reate_task\x18\x16 \x01(\x0b\x32\x1d.aether.v1.CreateTaskResponseH\x00\x12;\n\x13proxy_http_response\x18\x17 \x01(\x0b\x32\x1c.aether.v1.ProxyHttpResponseH\x00\x12>\n\x15proxy_http_body_chunk\x18\x18 \x01(\x0b\x32\x1d.aether.v1.ProxyHttpBodyChunkH\x00\x12*\n\ntunnel_ack\x18\x19 \x01(\x0b\x32\x14.aether.v1.TunnelAckH\x00\x12.\n\x0ctunnel_close\x18\x1a \x01(\x0b\x32\x16.aether.v1.TunnelCloseH\x00\x12,\n\x0btunnel_data\x18\x1b \x01(\x0b\x32\x15.aether.v1.TunnelDataH\x00\x12\x39\n\x12proxy_http_request\x18\x1c \x01(\x0b\x32\x1b.aether.v1.ProxyHttpRequestH\x00\x12I\n\x1aresolve_authority_response\x18\x1d \x01(\x0b\x32#.aether.v1.ResolveAuthorityResponseH\x00\x12I\n\x1a\x63onnection_status_response\x18\x1e \x01(\x0b\x32#.aether.v1.ConnectionStatusResponseH\x00\x12I\n\x1a\x61uthority_grant_revocation\x18\x1f \x01(\x0b\x32#.aether.v1.AuthorityGrantRevocationH\x00\x12J\n\x1bsubmit_audit_event_response\x18 \x01(\x0b\x32#.aether.v1.SubmitAuditEventResponseH\x00\x12R\n\x1a\x61uthority_request_response\x18! \x01(\x0b\x32,.aether.v1.AuthorityRequestOperationResponseH\x00\x12\x43\n\x17\x61uthority_request_event\x18\" \x01(\x0b\x32 .aether.v1.AuthorityRequestEventH\x00\x12\x34\n\x0ftask_hibernated\x18# \x01(\x0b\x32\x19.aether.v1.TaskHibernatedH\x00\x12R\n\x1atask_subscription_response\x18$ \x01(\x0b\x32,.aether.v1.TaskSubscriptionOperationResponseH\x00\x12*\n\ntask_event\x18% \x01(\x0b\x32\x14.aether.v1.TaskEventH\x00\x12\x19\n\x11\x61\x63tive_extensions\x18& \x03(\tB\t\n\x07payload\"W\n\x0eTaskHibernated\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x34\n\ndescriptor\x18\x02 \x01(\x0b\x32 .aether.v1.HibernationDescriptor\"\xb6\x02\n\rConnectionAck\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0f\n\x07resumed\x18\x02 \x01(\x08\x12\x13\n\x0b\x61ssigned_id\x18\x03 \x01(\t\x12=\n\x15negotiated_extensions\x18\x04 \x03(\x0b\x32\x1e.aether.v1.NegotiatedExtension\x12#\n\x1bserver_supported_extensions\x18\x05 \x03(\t\x12\x16\n\x0eserver_version\x18\x32 \x01(\t\x12/\n\x11server_build_info\x18\x33 \x01(\x0b\x32\x14.aether.v1.BuildInfo\x12\"\n\x1ainitial_connection_unix_ms\x18< \x01(\x03\x12\x1a\n\x12reconnection_count\x18= \x01(\x05\"\xcd\x05\n\x0eInitConnection\x12)\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x18.aether.v1.AgentIdentityH\x00\x12\'\n\x04task\x18\x02 \x01(\x0b\x32\x17.aether.v1.TaskIdentityH\x00\x12\'\n\x04user\x18\x03 \x01(\x0b\x32\x17.aether.v1.UserIdentityH\x00\x12\x37\n\x0corchestrator\x18\x04 \x01(\x0b\x32\x1f.aether.v1.OrchestratorIdentityH\x00\x12<\n\x0fworkflow_engine\x18\x05 \x01(\x0b\x32!.aether.v1.WorkflowEngineIdentityH\x00\x12:\n\x0emetrics_bridge\x18\x06 \x01(\x0b\x32 .aether.v1.MetricsBridgeIdentityH\x00\x12+\n\x06\x62ridge\x18\x07 \x01(\x0b\x32\x19.aether.v1.BridgeIdentityH\x00\x12-\n\x07service\x18\x08 \x01(\x0b\x32\x1a.aether.v1.ServiceIdentityH\x00\x12?\n\x0b\x63redentials\x18\n \x03(\x0b\x32*.aether.v1.InitConnection.CredentialsEntry\x12\x19\n\x11resume_session_id\x18\x0b \x01(\t\x12\x33\n\nextensions\x18\x0c \x03(\x0b\x32\x1f.aether.v1.ExtensionDeclaration\x12\x16\n\x0e\x63lient_version\x18\x32 \x01(\t\x12\x12\n\nclient_sdk\x18\x33 \x01(\t\x12/\n\x11\x63lient_build_info\x18\x34 \x01(\x0b\x32\x14.aether.v1.BuildInfo\x1a\x32\n\x10\x43redentialsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b\x63lient_type\"J\n\tBuildInfo\x12\x0e\n\x06\x63ommit\x18\x01 \x01(\t\x12\x10\n\x08\x62uilt_at\x18\x02 \x01(\t\x12\x0f\n\x07runtime\x18\x03 \x01(\t\x12\n\n\x02os\x18\x04 \x01(\t\"[\n\x14\x45xtensionDeclaration\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x10\n\x08required\x18\x03 \x01(\x08\x12\x13\n\x0bjson_schema\x18\x04 \x01(\t\"`\n\x13NegotiatedExtension\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x11\n\tsupported\x18\x03 \x01(\x08\x12\x18\n\x10rejection_reason\x18\x04 \x01(\t\"-\n\x16WorkflowEngineIdentity\x12\x13\n\x0binstance_id\x18\x01 \x01(\t\",\n\x15MetricsBridgeIdentity\x12\x13\n\x0binstance_id\x18\x01 \x01(\t\"]\n\x14OrchestratorIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\x12\x1a\n\x12supported_profiles\x18\x03 \x03(\t\";\n\x0e\x42ridgeIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\"V\n\x0fServiceIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\x12\x18\n\x10no_pool_consumer\x18\x03 \x01(\x08\"M\n\rAgentIdentity\x12\x11\n\tworkspace\x18\x01 \x01(\t\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12\x11\n\tspecifier\x18\x03 \x01(\t\"S\n\x0cTaskIdentity\x12\x11\n\tworkspace\x18\x01 \x01(\t\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12\x18\n\x10unique_specifier\x18\x03 \x01(\t\"2\n\x0cUserIdentity\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x11\n\twindow_id\x18\x02 \x01(\t\"<\n\x0cPrincipalRef\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\"\x9e\x01\n\x14\x41uthorizationContext\x12\x16\n\x0e\x61uthority_mode\x18\x01 \x01(\t\x12(\n\x07subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x10\n\x08grant_id\x18\x03 \x01(\t\x12\x32\n\x08resolved\x18\n \x01(\x0b\x32 .aether.v1.ResolvedAuthorityInfo\"\xbc\x01\n\x15ResolvedAuthorityInfo\x12-\n\x0croot_subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x03 \x01(\t\x12\x18\n\x10max_access_level\x18\x04 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\x05 \x03(\t\x12\x15\n\rexpires_at_ms\x18\x06 \x01(\x03\"\xb1\x01\n\x0bSendMessage\x12\x14\n\x0ctarget_topic\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x36\n\rauthorization\x18\x04 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rapp_workspace\x18\x05 \x01(\t\"\xc4\x01\n\x06Metric\x12\x10\n\x08trace_id\x18\x01 \x01(\t\x12\'\n\x07\x65ntries\x18\x02 \x03(\x0b\x32\x16.aether.v1.MetricEntry\x12\x31\n\x08metadata\x18\x03 \x03(\x0b\x32\x1f.aether.v1.Metric.MetadataEntry\x12\x1b\n\x13\x63lient_timestamp_ms\x18\x04 \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"6\n\x0bMetricEntry\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04kind\x18\x02 \x01(\t\x12\x0b\n\x03qty\x18\x03 \x01(\x01\"+\n\x0fSwitchWorkspace\x12\x18\n\x10new_workspace_id\x18\x01 \x01(\t\"\x8a\x06\n\x0bKVOperation\x12)\n\x02op\x18\x01 \x01(\x0e\x32\x1d.aether.v1.KVOperation.OpType\x12+\n\x05scope\x18\x02 \x01(\x0e\x32\x1c.aether.v1.KVOperation.Scope\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\r\n\x05value\x18\x04 \x01(\x0c\x12\x0f\n\x07user_id\x18\x05 \x01(\t\x12\x11\n\tworkspace\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x03\x12\x12\n\nrequest_id\x18\x08 \x01(\t\x12\x36\n\rauthorization\x18\t \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x13\n\x0bguard_value\x18\n \x01(\x03\x12\x13\n\x0b\x64\x65lta_value\x18\x0b \x01(\x03\x12\x16\n\x0e\x65xpected_value\x18\x0c \x01(\x0c\x12\r\n\x05limit\x18\r \x01(\x05\x12\x0e\n\x06\x63ursor\x18\x0e \x01(\t\x12\x17\n\x0ftarget_identity\x18\x0f \x01(\t\"\xda\x01\n\x06OpType\x12\x07\n\x03GET\x10\x00\x12\x07\n\x03PUT\x10\x01\x12\x08\n\x04LIST\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03\x12\r\n\tINCREMENT\x10\x04\x12\r\n\tDECREMENT\x10\x05\x12\x10\n\x0cINCREMENT_IF\x10\x06\x12\x10\n\x0c\x44\x45\x43REMENT_IF\x10\x07\x12\n\n\x06SET_NX\x10\x08\x12\x13\n\x0f\x43OMPARE_AND_SET\x10\t\x12\x16\n\x12\x43OMPARE_AND_DELETE\x10\n\x12\x12\n\x0ePURGE_IDENTITY\x10\x0b\x12\x0b\n\x07SET_ADD\x10\x0c\x12\x0c\n\x08SET_CARD\x10\r\"\xb2\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\n\n\x06GLOBAL\x10\x01\x12\r\n\tWORKSPACE\x10\x02\x12\x08\n\x04USER\x10\x03\x12\x12\n\x0eUSER_WORKSPACE\x10\x04\x12\x14\n\x10GLOBAL_EXCLUSIVE\x10\x05\x12\x17\n\x13WORKSPACE_EXCLUSIVE\x10\x06\x12\x0f\n\x0bUSER_SHARED\x10\x07\x12\x19\n\x15USER_WORKSPACE_SHARED\x10\x08\"\xfd\x01\n\nKVResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12\x30\n\x06kv_map\x18\x04 \x03(\x0b\x32 .aether.v1.KVResponse.KvMapEntry\x12\x12\n\nrequest_id\x18\x05 \x01(\t\x12\x15\n\rcounter_value\x18\x06 \x01(\x03\x12\x0f\n\x07\x61pplied\x18\x07 \x01(\x08\x12\x13\n\x0bnext_cursor\x18\x08 \x01(\t\x12\x10\n\x08has_more\x18\t \x01(\x08\x1a,\n\nKvMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\xad\x01\n\x0fIncomingMessage\x12\x14\n\x0csource_topic\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x32\n\x11on_behalf_subject\x18\x05 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\"\xf0\x04\n\x0e\x43onfigSnapshot\x12\x31\n\x02kv\x18\x01 \x03(\x0b\x32!.aether.v1.ConfigSnapshot.KvEntryB\x02\x18\x01\x12>\n\tglobal_kv\x18\x02 \x03(\x0b\x32\'.aether.v1.ConfigSnapshot.GlobalKvEntryB\x02\x18\x01\x12@\n\x0ctask_context\x18\x03 \x03(\x0b\x32*.aether.v1.ConfigSnapshot.TaskContextEntry\x12S\n\x16workspace_exclusive_kv\x18\x04 \x03(\x0b\x32\x33.aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry\x12M\n\x13global_exclusive_kv\x18\x05 \x03(\x0b\x32\x30.aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry\x1a)\n\x07KvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a/\n\rGlobalKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x32\n\x10TaskContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19WorkspaceExclusiveKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16GlobalExclusiveKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\x81\x01\n\x06Signal\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x1c.aether.v1.Signal.SignalType\x12\x0e\n\x06reason\x18\x02 \x01(\t\";\n\nSignalType\x12\x14\n\x10\x46ORCE_DISCONNECT\x10\x00\x12\x17\n\x13GRACEFUL_DISCONNECT\x10\x01\"m\n\rErrorResponse\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\tretryable\x18\x03 \x01(\x08\x12\x16\n\x0eretry_after_ms\x18\x04 \x01(\x03\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"\xe7\x01\n\x0bRetryPolicy\x12\x14\n\x0cmax_attempts\x18\x01 \x01(\x05\x12+\n\x07\x62\x61\x63koff\x18\x02 \x01(\x0e\x32\x1a.aether.v1.BackoffStrategy\x12\x18\n\x10initial_delay_ms\x18\x03 \x01(\x03\x12\x14\n\x0cmax_delay_ms\x18\x04 \x01(\x03\x12\x15\n\rjitter_factor\x18\x05 \x01(\x01\x12\x13\n\x0bschedule_ms\x18\x06 \x03(\x03\x12\x1e\n\x16retryable_status_codes\x18\x07 \x03(\x05\x12\x19\n\x11honor_retry_after\x18\x08 \x01(\x08\"f\n\x13TaskCompletionEvent\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x12\n\nevent_name\x18\x02 \x01(\t\x12*\n\x0bon_statuses\x18\x03 \x03(\x0e\x32\x15.aether.v1.TaskStatus\"\xbb\x06\n\x11\x43reateTaskRequest\x12\x11\n\ttask_type\x18\x01 \x01(\t\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\x36\n\x0f\x61ssignment_mode\x18\x03 \x01(\x0e\x32\x1d.aether.v1.TaskAssignmentMode\x12\x17\n\x0ftarget_agent_id\x18\x04 \x01(\t\x12V\n\x16launch_param_overrides\x18\x05 \x03(\x0b\x32\x36.aether.v1.CreateTaskRequest.LaunchParamOverridesEntry\x12<\n\x08metadata\x18\x06 \x03(\x0b\x32*.aether.v1.CreateTaskRequest.MetadataEntry\x12\x0f\n\x07payload\x18\x07 \x01(\x0c\x12\x1d\n\x15target_implementation\x18\x08 \x01(\t\x12\x36\n\rauthorization\x18\t \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x12\n\nrequest_id\x18\n \x01(\t\x12\x17\n\x0ftarget_identity\x18\x0b \x01(\t\x12(\n\ntask_class\x18\x0c \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x12\n\ncontext_id\x18\r \x01(\t\x12,\n\x0cretry_policy\x18\x0e \x01(\x0b\x32\x16.aether.v1.RetryPolicy\x12)\n\x08priority\x18\x0f \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x17\n\x0fidempotency_key\x18\x10 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x11 \x01(\t\x12\x14\n\x0croot_task_id\x18\x12 \x01(\t\x12\x38\n\x10\x63ompletion_event\x18\x13 \x01(\x0b\x32\x1e.aether.v1.TaskCompletionEvent\x1a;\n\x19LaunchParamOverridesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xca\x01\n\x12\x43reateTaskResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x12\n\nerror_code\x18\x04 \x01(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x07 \x01(\t\x12\x12\n\ntask_token\x18\x08 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\t \x01(\t\"\x87\x04\n\x0eTaskAssignment\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x03 \x01(\t\x12\x39\n\x08metadata\x18\x04 \x03(\x0b\x32\'.aether.v1.TaskAssignment.MetadataEntry\x12\x13\n\x0b\x61ssigned_at\x18\x05 \x01(\x03\x12\x0f\n\x07profile\x18\x06 \x01(\t\x12\x42\n\rlaunch_params\x18\x07 \x03(\x0b\x32+.aether.v1.TaskAssignment.LaunchParamsEntry\x12\x1d\n\x15target_implementation\x18\x08 \x01(\t\x12\x11\n\tworkspace\x18\t \x01(\t\x12\x11\n\tspecifier\x18\n \x01(\t\x12\x0f\n\x07payload\x18\x0b \x01(\x0c\x12(\n\ntask_class\x18\x0c \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x16\n\x0e\x63heckpoint_key\x18\r \x01(\t\x12\x19\n\x11resume_session_id\x18\x0e \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11LaunchParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb8\x01\n\x13\x43heckpointOperation\x12\x31\n\x02op\x18\x01 \x01(\x0e\x32%.aether.v1.CheckpointOperation.OpType\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x0b\n\x03ttl\x18\x04 \x01(\x03\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"2\n\x06OpType\x12\x08\n\x04SAVE\x10\x00\x12\x08\n\x04LOAD\x10\x01\x12\n\n\x06\x44\x45LETE\x10\x02\x12\x08\n\x04LIST\x10\x03\"v\n\x12\x43heckpointResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x10\n\x08saved_at\x18\x05 \x01(\x03\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"\xec\x01\n\nAdminQuery\x12(\n\x02op\x18\x01 \x01(\x0e\x32\x1c.aether.v1.AdminQuery.OpType\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12+\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x1b.aether.v1.ConnectionFilter\x12\x12\n\nrequest_id\x18\x04 \x01(\t\"_\n\x06OpType\x12\x0e\n\nGET_HEALTH\x10\x00\x12\x0c\n\x08GET_INFO\x10\x01\x12\r\n\tGET_STATS\x10\x02\x12\x14\n\x10LIST_CONNECTIONS\x10\x03\x12\x12\n\x0eGET_CONNECTION\x10\x04\"l\n\x10\x43onnectionFilter\x12&\n\x04type\x18\x01 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\"\xf0\x01\n\x0e\x43onnectionInfo\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12&\n\x04type\x18\x02 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x10\n\x08identity\x18\x03 \x01(\t\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x16\n\x0eimplementation\x18\x05 \x01(\t\x12\x11\n\tspecifier\x18\x06 \x01(\t\x12\x14\n\x0c\x63onnected_at\x18\x07 \x01(\x03\x12\x10\n\x08\x64uration\x18\x08 \x01(\t\x12\x13\n\x0bremote_addr\x18\t \x01(\t\x12\x15\n\rlast_activity\x18\n \x01(\x03\"\xac\x02\n\rAdminResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12%\n\x06health\x18\x03 \x01(\x0b\x32\x15.aether.v1.HealthInfo\x12$\n\x04info\x18\x04 \x01(\x0b\x32\x16.aether.v1.GatewayInfo\x12&\n\x05stats\x18\x05 \x01(\x0b\x32\x17.aether.v1.GatewayStats\x12-\n\nconnection\x18\x06 \x01(\x0b\x32\x19.aether.v1.ConnectionInfo\x12.\n\x0b\x63onnections\x18\x07 \x03(\x0b\x32\x19.aether.v1.ConnectionInfo\x12\x13\n\x0btotal_count\x18\x08 \x01(\x05\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xea\x01\n\nHealthInfo\x12\'\n\x06status\x18\x01 \x01(\x0e\x32\x17.aether.v1.HealthStatus\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x31\n\x06\x63hecks\x18\x03 \x03(\x0b\x32!.aether.v1.HealthInfo.ChecksEntry\x12&\n\x05stats\x18\x04 \x01(\x0b\x32\x17.aether.v1.GatewayStats\x1a\x45\n\x0b\x43hecksEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.aether.v1.HealthCheck:\x02\x38\x01\"[\n\x0bHealthCheck\x12,\n\x06status\x18\x01 \x01(\x0e\x32\x1c.aether.v1.HealthCheckStatus\x12\x0f\n\x07latency\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"\xb4\x01\n\x0bGatewayInfo\x12\x12\n\ngateway_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x12\n\nstarted_at\x18\x03 \x01(\x03\x12\x0e\n\x06uptime\x18\x04 \x01(\t\x12\x12\n\ngo_version\x18\x05 \x01(\t\x12\x16\n\x0enum_goroutines\x18\x06 \x01(\x05\x12\x17\n\x0fmemory_alloc_mb\x18\x07 \x01(\x01\x12\x17\n\x0fnum_connections\x18\x08 \x01(\x05\"\x9a\x03\n\x0cGatewayStats\x12\x19\n\x11\x61gent_connections\x18\x01 \x01(\x05\x12\x18\n\x10task_connections\x18\x02 \x01(\x05\x12\x18\n\x10user_connections\x18\x03 \x01(\x05\x12 \n\x18orchestrator_connections\x18\x04 \x01(\x05\x12!\n\x19workflow_engine_connected\x18\x05 \x01(\x08\x12 \n\x18metrics_bridge_connected\x18\x06 \x01(\x08\x12\x13\n\x0btotal_tasks\x18\x07 \x01(\x05\x12\x15\n\rpending_tasks\x18\x08 \x01(\x05\x12\x15\n\rrunning_tasks\x18\t \x01(\x05\x12\x17\n\x0f\x63ompleted_tasks\x18\n \x01(\x05\x12\x14\n\x0c\x66\x61iled_tasks\x18\x0b \x01(\x05\x12\x1b\n\x13messages_per_second\x18\x0c \x01(\x01\x12\x16\n\x0etotal_messages\x18\r \x01(\x03\x12\x15\n\ractive_timers\x18\x0e \x01(\x05\x12\x16\n\x0epending_timers\x18\x0f \x01(\x05\"\x8c\x02\n\x10SessionOperation\x12.\n\x02op\x18\x01 \x01(\x0e\x32\".aether.v1.SessionOperation.OpType\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12+\n\x06\x66ilter\x18\x05 \x01(\x0b\x32\x1b.aether.v1.ConnectionFilter\x12\x36\n\rauthorization\x18\x06 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"+\n\x06OpType\x12\x0e\n\nDISCONNECT\x10\x00\x12\x08\n\x04LIST\x10\x01\x12\x07\n\x03GET\x10\x02\"\xd3\x01\n\x18SessionOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12-\n\nconnection\x18\x05 \x01(\x0b\x32\x19.aether.v1.ConnectionInfo\x12.\n\x0b\x63onnections\x18\x06 \x03(\x0b\x32\x19.aether.v1.ConnectionInfo\x12\x13\n\x0btotal_count\x18\x07 \x01(\x05\"\x9d\x01\n\tTaskQuery\x12\'\n\x02op\x18\x01 \x01(\x0e\x32\x1b.aether.v1.TaskQuery.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12%\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x15.aether.v1.TaskFilter\x12\x12\n\nrequest_id\x18\x04 \x01(\t\"\x1b\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\"\xec\x05\n\nTaskFilter\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\x11\n\ttask_type\x18\x03 \x01(\t\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\x12\'\n\x08statuses\x18\x06 \x03(\x0e\x32\x15.aether.v1.TaskStatus\x12\x14\n\x0csubject_type\x18\x07 \x01(\t\x12\x12\n\nsubject_id\x18\x08 \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\t \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\n \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x0b \x01(\t\x12\x16\n\x0eparent_task_id\x18\x0c \x01(\t\x12(\n\ntask_class\x18\r \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x32\n\x14\x65xclude_task_classes\x18\x0e \x03(\x0e\x32\x14.aether.v1.TaskClass\x12\x12\n\ncontext_id\x18\x0f \x01(\t\x12/\n\x10\x65xclude_statuses\x18\x10 \x03(\x0e\x32\x15.aether.v1.TaskStatus\x12.\n\rcreator_actor\x18\x11 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12&\n\x1estatus_timestamp_after_unix_ms\x18\x12 \x01(\x03\x12\x12\n\npage_token\x18\x13 \x01(\t\x12\x1b\n\x13include_descendants\x18\x14 \x01(\x08\x12)\n\x08priority\x18\x15 \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12-\n\x0cmin_priority\x18\x16 \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x16\n\x0e\x63orrelation_id\x18\x17 \x01(\t\x12\x14\n\x0croot_task_id\x18\x18 \x01(\t\"\xc7\x07\n\x08TaskInfo\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12%\n\x06status\x18\x03 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x05 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x06 \x01(\t\x12\x12\n\ncreated_at\x18\x07 \x01(\x03\x12\x12\n\nstarted_at\x18\x08 \x01(\x03\x12\x14\n\x0c\x63ompleted_at\x18\t \x01(\x03\x12\x0f\n\x07\x61ttempt\x18\n \x01(\x05\x12\x14\n\x0cmax_attempts\x18\x0b \x01(\x05\x12\r\n\x05\x65rror\x18\x0c \x01(\t\x12\x33\n\x08metadata\x18\r \x03(\x0b\x32!.aether.v1.TaskInfo.MetadataEntry\x12\x16\n\x0e\x61uthority_mode\x18\x0e \x01(\t\x12\x14\n\x0csubject_type\x18\x0f \x01(\t\x12\x12\n\nsubject_id\x18\x10 \x01(\t\x12\x19\n\x11root_subject_type\x18\x11 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x12 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x13 \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x14 \x01(\t\x12!\n\x19parent_authority_grant_id\x18\x15 \x01(\t\x12\x18\n\x10\x63reator_actor_id\x18\x16 \x01(\t\x12\x16\n\x0eparent_task_id\x18\x17 \x01(\t\x12(\n\ntask_class\x18\x18 \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x17\n\x0f\x64isconnected_at\x18\x19 \x01(\x03\x12\x17\n\x0fgrace_window_ms\x18\x1a \x01(\x03\x12&\n\twait_spec\x18\x1b \x01(\x0b\x32\x13.aether.v1.WaitSpec\x12\x12\n\ndepends_on\x18\x1c \x03(\t\x12\x12\n\ncontext_id\x18\x1d \x01(\t\x12\x11\n\tpaused_at\x18\x1e \x01(\x03\x12)\n\x08priority\x18\x1f \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x16\n\x0e\x63orrelation_id\x18 \x01(\t\x12\x14\n\x0croot_task_id\x18! \x01(\t\x12\x38\n\x10\x63ompletion_event\x18\" \x01(\x0b\x32\x1e.aether.v1.TaskCompletionEvent\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\x11TaskQueryResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12!\n\x04task\x18\x03 \x01(\x0b\x32\x13.aether.v1.TaskInfo\x12\"\n\x05tasks\x18\x04 \x03(\x0b\x32\x13.aether.v1.TaskInfo\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x17\n\x0fnext_page_token\x18\x07 \x01(\t\"\x8e\x02\n\rTaskOperation\x12+\n\x02op\x18\x01 \x01(\x0e\x32\x1f.aether.v1.TaskOperation.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12&\n\twait_spec\x18\x05 \x01(\x0b\x32\x13.aether.v1.WaitSpec\"s\n\x06OpType\x12\t\n\x05RETRY\x10\x00\x12\n\n\x06\x43\x41NCEL\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x12\x08\n\x04\x46\x41IL\x10\x03\x12\t\n\x05PAUSE\x10\x04\x12\x0c\n\x08WAIT_FOR\x10\x05\x12\n\n\x06RESUME\x10\x06\x12\n\n\x06REJECT\x10\x07\x12\t\n\x05\x43LAIM\x10\x08\"\xec\x02\n\x08WaitSpec\x12%\n\x06reason\x18\x01 \x01(\x0e\x32\x15.aether.v1.WaitReason\x12\x1a\n\x12\x65xpected_principal\x18\x02 \x01(\t\x12\x38\n\x0binput_match\x18\x03 \x03(\x0b\x32#.aether.v1.WaitSpec.InputMatchEntry\x12\x1c\n\x14\x61uthority_request_id\x18\x04 \x01(\t\x12\x12\n\ndepends_on\x18\x05 \x03(\t\x12\x13\n\x0bwake_on_any\x18\x06 \x01(\x08\x12\x12\n\ntimeout_ms\x18\x07 \x01(\x03\x12\x1e\n\x16scheduled_wake_unix_ms\x18\x08 \x01(\x03\x12\x35\n\x0bhibernation\x18\t \x01(\x0b\x32 .aether.v1.HibernationDescriptor\x1a\x31\n\x0fInputMatchEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x7f\n\x15HibernationDescriptor\x12\x16\n\x0e\x63heckpoint_key\x18\x01 \x01(\t\x12\x19\n\x11resume_session_id\x18\x02 \x01(\t\x12\x18\n\x10wake_event_types\x18\x03 \x03(\t\x12\x19\n\x11\x65scalation_policy\x18\x04 \x01(\t\"\x7f\n\x15TaskOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12!\n\x04task\x18\x04 \x01(\x0b\x32\x13.aether.v1.TaskInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"\xa0\x02\n\x12WorkspaceOperation\x12\x30\n\x02op\x18\x01 \x01(\x0e\x32$.aether.v1.WorkspaceOperation.OpType\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12*\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x1a.aether.v1.WorkspaceFilter\x12+\n\tworkspace\x18\x04 \x01(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"U\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\n\n\x06\x43REATE\x10\x02\x12\n\n\x06UPDATE\x10\x03\x12\n\n\x06\x44\x45LETE\x10\x04\x12\x14\n\x10GET_MESSAGE_FLOW\x10\x05\"C\n\x0fWorkspaceFilter\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\xd1\x02\n\rWorkspaceInfo\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x11\n\ttenant_id\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\x12\x38\n\x08metadata\x18\x07 \x03(\x0b\x32&.aether.v1.WorkspaceInfo.MetadataEntry\x12\x15\n\ractive_agents\x18\x08 \x01(\x05\x12\x14\n\x0c\x61\x63tive_tasks\x18\t \x01(\x05\x12\x14\n\x0c\x61\x63tive_users\x18\n \x01(\x05\x12\x16\n\x0etotal_messages\x18\x0b \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xfa\x01\n\x11WorkspaceResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12+\n\tworkspace\x18\x04 \x01(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12,\n\nworkspaces\x18\x05 \x03(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x30\n\x0cmessage_flow\x18\x07 \x01(\x0b\x32\x1a.aether.v1.MessageFlowInfo\x12\x12\n\nrequest_id\x18\x08 \x01(\t\"\x83\x01\n\x0fMessageFlowInfo\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\"\n\x05nodes\x18\x02 \x03(\x0b\x32\x13.aether.v1.FlowNode\x12\"\n\x05\x65\x64ges\x18\x03 \x03(\x0b\x32\x13.aether.v1.FlowEdge\x12\x12\n\nupdated_at\x18\x04 \x01(\x03\"\x97\x01\n\x08\x46lowNode\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12&\n\x04type\x18\x03 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x16\n\x0eimplementation\x18\x05 \x01(\t\x12\x11\n\tspecifier\x18\x06 \x01(\t\x12\r\n\x05topic\x18\x07 \x01(\t\"B\n\x08\x46lowEdge\x12\x0c\n\x04\x66rom\x18\x01 \x01(\t\x12\n\n\x02to\x18\x02 \x01(\t\x12\r\n\x05label\x18\x03 \x01(\t\x12\r\n\x05\x63ount\x18\x04 \x01(\x03\"\xdf\x02\n\x0e\x41gentOperation\x12,\n\x02op\x18\x01 \x01(\x0e\x32 .aether.v1.AgentOperation.OpType\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12&\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x16.aether.v1.AgentFilter\x12/\n\x05\x61gent\x18\x04 \x01(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x33\n\rlaunch_params\x18\x05 \x01(\x0b\x32\x1c.aether.v1.AgentLaunchParams\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"e\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\x0c\n\x08REGISTER\x10\x02\x12\n\n\x06UPDATE\x10\x03\x12\n\n\x06\x44\x45LETE\x10\x04\x12\n\n\x06LAUNCH\x10\x05\x12\x16\n\x12LIST_ORCHESTRATORS\x10\x06\"J\n\x0b\x41gentFilter\x12\x1c\n\x14orchestrator_profile\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\xde\x03\n\x15\x41gentRegistrationInfo\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x1c\n\x14orchestrator_profile\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12I\n\rlaunch_params\x18\x04 \x03(\x0b\x32\x32.aether.v1.AgentRegistrationInfo.LaunchParamsEntry\x12\x15\n\rregistered_at\x18\x05 \x01(\x03\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\x12<\n\x0fresource_schema\x18\x07 \x03(\x0b\x32#.aether.v1.AgentResourceSchemaEntry\x12H\n\x0c\x63\x61pabilities\x18\x08 \x03(\x0b\x32\x32.aether.v1.AgentRegistrationInfo.CapabilitiesEntry\x12\x12\n\nextensions\x18\t \x03(\t\x1a\x33\n\x11LaunchParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x43\x61pabilitiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\"n\n\x18\x41gentResourceSchemaEntry\x12\x1c\n\x14resource_type_prefix\x18\x01 \x01(\t\x12\x18\n\x10permission_verbs\x18\x02 \x03(\t\x12\x1a\n\x12resource_id_schema\x18\x03 \x01(\t\"\xbb\x01\n\x11\x41gentLaunchParams\x12\x11\n\tspecifier\x18\x01 \x01(\t\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12I\n\x0fparam_overrides\x18\x03 \x03(\x0b\x32\x30.aether.v1.AgentLaunchParams.ParamOverridesEntry\x1a\x35\n\x13ParamOverridesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"S\n\x10OrchestratorInfo\x12\x17\n\x0forchestrator_id\x18\x01 \x01(\t\x12\x10\n\x08profiles\x18\x02 \x03(\t\x12\x14\n\x0c\x63onnected_at\x18\x03 \x01(\x03\"5\n\x11\x41gentLaunchResult\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xb5\x02\n\rAgentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12/\n\x05\x61gent\x18\x04 \x01(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x30\n\x06\x61gents\x18\x05 \x03(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x32\n\rorchestrators\x18\x07 \x03(\x0b\x32\x1b.aether.v1.OrchestratorInfo\x12\x33\n\rlaunch_result\x18\x08 \x01(\x0b\x32\x1c.aether.v1.AgentLaunchResult\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xac\x0c\n\x0c\x41\x43LOperation\x12*\n\x02op\x18\x01 \x01(\x0e\x32\x1e.aether.v1.ACLOperation.OpType\x12\x0f\n\x07rule_id\x18\x02 \x01(\t\x12\x15\n\rrule_category\x18\x04 \x01(\t\x12\x16\n\x0eretention_days\x18\x05 \x01(\x05\x12-\n\x0brule_filter\x18\n \x01(\x0b\x32\x18.aether.v1.ACLRuleFilter\x12/\n\x0c\x61udit_filter\x18\x0b \x01(\x0b\x32\x19.aether.v1.ACLAuditFilter\x12\x31\n\rgrant_request\x18\x0c \x01(\x0b\x32\x1a.aether.v1.ACLGrantRequest\x12:\n\x10\x66\x61llback_request\x18\x0e \x01(\x0b\x32 .aether.v1.ACLSetFallbackRequest\x12\x12\n\nrequest_id\x18\x13 \x01(\t\x12\x0c\n\x04name\x18\x14 \x01(\t\x12*\n\tprincipal\x18\x15 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x31\n\rgroup_request\x18\x16 \x01(\x0b\x32\x1a.aether.v1.ACLGroupRequest\x12/\n\x0crole_request\x18\x17 \x01(\x0b\x32\x19.aether.v1.ACLRoleRequest\x12\x38\n\x0emember_request\x18\x18 \x01(\x0b\x32 .aether.v1.ACLGroupMemberRequest\x12?\n\x12\x61ssignment_request\x18\x19 \x01(\x0b\x32#.aether.v1.ACLRoleAssignmentRequest\x12\x15\n\rresource_type\x18\x1a \x01(\t\x12\x13\n\x0bresource_id\x18\x1b \x01(\t\x12\x16\n\x0erequired_level\x18\x1c \x01(\x05\x12\x36\n\rauthorization\x18\x1d \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"\xa3\x05\n\x06OpType\x12\x0e\n\nLIST_RULES\x10\x00\x12\x0c\n\x08GET_RULE\x10\x01\x12\t\n\x05GRANT\x10\x02\x12\n\n\x06REVOKE\x10\x03\x12\x0f\n\x0bQUERY_AUDIT\x10\x04\x12\x17\n\x13GET_FALLBACK_POLICY\x10\x08\x12\x17\n\x13SET_FALLBACK_POLICY\x10\t\x12\x13\n\x0f\x43LEANUP_EXPIRED\x10\n\x12\x16\n\x12\x43LEANUP_AUDIT_LOGS\x10\x0b\x12\x10\n\x0c\x43REATE_GROUP\x10\x11\x12\x10\n\x0c\x44\x45LETE_GROUP\x10\x12\x12\r\n\tGET_GROUP\x10\x13\x12\x0f\n\x0bLIST_GROUPS\x10\x14\x12\x14\n\x10\x41\x44\x44_GROUP_MEMBER\x10\x15\x12\x17\n\x13REMOVE_GROUP_MEMBER\x10\x16\x12\x16\n\x12LIST_GROUP_MEMBERS\x10\x17\x12\x0f\n\x0b\x43REATE_ROLE\x10\x18\x12\x0f\n\x0b\x44\x45LETE_ROLE\x10\x19\x12\x0c\n\x08GET_ROLE\x10\x1a\x12\x0e\n\nLIST_ROLES\x10\x1b\x12\x0f\n\x0b\x41SSIGN_ROLE\x10\x1c\x12\x11\n\rUNASSIGN_ROLE\x10\x1d\x12\x19\n\x15LIST_ROLE_ASSIGNMENTS\x10\x1e\x12\x19\n\x15LIST_PRINCIPAL_GROUPS\x10\x1f\x12\x18\n\x14LIST_PRINCIPAL_ROLES\x10 \x12\x12\n\x0e\x45XPLAIN_ACCESS\x10!\"\x04\x08\x05\x10\x05\"\x04\x08\x06\x10\x06\"\x04\x08\x07\x10\x07\"\x04\x08\x0c\x10\x0c\"\x04\x08\r\x10\r\"\x04\x08\x0e\x10\x0e\"\x04\x08\x0f\x10\x0f\"\x04\x08\x10\x10\x10*\x15LIST_AUTHORITY_GRANTS*\x13GET_AUTHORITY_GRANT*\x16\x43REATE_AUTHORITY_GRANT*\x15RENEW_AUTHORITY_GRANT*\x16REVOKE_AUTHORITY_GRANTJ\x04\x08\x03\x10\x04J\x04\x08\x06\x10\x07J\x04\x08\x07\x10\x08J\x04\x08\r\x10\x0eJ\x04\x08\x08\x10\tJ\x04\x08\x10\x10\x11J\x04\x08\x11\x10\x12J\x04\x08\x12\x10\x13R\x12\x61uthority_grant_idR\x16\x61uthority_grant_filterR\x17\x61uthority_grant_requestR\x1d\x61uthority_grant_renew_request\"\x88\x01\n\rACLRuleFilter\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\r\n\x05limit\x18\x05 \x01(\x05\x12\x0e\n\x06offset\x18\x06 \x01(\x05\"\xd4\x01\n\x0e\x41\x43LAuditFilter\x12\x12\n\nstart_time\x18\x01 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x03\x12\x16\n\x0eprincipal_type\x18\x03 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x04 \x01(\t\x12\x15\n\rresource_type\x18\x05 \x01(\t\x12\x13\n\x0bresource_id\x18\x06 \x01(\t\x12\x10\n\x08\x64\x65\x63ision\x18\x07 \x01(\t\x12\x11\n\tworkspace\x18\x08 \x01(\t\x12\r\n\x05limit\x18\t \x01(\x05\x12\x0e\n\x06offset\x18\n \x01(\x05\"\xb9\x01\n\x0f\x41\x43LGrantRequest\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x05 \x01(\x05\x12\x12\n\ngranted_by\x18\x06 \x01(\t\x12\x0e\n\x06reason\x18\x07 \x01(\t\x12\x12\n\nexpires_at\x18\x08 \x01(\x03\"a\n\x15\x41\x43LSetFallbackRequest\x12\x15\n\rrule_category\x18\x01 \x01(\t\x12\x1d\n\x15\x66\x61llback_access_level\x18\x02 \x01(\x05\x12\x12\n\nupdated_by\x18\x03 \x01(\t\"\xff\x01\n\x17\x41\x43LAuthorityGrantFilter\x12\x15\n\rroot_grant_id\x18\x01 \x01(\t\x12\x14\n\x0csubject_type\x18\x02 \x01(\t\x12\x12\n\nsubject_id\x18\x03 \x01(\t\x12\x15\n\rdelegate_type\x18\x04 \x01(\t\x12\x13\n\x0b\x64\x65legate_id\x18\x05 \x01(\t\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12\x17\n\x0finclude_revoked\x18\x08 \x01(\x08\x12\x13\n\x0b\x61\x63tive_only\x18\t \x01(\x08\x12\r\n\x05limit\x18\n \x01(\x05\x12\x0e\n\x06offset\x18\x0b \x01(\x05\"N\n#ACLAuthorityGrantResourceScopeEntry\x12\x15\n\rresource_type\x18\x01 \x01(\t\x12\x10\n\x08patterns\x18\x02 \x03(\t\"\xa9\x05\n\x18\x41\x43LAuthorityGrantRequest\x12(\n\x07subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fparent_grant_id\x18\x05 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x06 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\x07 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\x08 \x03(\t\x12\x46\n\x0eresource_scope\x18\t \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\n \x03(\t\x12\x18\n\x10max_access_level\x18\x0b \x01(\x05\x12\x15\n\raudience_type\x18\x0c \x01(\t\x12\x13\n\x0b\x61udience_id\x18\r \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x0e \x01(\x08\x12\x12\n\nexpires_at\x18\x0f \x01(\x03\x12\x17\n\x0frenewable_until\x18\x10 \x01(\x03\x12\x0e\n\x06reason\x18\x11 \x01(\t\x12\x43\n\x08metadata\x18\x12 \x03(\x0b\x32\x31.aether.v1.ACLAuthorityGrantRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x1d\x41\x43LRenewAuthorityGrantRequest\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x12\n\nexpires_at\x18\x02 \x01(\x03\x12\x16\n\x0e\x65xtend_seconds\x18\x03 \x01(\x05\"\xf5\x01\n\x0b\x41\x43LRuleInfo\x12\x0f\n\x07rule_id\x18\x01 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x02 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x03 \x01(\t\x12\x15\n\rresource_type\x18\x04 \x01(\t\x12\x13\n\x0bresource_id\x18\x05 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x06 \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x07 \x01(\t\x12\x12\n\ngranted_by\x18\x08 \x01(\t\x12\x12\n\ngranted_at\x18\t \x01(\x03\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x0e\n\x06reason\x18\x0b \x01(\t\"\xac\x01\n\x15\x41\x43LFallbackPolicyInfo\x12\x11\n\tpolicy_id\x18\x01 \x01(\t\x12\x15\n\rrule_category\x18\x02 \x01(\t\x12\x1d\n\x15\x66\x61llback_access_level\x18\x03 \x01(\x05\x12\"\n\x1a\x66\x61llback_access_level_name\x18\x04 \x01(\t\x12\x12\n\nupdated_by\x18\x05 \x01(\t\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\"\xc3\x03\n\x11\x41\x43LAuditEntryInfo\x12\x10\n\x08\x61udit_id\x18\x01 \x01(\x03\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x10\n\x08\x64\x65\x63ision\x18\x03 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x04 \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x05 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x06 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x07 \x01(\t\x12\x15\n\rresource_type\x18\x08 \x01(\t\x12\x13\n\x0bresource_id\x18\t \x01(\t\x12\x11\n\toperation\x18\n \x01(\t\x12\x11\n\tworkspace\x18\x0b \x01(\t\x12\x0f\n\x07rule_id\x18\r \x01(\t\x12\x18\n\x10\x66\x61llback_applied\x18\x0e \x01(\x08\x12\x12\n\ngateway_id\x18\x0f \x01(\t\x12\x12\n\nsession_id\x18\x10 \x01(\t\x12<\n\x08metadata\x18\x11 \x03(\x0b\x32*.aether.v1.ACLAuditEntryInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01J\x04\x08\x0c\x10\r\"\xb4\x06\n\x15\x41\x43LAuthorityGrantInfo\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x15\n\rroot_grant_id\x18\x02 \x01(\t\x12(\n\x07subject\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x05 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x06 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fparent_grant_id\x18\x07 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x08 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\t \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\n \x03(\t\x12\x46\n\x0eresource_scope\x18\x0b \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x0c \x03(\t\x12\x18\n\x10max_access_level\x18\r \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x0e \x01(\t\x12\x15\n\raudience_type\x18\x0f \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x10 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x11 \x01(\x08\x12\x12\n\nexpires_at\x18\x12 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x13 \x01(\x03\x12\x12\n\nrenewed_at\x18\x14 \x01(\x03\x12\x0f\n\x07revoked\x18\x15 \x01(\x08\x12\x12\n\nrevoked_at\x18\x16 \x01(\x03\x12\x0e\n\x06reason\x18\x17 \x01(\t\x12@\n\x08metadata\x18\x18 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantInfo.MetadataEntry\x12\x12\n\ncreated_at\x18\x19 \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\x10\x41\x43LCleanupResult\x12\x15\n\rdeleted_count\x18\x01 \x01(\x03\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xb5\x01\n\x0f\x41\x43LGroupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x12\n\ncreated_by\x18\x03 \x01(\t\x12:\n\x08metadata\x18\x04 \x03(\x0b\x32(.aether.v1.ACLGroupRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb3\x01\n\x0e\x41\x43LRoleRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x12\n\ncreated_by\x18\x03 \x01(\t\x12\x39\n\x08metadata\x18\x04 \x03(\x0b\x32\'.aether.v1.ACLRoleRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"g\n\x15\x41\x43LGroupMemberRequest\x12\x13\n\x0bmember_type\x18\x01 \x01(\t\x12\x11\n\tmember_id\x18\x02 \x01(\t\x12\x12\n\ngranted_by\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\"n\n\x18\x41\x43LRoleAssignmentRequest\x12\x15\n\rassignee_type\x18\x01 \x01(\t\x12\x13\n\x0b\x61ssignee_id\x18\x02 \x01(\t\x12\x12\n\ngranted_by\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\"\xdb\x01\n\x0c\x41\x43LGroupInfo\x12\x10\n\x08group_id\x18\x01 \x01(\t\x12\x12\n\ngroup_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x12\n\ncreated_by\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x37\n\x08metadata\x18\x06 \x03(\x0b\x32%.aether.v1.ACLGroupInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd7\x01\n\x0b\x41\x43LRoleInfo\x12\x0f\n\x07role_id\x18\x01 \x01(\t\x12\x11\n\trole_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x12\n\ncreated_by\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x36\n\x08metadata\x18\x06 \x03(\x0b\x32$.aether.v1.ACLRoleInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8c\x01\n\x12\x41\x43LGroupMemberInfo\x12\x12\n\ngroup_name\x18\x01 \x01(\t\x12\x13\n\x0bmember_type\x18\x02 \x01(\t\x12\x11\n\tmember_id\x18\x03 \x01(\t\x12\x12\n\ngranted_by\x18\x04 \x01(\t\x12\x12\n\ngranted_at\x18\x05 \x01(\x03\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\"\x92\x01\n\x15\x41\x43LRoleAssignmentInfo\x12\x11\n\trole_name\x18\x01 \x01(\t\x12\x15\n\rassignee_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61ssignee_id\x18\x03 \x01(\t\x12\x12\n\ngranted_by\x18\x04 \x01(\t\x12\x12\n\ngranted_at\x18\x05 \x01(\x03\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\"v\n\x19\x41\x43LAccessContributionInfo\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0f\n\x07rule_id\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x03 \x01(\x05\x12\x10\n\x08resource\x18\x04 \x01(\t\x12\x0f\n\x07\x65xpired\x18\x05 \x01(\x08\"\xe9\x01\n\x18\x41\x43LAccessExplanationInfo\x12\x11\n\tprincipal\x18\x01 \x01(\t\x12\x10\n\x08subjects\x18\x02 \x03(\t\x12;\n\rcontributions\x18\x03 \x03(\x0b\x32$.aether.v1.ACLAccessContributionInfo\x12\x0f\n\x07\x61llowed\x18\x04 \x01(\x08\x12\x10\n\x08\x64\x65\x63ision\x18\x05 \x01(\t\x12\x1e\n\x16\x65\x66\x66\x65\x63tive_access_level\x18\x06 \x01(\x05\x12\x18\n\x10\x66\x61llback_applied\x18\x07 \x01(\x08\x12\x0e\n\x06reason\x18\x08 \x01(\t\"\xdd\x06\n\x0b\x41\x43LResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12$\n\x04rule\x18\x04 \x01(\x0b\x32\x16.aether.v1.ACLRuleInfo\x12%\n\x05rules\x18\x05 \x03(\x0b\x32\x16.aether.v1.ACLRuleInfo\x12\x13\n\x0btotal_rules\x18\x06 \x01(\x05\x12\x39\n\x0f\x66\x61llback_policy\x18\x08 \x01(\x0b\x32 .aether.v1.ACLFallbackPolicyInfo\x12\x33\n\raudit_entries\x18\t \x03(\x0b\x32\x1c.aether.v1.ACLAuditEntryInfo\x12\x1b\n\x13total_audit_entries\x18\n \x01(\x05\x12\x33\n\x0e\x63leanup_result\x18\x0b \x01(\x0b\x32\x1b.aether.v1.ACLCleanupResult\x12\x39\n\x0f\x61uthority_grant\x18\r \x01(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12:\n\x10\x61uthority_grants\x18\x0e \x03(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\x1e\n\x16total_authority_grants\x18\x0f \x01(\x05\x12\x12\n\nrequest_id\x18\x0c \x01(\t\x12&\n\x05group\x18\x10 \x01(\x0b\x32\x17.aether.v1.ACLGroupInfo\x12\'\n\x06groups\x18\x11 \x03(\x0b\x32\x17.aether.v1.ACLGroupInfo\x12$\n\x04role\x18\x12 \x01(\x0b\x32\x16.aether.v1.ACLRoleInfo\x12%\n\x05roles\x18\x13 \x03(\x0b\x32\x16.aether.v1.ACLRoleInfo\x12\x34\n\rgroup_members\x18\x14 \x03(\x0b\x32\x1d.aether.v1.ACLGroupMemberInfo\x12:\n\x10role_assignments\x18\x15 \x03(\x0b\x32 .aether.v1.ACLRoleAssignmentInfo\x12\x38\n\x0b\x65xplanation\x18\x16 \x01(\x0b\x32#.aether.v1.ACLAccessExplanationInfoJ\x04\x08\x07\x10\x08\"\xb5\x05\n\x17\x41uthorityGrantOperation\x12\x35\n\x02op\x18\x01 \x01(\x0e\x32).aether.v1.AuthorityGrantOperation.OpType\x12\x10\n\x08grant_id\x18\x02 \x01(\t\x12\x42\n\x10\x65xchange_request\x18\x03 \x01(\x0b\x32(.aether.v1.AuthorityGrantExchangeRequest\x12>\n\x0e\x64\x65rive_request\x18\x04 \x01(\x0b\x32&.aether.v1.AuthorityGrantDeriveRequest\x12?\n\rrenew_request\x18\x05 \x01(\x0b\x32(.aether.v1.ACLRenewAuthorityGrantRequest\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12:\n\x0clist_request\x18\x07 \x01(\x0b\x32$.aether.v1.AuthorityGrantListRequest\x12M\n\x16\x62\x61tch_exchange_request\x18\x08 \x01(\x0b\x32-.aether.v1.AuthorityGrantBatchExchangeRequest\x12R\n\x19\x64\x65rive_for_target_request\x18\t \x01(\x0b\x32/.aether.v1.AuthorityGrantDeriveForTargetRequest\"\x98\x01\n\x06OpType\x12\x0c\n\x08\x45XCHANGE\x10\x00\x12\n\n\x06\x44\x45RIVE\x10\x01\x12\x07\n\x03GET\x10\x02\x12\t\n\x05RENEW\x10\x03\x12\n\n\x06REVOKE\x10\x04\x12\x12\n\x0eLIST_MY_GRANTS\x10\x05\x12\x15\n\x11LIST_GRANTS_ON_ME\x10\x06\x12\x12\n\x0e\x42\x41TCH_EXCHANGE\x10\x07\x12\x15\n\x11\x44\x45RIVE_FOR_TARGET\x10\x08\"\x85\x04\n\x1d\x41uthorityGrantExchangeRequest\x12\x19\n\x11source_session_id\x18\x01 \x01(\t\x12\x17\n\x0fworkspace_scope\x18\x02 \x03(\t\x12\x46\n\x0eresource_scope\x18\x03 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x04 \x03(\t\x12\x18\n\x10max_access_level\x18\x05 \x01(\x05\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x08 \x01(\x08\x12\x12\n\nexpires_at\x18\t \x01(\x03\x12\x17\n\x0frenewable_until\x18\n \x01(\x03\x12\x14\n\x0cmay_delegate\x18\x0b \x01(\x08\x12\x16\n\x0eremaining_hops\x18\x0c \x01(\x05\x12\x0e\n\x06reason\x18\r \x01(\t\x12H\n\x08metadata\x18\x0e \x03(\x0b\x32\x36.aether.v1.AuthorityGrantExchangeRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xaa\x04\n\x1b\x41uthorityGrantDeriveRequest\x12\x17\n\x0fparent_grant_id\x18\x01 \x01(\t\x12)\n\x08\x64\x65legate\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fworkspace_scope\x18\x03 \x03(\t\x12\x46\n\x0eresource_scope\x18\x04 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x05 \x03(\t\x12\x18\n\x10max_access_level\x18\x06 \x01(\x05\x12\x15\n\raudience_type\x18\x07 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x08 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\t \x01(\x08\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x17\n\x0frenewable_until\x18\x0b \x01(\x03\x12\x14\n\x0cmay_delegate\x18\x0c \x01(\x08\x12\x16\n\x0eremaining_hops\x18\r \x01(\x05\x12\x0e\n\x06reason\x18\x0e \x01(\t\x12\x46\n\x08metadata\x18\x0f \x03(\x0b\x32\x34.aether.v1.AuthorityGrantDeriveRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xef\x01\n\x16\x41uthorityGrantResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12/\n\x05grant\x18\x04 \x01(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\x12\x30\n\x06grants\x18\x06 \x03(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\r\n\x05total\x18\x07 \x01(\x05\x12\x1e\n\x16\x63\x61\x63he_hint_ttl_seconds\x18\x08 \x01(\x05\"\x7f\n\x19\x41uthorityGrantListRequest\x12\x15\n\raudience_type\x18\x01 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x02 \x01(\t\x12\x17\n\x0finclude_revoked\x18\x03 \x01(\x08\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\"}\n\"AuthorityGrantBatchExchangeRequest\x12:\n\x08requests\x18\x01 \x03(\x0b\x32(.aether.v1.AuthorityGrantExchangeRequest\x12\x1b\n\x13stop_on_first_error\x18\x02 \x01(\x08\"\xb2\x02\n$AuthorityGrantDeriveForTargetRequest\x12\x17\n\x0fparent_grant_id\x18\x01 \x01(\t\x12\'\n\x06target\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x03 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x04 \x01(\t\x12\x17\n\x0foperation_scope\x18\x05 \x03(\t\x12\x18\n\x10max_access_level\x18\x06 \x01(\x05\x12\x12\n\nexpires_at\x18\x07 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x08 \x01(\x03\x12\x14\n\x0cmay_delegate\x18\t \x01(\x08\x12\x16\n\x0eremaining_hops\x18\n \x01(\x05\x12\x0e\n\x06reason\x18\x0b \x01(\t\"\xc3\x01\n\x11\x41uthorityIdentity\x12(\n\x07subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\"\xd1\x01\n\rAuthoritySpan\x12\x17\n\x0fworkspace_scope\x18\x01 \x03(\t\x12\x18\n\x10max_access_level\x18\x02 \x01(\x05\x12\x15\n\raudience_type\x18\x03 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x04 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x05 \x01(\x08\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x07 \x01(\x03\x12\x0f\n\x07revoked\x18\x08 \x01(\x08\"x\n\x18\x41uthorityGrantRevocation\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x15\n\rroot_grant_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrevoked_at\x18\x04 \x01(\x03\x12\x0f\n\x07\x63\x61scade\x18\x05 \x01(\x08\"_\n\x1d\x41uthorityRequestRoutingTarget\x12*\n\tprincipal\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x12\n\ncapability\x18\x02 \x01(\t\"M\n\"AuthorityRequestResourceScopeEntry\x12\x15\n\rresource_type\x18\x01 \x01(\t\x12\x10\n\x08patterns\x18\x02 \x03(\t\"\xc7\x06\n\x10\x41uthorityRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x31\n\x06status\x18\x02 \x01(\x0e\x32!.aether.v1.AuthorityRequestStatus\x12\x31\n\x10requesting_actor\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12/\n\x0etarget_subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x1f\n\x17\x64\x65sired_workspace_scope\x18\x05 \x03(\t\x12M\n\x16\x64\x65sired_resource_scope\x18\x06 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17\x64\x65sired_operation_scope\x18\x07 \x03(\t\x12\x36\n\x16requested_access_level\x18\x08 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12\"\n\x1arequested_duration_seconds\x18\t \x01(\x03\x12\x15\n\raudience_type\x18\n \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x0b \x01(\t\x12@\n\x0erouting_target\x18\x0c \x01(\x0b\x32(.aether.v1.AuthorityRequestRoutingTarget\x12\x0e\n\x06reason\x18\r \x01(\t\x12\x0f\n\x07task_id\x18\x0e \x01(\t\x12;\n\x08metadata\x18\x0f \x03(\x0b\x32).aether.v1.AuthorityRequest.MetadataEntry\x12\x12\n\ncreated_at\x18\x10 \x01(\x03\x12\x12\n\nexpires_at\x18\x11 \x01(\x03\x12\x13\n\x0bresolved_at\x18\x12 \x01(\x03\x12\x18\n\x10granted_grant_id\x18\x13 \x01(\t\x12,\n\x0bresolved_by\x18\x14 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x19\n\x11resolution_reason\x18\x15 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xfa\x04\n\x1d\x43reateAuthorityRequestPayload\x12\x31\n\x10requesting_actor\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12/\n\x0etarget_subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x1f\n\x17\x64\x65sired_workspace_scope\x18\x03 \x03(\t\x12M\n\x16\x64\x65sired_resource_scope\x18\x04 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17\x64\x65sired_operation_scope\x18\x05 \x03(\t\x12\x36\n\x16requested_access_level\x18\x06 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12\"\n\x1arequested_duration_seconds\x18\x07 \x01(\x03\x12\x15\n\raudience_type\x18\x08 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\t \x01(\t\x12@\n\x0erouting_target\x18\n \x01(\x0b\x32(.aether.v1.AuthorityRequestRoutingTarget\x12\x0e\n\x06reason\x18\x0b \x01(\t\x12\x0f\n\x07task_id\x18\x0c \x01(\t\x12H\n\x08metadata\x18\r \x03(\x0b\x32\x36.aether.v1.CreateAuthorityRequestPayload.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xca\x03\n\x1eResolveAuthorityRequestPayload\x12\x44\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32\x32.aether.v1.ResolveAuthorityRequestPayload.Decision\x12\x1f\n\x17granted_workspace_scope\x18\x02 \x03(\t\x12M\n\x16granted_resource_scope\x18\x03 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17granted_operation_scope\x18\x04 \x03(\t\x12\x34\n\x14granted_access_level\x18\x05 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12 \n\x18granted_duration_seconds\x18\x06 \x01(\x03\x12\x0e\n\x06reason\x18\x07 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x08 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\t \x01(\x05\";\n\x08\x44\x65\x63ision\x12\x18\n\x14\x44\x45\x43ISION_UNSPECIFIED\x10\x00\x12\x0b\n\x07\x41PPROVE\x10\x01\x12\x08\n\x04\x44\x45NY\x10\x02\"\xa0\x01\n\x1a\x41uthorityRequestListFilter\x12\x31\n\x06status\x18\x01 \x01(\x0e\x32!.aether.v1.AuthorityRequestStatus\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x1d\n\x15matching_capabilities\x18\x05 \x03(\t\"\xb5\x03\n\x19\x41uthorityRequestOperation\x12\x37\n\x02op\x18\x01 \x01(\x0e\x32+.aether.v1.AuthorityRequestOperation.OpType\x12\x12\n\nrequest_id\x18\x02 \x01(\t\x12\x38\n\x06\x63reate\x18\x03 \x01(\x0b\x32(.aether.v1.CreateAuthorityRequestPayload\x12:\n\x07resolve\x18\x04 \x01(\x0b\x32).aether.v1.ResolveAuthorityRequestPayload\x12:\n\x0blist_filter\x18\x05 \x01(\x0b\x32%.aether.v1.AuthorityRequestListFilter\x12\x19\n\x11\x63lient_request_id\x18\x06 \x01(\t\x12\x0e\n\x06reason\x18\x07 \x01(\t\"n\n\x06OpType\x12$\n AUTHORITY_REQUEST_OP_UNSPECIFIED\x10\x00\x12\n\n\x06\x43REATE\x10\x01\x12\x07\n\x03GET\x10\x02\x12\x10\n\x0cLIST_PENDING\x10\x03\x12\x0b\n\x07RESOLVE\x10\x04\x12\n\n\x06\x43\x41NCEL\x10\x05\"\xd0\x01\n!AuthorityRequestOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_request_id\x18\x03 \x01(\t\x12,\n\x07request\x18\x04 \x01(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12-\n\x08requests\x18\x05 \x03(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\"\x8b\x03\n\x15\x41uthorityRequestEvent\x12>\n\nevent_type\x18\x01 \x01(\x0e\x32*.aether.v1.AuthorityRequestEvent.EventType\x12,\n\x07request\x18\x02 \x01(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12\x12\n\nemitted_at\x18\x03 \x01(\x03\"\xef\x01\n\tEventType\x12\'\n#AUTHORITY_REQUEST_EVENT_UNSPECIFIED\x10\x00\x12#\n\x1f\x41UTHORITY_REQUEST_EVENT_CREATED\x10\x01\x12$\n AUTHORITY_REQUEST_EVENT_APPROVED\x10\x02\x12\"\n\x1e\x41UTHORITY_REQUEST_EVENT_DENIED\x10\x03\x12#\n\x1f\x41UTHORITY_REQUEST_EVENT_EXPIRED\x10\x04\x12%\n!AUTHORITY_REQUEST_EVENT_CANCELLED\x10\x05\"\x84\x02\n\x0eTokenOperation\x12,\n\x02op\x18\x01 \x01(\x0e\x32 .aether.v1.TokenOperation.OpType\x12\x10\n\x08token_id\x18\x02 \x01(\t\x12\x35\n\x0e\x63reate_request\x18\x03 \x01(\x0b\x32\x1d.aether.v1.TokenCreateRequest\x12&\n\x06\x66ilter\x18\x04 \x01(\x0b\x32\x16.aether.v1.TokenFilter\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"?\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\n\n\x06\x43REATE\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03\x12\n\n\x06REVOKE\x10\x04\"\x94\x01\n\x12TokenCreateRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x02 \x01(\t\x12\x1a\n\x12workspace_patterns\x18\x03 \x03(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\x12\x18\n\x10\x65xpires_in_hours\x18\x05 \x01(\x05\x12\x12\n\ncreated_by\x18\x06 \x01(\t\"E\n\x0bTokenFilter\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x17\n\x0finclude_revoked\x18\x03 \x01(\x08\"\xf4\x01\n\tTokenInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x03 \x01(\t\x12\x1a\n\x12workspace_patterns\x18\x04 \x03(\t\x12\x0e\n\x06scopes\x18\x05 \x03(\t\x12\x12\n\ncreated_by\x18\x06 \x01(\t\x12\x12\n\nexpires_at\x18\x07 \x01(\x03\x12\x14\n\x0clast_used_at\x18\x08 \x01(\x03\x12\x0f\n\x07revoked\x18\t \x01(\x08\x12\x12\n\nrevoked_at\x18\n \x01(\x03\x12\x12\n\ncreated_at\x18\x0b \x01(\x03\x12\x12\n\nupdated_at\x18\x0c \x01(\x03\"\xfa\x01\n\rTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12#\n\x05token\x18\x04 \x01(\x0b\x32\x14.aether.v1.TokenInfo\x12$\n\x06tokens\x18\x05 \x03(\x0b\x32\x14.aether.v1.TokenInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x17\n\x0fplaintext_token\x18\x07 \x01(\t\x12+\n\rcreated_token\x18\x08 \x01(\x0b\x32\x14.aether.v1.TokenInfo\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xb6\x02\n\x0eProgressReport\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\x12\x12\n\ncompletion\x18\x03 \x01(\x01\x12\x0f\n\x07summary\x18\x04 \x01(\t\x12%\n\x04step\x18\x05 \x01(\x0b\x32\x17.aether.v1.ProgressStep\x12\x11\n\trecipient\x18\x06 \x01(\t\x12\x12\n\nrequest_id\x18\x07 \x01(\t\x12\x39\n\x08metadata\x18\x08 \x03(\x0b\x32\'.aether.v1.ProgressReport.MetadataEntry\x12%\n\x04kind\x18\t \x01(\x0e\x32\x17.aether.v1.ProgressKind\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"f\n\x0cProgressStep\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65tail\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x05\x12\x13\n\x0btotal_steps\x18\x04 \x01(\x05\x12\x11\n\tstep_type\x18\x05 \x01(\t\"\xef\x02\n\x0eProgressUpdate\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x12\n\ncompletion\x18\x04 \x01(\x01\x12\x0f\n\x07summary\x18\x05 \x01(\t\x12%\n\x04step\x18\x06 \x01(\x0b\x32\x17.aether.v1.ProgressStep\x12\x14\n\x0ctimestamp_ms\x18\x07 \x01(\x03\x12\x11\n\tworkspace\x18\x08 \x01(\t\x12\x12\n\nrequest_id\x18\t \x01(\t\x12\x39\n\x08metadata\x18\n \x03(\x0b\x32\'.aether.v1.ProgressUpdate.MetadataEntry\x12\x11\n\trecipient\x18\x0b \x01(\t\x12%\n\x04kind\x18\x0c \x01(\x0e\x32\x17.aether.v1.ProgressKind\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd9\x05\n\x11WorkflowOperation\x12/\n\x02op\x18\x01 \x01(\x0e\x32#.aether.v1.WorkflowOperation.OpType\x12\n\n\x02id\x18\x02 \x01(\t\x12\x14\n\x0csecondary_id\x18\x03 \x01(\t\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x15\n\rstatus_filter\x18\x07 \x01(\t\"\xa4\x04\n\x06OpType\x12\x0e\n\nLIST_RULES\x10\x00\x12\x0c\n\x08GET_RULE\x10\x01\x12\x0f\n\x0b\x43REATE_RULE\x10\x02\x12\x0f\n\x0bUPDATE_RULE\x10\x03\x12\x0f\n\x0b\x44\x45LETE_RULE\x10\x04\x12\x12\n\x0eLIST_WORKFLOWS\x10\x05\x12\x10\n\x0cGET_WORKFLOW\x10\x06\x12\x13\n\x0f\x43REATE_WORKFLOW\x10\x07\x12\x13\n\x0f\x44\x45LETE_WORKFLOW\x10\x08\x12\x12\n\x0eLIST_SCHEDULES\x10\t\x12\x13\n\x0f\x43REATE_SCHEDULE\x10\n\x12\x13\n\x0f\x44\x45LETE_SCHEDULE\x10\x0b\x12\x13\n\x0fLIST_EXECUTIONS\x10\x0c\x12\x11\n\rGET_EXECUTION\x10\r\x12\x14\n\x10\x43\x41NCEL_EXECUTION\x10\x0e\x12\x17\n\x13LIST_STATE_MACHINES\x10\x0f\x12\x15\n\x11GET_STATE_MACHINE\x10\x10\x12\x18\n\x14\x43REATE_STATE_MACHINE\x10\x11\x12\x18\n\x14\x44\x45LETE_STATE_MACHINE\x10\x12\x12\x15\n\x11LIST_SM_INSTANCES\x10\x13\x12\x13\n\x0fGET_SM_INSTANCE\x10\x14\x12\x16\n\x12\x43REATE_SM_INSTANCE\x10\x15\x12\x11\n\rSEND_SM_EVENT\x10\x16\x12\x13\n\x0fUPSERT_SCHEDULE\x10\x17\x12\x0e\n\nLIST_JOINS\x10\x18\x12\x0c\n\x08GET_JOIN\x10\x19\x12\x0f\n\x0b\x43\x41NCEL_JOIN\x10\x1a\"z\n\x10WorkflowResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"\xaa\x02\n\x0fMessageEnvelope\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x14\n\x0ctimestamp_ms\x18\x04 \x01(\x03\x12:\n\x08metadata\x18\x05 \x03(\x0b\x32(.aether.v1.MessageEnvelope.MetadataEntry\x12\x11\n\tworkspace\x18\x06 \x01(\t\x12\x32\n\x11on_behalf_subject\x18\x07 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf7\x03\n\nAuditQuery\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x03\x12\x12\n\nevent_type\x18\x04 \x01(\t\x12\x12\n\nactor_type\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x06 \x01(\t\x12\x15\n\rresource_type\x18\x07 \x01(\t\x12\x13\n\x0bresource_id\x18\x08 \x01(\t\x12\x11\n\toperation\x18\t \x01(\t\x12\x11\n\tworkspace\x18\n \x01(\t\x12\x15\n\ronly_failures\x18\x0b \x01(\x08\x12\r\n\x05limit\x18\x0c \x01(\x05\x12\x0e\n\x06offset\x18\r \x01(\x05\x12\x14\n\x0csubject_type\x18\x0e \x01(\t\x12\x12\n\nsubject_id\x18\x0f \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\x10 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x11 \x01(\t\x12\x36\n\rauthorization\x18\x12 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x1b\n\x13\x65xclude_actor_types\x18\x13 \x03(\t\x12\x1a\n\x12\x65xclude_workspaces\x18\x14 \x03(\t\x12\x1e\n\x16\x65xclude_service_direct\x18\x15 \x01(\x08\"\x85\x01\n\x12\x41uditQueryResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x07\x65ntries\x18\x04 \x03(\x0b\x32\x15.aether.v1.AuditEntry\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\"\x8a\x04\n\nAuditEntry\x12\x10\n\x08\x61udit_id\x18\x01 \x01(\x03\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x12\n\nevent_type\x18\x03 \x01(\t\x12\x12\n\nactor_type\x18\x04 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x05 \x01(\t\x12\x15\n\rresource_type\x18\x06 \x01(\t\x12\x13\n\x0bresource_id\x18\x07 \x01(\t\x12\x11\n\toperation\x18\x08 \x01(\t\x12\x11\n\tworkspace\x18\t \x01(\t\x12\x12\n\nsession_id\x18\n \x01(\t\x12\x12\n\ngateway_id\x18\x0b \x01(\t\x12\x0f\n\x07success\x18\x0c \x01(\x08\x12\x15\n\rerror_message\x18\r \x01(\t\x12\x15\n\rmetadata_json\x18\x0e \x01(\t\x12\x14\n\x0csubject_type\x18\x0f \x01(\t\x12\x12\n\nsubject_id\x18\x10 \x01(\t\x12\x19\n\x11root_subject_type\x18\x11 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x12 \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\x13 \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x14 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x15 \x01(\t\x12!\n\x19parent_authority_grant_id\x18\x16 \x01(\t\x12\x0e\n\x06source\x18\x17 \x01(\t\"\xb7\x02\n\x17SubmitAuditEventRequest\x12\x12\n\nevent_type\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\x11\n\tworkspace\x18\x05 \x01(\t\x12\x0f\n\x07success\x18\x06 \x01(\x08\x12\x15\n\rerror_message\x18\x07 \x01(\t\x12\x42\n\x08metadata\x18\x08 \x03(\x0b\x32\x30.aether.v1.SubmitAuditEventRequest.MetadataEntry\x12\x19\n\x11\x63lient_request_id\x18\t \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"q\n\x18SubmitAuditEventResponse\x12\x19\n\x11\x63lient_request_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x12\n\nerror_code\x18\x03 \x01(\t\x12\x15\n\rerror_message\x18\x04 \x01(\t\"\xfe\x03\n\x10ProxyHttpRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x02 \x01(\t\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x39\n\x07headers\x18\x05 \x03(\x0b\x32(.aether.v1.ProxyHttpRequest.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x06 \x01(\x0c\x12\x14\n\x0c\x62ody_chunked\x18\x07 \x01(\x08\x12\x36\n\rauthorization\x18\x08 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rapp_workspace\x18\t \x01(\t\x12\x12\n\ntimeout_ms\x18\n \x01(\x03\x12\x18\n\x10\x66ollow_redirects\x18\x0b \x01(\x08\x12\x14\n\x0c\x62\x61\x63kend_name\x18\x0c \x01(\t\x12$\n\x1cstream_response_indefinitely\x18\r \x01(\x08\x12\x1e\n\x16stream_idle_timeout_ms\x18\x0e \x01(\x03\x12\x1f\n\x17max_response_body_bytes\x18\x0f \x01(\x03\x12\x19\n\x11proxy_chain_depth\x18\x10 \x01(\r\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf2\x01\n\x11ProxyHttpResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x13\n\x0bstatus_code\x18\x02 \x01(\x05\x12:\n\x07headers\x18\x03 \x03(\x0b\x32).aether.v1.ProxyHttpResponse.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x12\x14\n\x0c\x62ody_chunked\x18\x05 \x01(\x08\x12$\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x15.aether.v1.ProxyError\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"d\n\x12ProxyHttpBodyChunk\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x12\n\nis_request\x18\x02 \x01(\x08\x12\x0b\n\x03seq\x18\x03 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0b\n\x03\x66in\x18\x05 \x01(\x08\"\xe2\x01\n\nProxyError\x12(\n\x04kind\x18\x01 \x01(\x0e\x32\x1a.aether.v1.ProxyError.Kind\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x98\x01\n\x04Kind\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x44IAL_FAILED\x10\x01\x12\x0b\n\x07TIMEOUT\x10\x02\x12\x12\n\x0eUPSTREAM_RESET\x10\x03\x12\x0e\n\nACL_DENIED\x10\x04\x12\x17\n\x13SIDECAR_UNAVAILABLE\x10\x05\x12\x15\n\x11PAYLOAD_TOO_LARGE\x10\x06\x12\x11\n\rDECODE_FAILED\x10\x07\"\xbd\x03\n\nTunnelOpen\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x02 \x01(\t\x12\x30\n\x08protocol\x18\x03 \x01(\x0e\x32\x1e.aether.v1.TunnelOpen.Protocol\x12\x13\n\x0bremote_hint\x18\x04 \x01(\t\x12\x35\n\x08metadata\x18\x05 \x03(\x0b\x32#.aether.v1.TunnelOpen.MetadataEntry\x12\x36\n\rauthorization\x18\x06 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x17\n\x0fidle_timeout_ms\x18\x07 \x01(\x03\x12\x11\n\tmax_bytes\x18\x08 \x01(\x03\x12\x15\n\rsession_token\x18\t \x01(\t\x12\x14\n\x0c\x62\x61\x63kend_name\x18\n \x01(\t\x12\x19\n\x11proxy_chain_depth\x18\x0b \x01(\r\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x08Protocol\x12\x07\n\x03TCP\x10\x00\x12\x07\n\x03UDP\x10\x01\x12\r\n\tWEBSOCKET\x10\x02\"G\n\nTunnelData\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x0b\n\x03\x66in\x18\x04 \x01(\x08\"\xad\x01\n\x0bTunnelClose\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12-\n\x06reason\x18\x02 \x01(\x0e\x32\x1d.aether.v1.TunnelClose.Reason\x12\x0e\n\x06\x64\x65tail\x18\x03 \x01(\t\"L\n\x06Reason\x12\n\n\x06NORMAL\x10\x00\x12\x0e\n\nPEER_RESET\x10\x01\x12\x10\n\x0cIDLE_TIMEOUT\x10\x02\x12\t\n\x05QUOTA\x10\x03\x12\t\n\x05\x45RROR\x10\x04\"@\n\tTunnelAck\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63k_seq\x18\x02 \x01(\r\x12\x0f\n\x07\x63redits\x18\x03 \x01(\r\"\xbd\x01\n\x17ResolveAuthorityRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12&\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x10\n\x08grant_id\x18\x03 \x01(\t\x12(\n\x07subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x05 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x06 \x01(\t\"z\n\x18ResolveAuthorityResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12/\n\tauthority\x18\x04 \x01(\x0b\x32\x1c.aether.v1.ResolvedAuthority\"\x93\x01\n\x11ResolvedAuthority\x12&\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12(\n\x07subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12,\n\x05grant\x18\x03 \x01(\x0b\x32\x1d.aether.v1.AuthorityGrantInfo\"\x88\x02\n\x12\x41uthorityGrantInfo\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x14\n\x0csubject_type\x18\x02 \x01(\t\x12\x12\n\nsubject_id\x18\x03 \x01(\t\x12\x19\n\x11root_subject_type\x18\x04 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x05 \x01(\t\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12\x18\n\x10max_access_level\x18\x08 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\t \x03(\t\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x0f\n\x07revoked\x18\x0b \x01(\x08\"Y\n\x17\x43onnectionStatusRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12*\n\tprincipal\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\"r\n\x18\x43onnectionStatusResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x11\n\tconnected\x18\x04 \x01(\x08\x12\x14\n\x0clast_seen_at\x18\x05 \x01(\x03\"\x9d\x02\n\x19TaskSubscriptionOperation\x12\x37\n\x02op\x18\x01 \x01(\x0e\x32+.aether.v1.TaskSubscriptionOperation.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x11\n\trecursive\x18\x03 \x01(\x08\x12\x19\n\x11\x63lient_request_id\x18\x04 \x01(\t\x12\x1f\n\x17start_timestamp_unix_ms\x18\x05 \x01(\x03\x12\x17\n\x0fsubscription_id\x18\x06 \x01(\t\"N\n\x06OpType\x12$\n TASK_SUBSCRIPTION_OP_UNSPECIFIED\x10\x00\x12\r\n\tSUBSCRIBE\x10\x01\x12\x0f\n\x0bUNSUBSCRIBE\x10\x02\"\x88\x01\n!TaskSubscriptionOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_request_id\x18\x03 \x01(\t\x12\x0f\n\x07task_id\x18\x04 \x01(\t\x12\x17\n\x0fsubscription_id\x18\x05 \x01(\t\"\xfb\x02\n\tTaskEvent\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x1a\n\x12\x65mitted_at_unix_ms\x18\x02 \x01(\x03\x12\x11\n\tworkspace\x18\x03 \x01(\t\x12\x16\n\x0eparent_task_id\x18\x04 \x01(\t\x12\x17\n\x0fsubscription_id\x18\x05 \x01(\t\x12;\n\x0estatus_changed\x18\n \x01(\x0b\x32!.aether.v1.TaskStatusChangedEventH\x00\x12\x30\n\x08progress\x18\x0b \x01(\x0b\x32\x1c.aether.v1.TaskProgressEventH\x00\x12=\n\x0f\x63hild_lifecycle\x18\x0c \x01(\x0b\x32\".aether.v1.TaskChildLifecycleEventH\x00\x12\x46\n\x11\x61uthority_request\x18\r \x01(\x0b\x32).aether.v1.TaskAuthorityRequestEventRelayH\x00\x42\x07\n\x05\x65vent\"~\n\x16TaskStatusChangedEvent\x12*\n\x0b\x66rom_status\x18\x01 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12(\n\tto_status\x18\x02 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\xb4\x01\n\x11TaskProgressEvent\x12\r\n\x05state\x18\x01 \x01(\t\x12\x10\n\x08progress\x18\x02 \x01(\x01\x12\x0f\n\x07message\x18\x03 \x01(\t\x12<\n\x08metadata\x18\x04 \x03(\x0b\x32*.aether.v1.TaskProgressEvent.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"p\n\x17TaskChildLifecycleEvent\x12\x15\n\rchild_task_id\x18\x01 \x01(\t\x12+\n\x0c\x63hild_status\x18\x02 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tlifecycle\x18\x03 \x01(\t\"Q\n\x1eTaskAuthorityRequestEventRelay\x12/\n\x05\x65vent\x18\x01 \x01(\x0b\x32 .aether.v1.AuthorityRequestEvent*t\n\x0bMessageType\x12\x1c\n\x18MESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04\x43HAT\x10\x01\x12\x0b\n\x07\x43ONTROL\x10\x02\x12\r\n\tTOOL_CALL\x10\x03\x12\t\n\x05\x45VENT\x10\x04\x12\n\n\x06METRIC\x10\x05\x12\n\n\x06OPAQUE\x10\x06*\xf2\x01\n\rPrincipalType\x12\x1e\n\x1aPRINCIPAL_TYPE_UNSPECIFIED\x10\x00\x12\x13\n\x0fPRINCIPAL_AGENT\x10\x01\x12\x12\n\x0ePRINCIPAL_TASK\x10\x02\x12\x12\n\x0ePRINCIPAL_USER\x10\x03\x12\x1a\n\x16PRINCIPAL_ORCHESTRATOR\x10\x04\x12\x1d\n\x19PRINCIPAL_WORKFLOW_ENGINE\x10\x05\x12\x1c\n\x18PRINCIPAL_METRICS_BRIDGE\x10\x06\x12\x14\n\x10PRINCIPAL_BRIDGE\x10\x07\x12\x15\n\x11PRINCIPAL_SERVICE\x10\x08*\xc4\x02\n\nTaskStatus\x12\x1b\n\x17TASK_STATUS_UNSPECIFIED\x10\x00\x12\x16\n\x12TASK_STATUS_QUEUED\x10\x01\x12\x17\n\x13TASK_STATUS_RUNNING\x10\x02\x12\x19\n\x15TASK_STATUS_COMPLETED\x10\x03\x12\x16\n\x12TASK_STATUS_FAILED\x10\x04\x12\x19\n\x15TASK_STATUS_CANCELLED\x10\x05\x12\x1d\n\x19TASK_STATUS_WAITING_INPUT\x10\x06\x12!\n\x1dTASK_STATUS_WAITING_AUTHORITY\x10\x07\x12\"\n\x1eTASK_STATUS_WAITING_DEPENDENCY\x10\x08\x12\x1a\n\x16TASK_STATUS_HIBERNATED\x10\t\x12\x18\n\x14TASK_STATUS_REJECTED\x10\n*\x81\x01\n\x0cHealthStatus\x12\x1d\n\x19HEALTH_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15HEALTH_STATUS_HEALTHY\x10\x01\x12\x1a\n\x16HEALTH_STATUS_DEGRADED\x10\x02\x12\x1b\n\x17HEALTH_STATUS_UNHEALTHY\x10\x03*s\n\x11HealthCheckStatus\x12#\n\x1fHEALTH_CHECK_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16HEALTH_CHECK_STATUS_OK\x10\x01\x12\x1d\n\x19HEALTH_CHECK_STATUS_ERROR\x10\x02*\xc3\x01\n\x0b\x41\x63\x63\x65ssLevel\x12\x1c\n\x18\x41\x43\x43\x45SS_LEVEL_UNSPECIFIED\x10\x00\x12\x15\n\x11\x41\x43\x43\x45SS_LEVEL_NONE\x10\x01\x12\x15\n\x11\x41\x43\x43\x45SS_LEVEL_READ\x10\x02\x12\x1a\n\x16\x41\x43\x43\x45SS_LEVEL_READWRITE\x10\x03\x12\x17\n\x13\x41\x43\x43\x45SS_LEVEL_MANAGE\x10\x04\x12\x16\n\x12\x41\x43\x43\x45SS_LEVEL_ADMIN\x10\x05\x12\x1b\n\x17\x41\x43\x43\x45SS_LEVEL_SUPERADMIN\x10\x06*=\n\x12TaskAssignmentMode\x12\x0f\n\x0bSELF_ASSIGN\x10\x00\x12\x0c\n\x08TARGETED\x10\x01\x12\x08\n\x04POOL\x10\x02*t\n\tTaskClass\x12\x1a\n\x16TASK_CLASS_UNSPECIFIED\x10\x00\x12\x1a\n\x16TASK_CLASS_INTERACTIVE\x10\x01\x12\x19\n\x15TASK_CLASS_BACKGROUND\x10\x02\x12\x14\n\x10TASK_CLASS_BATCH\x10\x03*\xa9\x01\n\x0cTaskPriority\x12\x1d\n\x19TASK_PRIORITY_UNSPECIFIED\x10\x00\x12\x16\n\x12TASK_PRIORITY_XLOW\x10\n\x12\x15\n\x11TASK_PRIORITY_LOW\x10\x14\x12\x18\n\x14TASK_PRIORITY_NORMAL\x10\x1e\x12\x16\n\x12TASK_PRIORITY_HIGH\x10(\x12\x19\n\x15TASK_PRIORITY_PREEMPT\x10\x32*\x99\x01\n\x0f\x42\x61\x63koffStrategy\x12 \n\x1c\x42\x41\x43KOFF_STRATEGY_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x42\x41\x43KOFF_STRATEGY_FIXED\x10\x01\x12 \n\x1c\x42\x41\x43KOFF_STRATEGY_EXPONENTIAL\x10\x02\x12&\n\"BACKOFF_STRATEGY_EXPLICIT_SCHEDULE\x10\x03*\x94\x01\n\nWaitReason\x12\x1b\n\x17WAIT_REASON_UNSPECIFIED\x10\x00\x12\x15\n\x11WAIT_REASON_INPUT\x10\x01\x12\x19\n\x15WAIT_REASON_AUTHORITY\x10\x02\x12\x1a\n\x16WAIT_REASON_DEPENDENCY\x10\x03\x12\x1b\n\x17WAIT_REASON_HIBERNATION\x10\x04*\x82\x02\n\x16\x41uthorityRequestStatus\x12(\n$AUTHORITY_REQUEST_STATUS_UNSPECIFIED\x10\x00\x12$\n AUTHORITY_REQUEST_STATUS_PENDING\x10\x01\x12%\n!AUTHORITY_REQUEST_STATUS_APPROVED\x10\x02\x12#\n\x1f\x41UTHORITY_REQUEST_STATUS_DENIED\x10\x03\x12$\n AUTHORITY_REQUEST_STATUS_EXPIRED\x10\x04\x12&\n\"AUTHORITY_REQUEST_STATUS_CANCELLED\x10\x05*t\n\x0cProgressKind\x12\x1d\n\x19PROGRESS_KIND_UNSPECIFIED\x10\x00\x12\x16\n\x12PROGRESS_KIND_CHAT\x10\x01\x12\x15\n\x11PROGRESS_KIND_APP\x10\x02\x12\x16\n\x12PROGRESS_KIND_TASK\x10\x03\x32X\n\rAetherGateway\x12G\n\x07\x43onnect\x12\x1a.aether.v1.UpstreamMessage\x1a\x1c.aether.v1.DownstreamMessage(\x01\x30\x01\x42-Z+github.com/scitrera/aether/api/proto;aetherb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x61\x65ther.proto\x12\taether.v1\"\xae\x0e\n\x0fUpstreamMessage\x12)\n\x04init\x18\x01 \x01(\x0b\x32\x19.aether.v1.InitConnectionH\x00\x12&\n\x04send\x18\x02 \x01(\x0b\x32\x16.aether.v1.SendMessageH\x00\x12\x36\n\x10switch_workspace\x18\x03 \x01(\x0b\x32\x1a.aether.v1.SwitchWorkspaceH\x00\x12\'\n\x05kv_op\x18\x04 \x01(\x0b\x32\x16.aether.v1.KVOperationH\x00\x12\x33\n\x0b\x63reate_task\x18\x05 \x01(\x0b\x32\x1c.aether.v1.CreateTaskRequestH\x00\x12\x37\n\rcheckpoint_op\x18\x06 \x01(\x0b\x32\x1e.aether.v1.CheckpointOperationH\x00\x12,\n\x0b\x61\x64min_query\x18\x07 \x01(\x0b\x32\x15.aether.v1.AdminQueryH\x00\x12\x31\n\nsession_op\x18\x08 \x01(\x0b\x32\x1b.aether.v1.SessionOperationH\x00\x12*\n\ntask_query\x18\t \x01(\x0b\x32\x14.aether.v1.TaskQueryH\x00\x12+\n\x07task_op\x18\n \x01(\x0b\x32\x18.aether.v1.TaskOperationH\x00\x12\x35\n\x0cworkspace_op\x18\x0b \x01(\x0b\x32\x1d.aether.v1.WorkspaceOperationH\x00\x12-\n\x08\x61gent_op\x18\x0c \x01(\x0b\x32\x19.aether.v1.AgentOperationH\x00\x12)\n\x06\x61\x63l_op\x18\r \x01(\x0b\x32\x17.aether.v1.ACLOperationH\x00\x12-\n\x08progress\x18\x0e \x01(\x0b\x32\x19.aether.v1.ProgressReportH\x00\x12\x33\n\x0bworkflow_op\x18\x0f \x01(\x0b\x32\x1c.aether.v1.WorkflowOperationH\x00\x12\x38\n\x11workflow_response\x18\x10 \x01(\x0b\x32\x1b.aether.v1.WorkflowResponseH\x00\x12-\n\x08token_op\x18\x11 \x01(\x0b\x32\x19.aether.v1.TokenOperationH\x00\x12,\n\x0b\x61udit_query\x18\x12 \x01(\x0b\x32\x15.aether.v1.AuditQueryH\x00\x12@\n\x12\x61uthority_grant_op\x18\x13 \x01(\x0b\x32\".aether.v1.AuthorityGrantOperationH\x00\x12\x39\n\x12proxy_http_request\x18\x14 \x01(\x0b\x32\x1b.aether.v1.ProxyHttpRequestH\x00\x12>\n\x15proxy_http_body_chunk\x18\x15 \x01(\x0b\x32\x1d.aether.v1.ProxyHttpBodyChunkH\x00\x12,\n\x0btunnel_open\x18\x16 \x01(\x0b\x32\x15.aether.v1.TunnelOpenH\x00\x12,\n\x0btunnel_data\x18\x17 \x01(\x0b\x32\x15.aether.v1.TunnelDataH\x00\x12.\n\x0ctunnel_close\x18\x18 \x01(\x0b\x32\x16.aether.v1.TunnelCloseH\x00\x12;\n\x13proxy_http_response\x18\x19 \x01(\x0b\x32\x1c.aether.v1.ProxyHttpResponseH\x00\x12*\n\ntunnel_ack\x18\x1a \x01(\x0b\x32\x14.aether.v1.TunnelAckH\x00\x12G\n\x19resolve_authority_request\x18\x1b \x01(\x0b\x32\".aether.v1.ResolveAuthorityRequestH\x00\x12G\n\x19\x63onnection_status_request\x18\x1c \x01(\x0b\x32\".aether.v1.ConnectionStatusRequestH\x00\x12@\n\x12submit_audit_event\x18\x1d \x01(\x0b\x32\".aether.v1.SubmitAuditEventRequestH\x00\x12\x44\n\x14\x61uthority_request_op\x18\x1e \x01(\x0b\x32$.aether.v1.AuthorityRequestOperationH\x00\x12\x44\n\x14task_subscription_op\x18\x1f \x01(\x0b\x32$.aether.v1.TaskSubscriptionOperationH\x00\x12\x37\n\x0c\x61\x63\x63\x65ss_check\x18! \x01(\x0b\x32\x1f.aether.v1.AccessCheckOperationH\x00\x12\x42\n\x12\x62\x61tch_access_check\x18\" \x01(\x0b\x32$.aether.v1.BatchAccessCheckOperationH\x00\x12\x19\n\x11\x61\x63tive_extensions\x18 \x03(\tB\t\n\x07payload\"\xc7\x11\n\x11\x44ownstreamMessage\x12)\n\x03msg\x18\x01 \x01(\x0b\x32\x1a.aether.v1.IncomingMessageH\x00\x12+\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x19.aether.v1.ConfigSnapshotH\x00\x12#\n\x06signal\x18\x03 \x01(\x0b\x32\x11.aether.v1.SignalH\x00\x12)\n\x05\x65rror\x18\x04 \x01(\x0b\x32\x18.aether.v1.ErrorResponseH\x00\x12#\n\x02kv\x18\x05 \x01(\x0b\x32\x15.aether.v1.KVResponseH\x00\x12\x34\n\x0ftask_assignment\x18\x06 \x01(\x0b\x32\x19.aether.v1.TaskAssignmentH\x00\x12\x32\n\x0e\x63onnection_ack\x18\x07 \x01(\x0b\x32\x18.aether.v1.ConnectionAckH\x00\x12\x33\n\ncheckpoint\x18\x08 \x01(\x0b\x32\x1d.aether.v1.CheckpointResponseH\x00\x12)\n\x05\x61\x64min\x18\t \x01(\x0b\x32\x18.aether.v1.AdminResponseH\x00\x12?\n\x10session_response\x18\n \x01(\x0b\x32#.aether.v1.SessionOperationResponseH\x00\x12\x32\n\ntask_query\x18\x0b \x01(\x0b\x32\x1c.aether.v1.TaskQueryResponseH\x00\x12\x33\n\x07task_op\x18\x0c \x01(\x0b\x32 .aether.v1.TaskOperationResponseH\x00\x12\x31\n\tworkspace\x18\r \x01(\x0b\x32\x1c.aether.v1.WorkspaceResponseH\x00\x12)\n\x05\x61gent\x18\x0e \x01(\x0b\x32\x18.aether.v1.AgentResponseH\x00\x12%\n\x03\x61\x63l\x18\x0f \x01(\x0b\x32\x16.aether.v1.ACLResponseH\x00\x12\x34\n\x0fprogress_update\x18\x10 \x01(\x0b\x32\x19.aether.v1.ProgressUpdateH\x00\x12\x38\n\x11workflow_response\x18\x11 \x01(\x0b\x32\x1b.aether.v1.WorkflowResponseH\x00\x12\x33\n\x0bworkflow_op\x18\x12 \x01(\x0b\x32\x1c.aether.v1.WorkflowOperationH\x00\x12)\n\x05token\x18\x13 \x01(\x0b\x32\x18.aether.v1.TokenResponseH\x00\x12\x37\n\x0e\x61udit_response\x18\x14 \x01(\x0b\x32\x1d.aether.v1.AuditQueryResponseH\x00\x12<\n\x0f\x61uthority_grant\x18\x15 \x01(\x0b\x32!.aether.v1.AuthorityGrantResponseH\x00\x12\x34\n\x0b\x63reate_task\x18\x16 \x01(\x0b\x32\x1d.aether.v1.CreateTaskResponseH\x00\x12;\n\x13proxy_http_response\x18\x17 \x01(\x0b\x32\x1c.aether.v1.ProxyHttpResponseH\x00\x12>\n\x15proxy_http_body_chunk\x18\x18 \x01(\x0b\x32\x1d.aether.v1.ProxyHttpBodyChunkH\x00\x12*\n\ntunnel_ack\x18\x19 \x01(\x0b\x32\x14.aether.v1.TunnelAckH\x00\x12.\n\x0ctunnel_close\x18\x1a \x01(\x0b\x32\x16.aether.v1.TunnelCloseH\x00\x12,\n\x0btunnel_data\x18\x1b \x01(\x0b\x32\x15.aether.v1.TunnelDataH\x00\x12\x39\n\x12proxy_http_request\x18\x1c \x01(\x0b\x32\x1b.aether.v1.ProxyHttpRequestH\x00\x12I\n\x1aresolve_authority_response\x18\x1d \x01(\x0b\x32#.aether.v1.ResolveAuthorityResponseH\x00\x12I\n\x1a\x63onnection_status_response\x18\x1e \x01(\x0b\x32#.aether.v1.ConnectionStatusResponseH\x00\x12I\n\x1a\x61uthority_grant_revocation\x18\x1f \x01(\x0b\x32#.aether.v1.AuthorityGrantRevocationH\x00\x12J\n\x1bsubmit_audit_event_response\x18 \x01(\x0b\x32#.aether.v1.SubmitAuditEventResponseH\x00\x12R\n\x1a\x61uthority_request_response\x18! \x01(\x0b\x32,.aether.v1.AuthorityRequestOperationResponseH\x00\x12\x43\n\x17\x61uthority_request_event\x18\" \x01(\x0b\x32 .aether.v1.AuthorityRequestEventH\x00\x12\x34\n\x0ftask_hibernated\x18# \x01(\x0b\x32\x19.aether.v1.TaskHibernatedH\x00\x12R\n\x1atask_subscription_response\x18$ \x01(\x0b\x32,.aether.v1.TaskSubscriptionOperationResponseH\x00\x12*\n\ntask_event\x18% \x01(\x0b\x32\x14.aether.v1.TaskEventH\x00\x12?\n\x15\x61\x63\x63\x65ss_check_response\x18\' \x01(\x0b\x32\x1e.aether.v1.AccessCheckResponseH\x00\x12J\n\x1b\x62\x61tch_access_check_response\x18( \x01(\x0b\x32#.aether.v1.BatchAccessCheckResponseH\x00\x12\x19\n\x11\x61\x63tive_extensions\x18& \x03(\tB\t\n\x07payload\"W\n\x0eTaskHibernated\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x34\n\ndescriptor\x18\x02 \x01(\x0b\x32 .aether.v1.HibernationDescriptor\"\xb6\x02\n\rConnectionAck\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0f\n\x07resumed\x18\x02 \x01(\x08\x12\x13\n\x0b\x61ssigned_id\x18\x03 \x01(\t\x12=\n\x15negotiated_extensions\x18\x04 \x03(\x0b\x32\x1e.aether.v1.NegotiatedExtension\x12#\n\x1bserver_supported_extensions\x18\x05 \x03(\t\x12\x16\n\x0eserver_version\x18\x32 \x01(\t\x12/\n\x11server_build_info\x18\x33 \x01(\x0b\x32\x14.aether.v1.BuildInfo\x12\"\n\x1ainitial_connection_unix_ms\x18< \x01(\x03\x12\x1a\n\x12reconnection_count\x18= \x01(\x05\"\xcd\x05\n\x0eInitConnection\x12)\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x18.aether.v1.AgentIdentityH\x00\x12\'\n\x04task\x18\x02 \x01(\x0b\x32\x17.aether.v1.TaskIdentityH\x00\x12\'\n\x04user\x18\x03 \x01(\x0b\x32\x17.aether.v1.UserIdentityH\x00\x12\x37\n\x0corchestrator\x18\x04 \x01(\x0b\x32\x1f.aether.v1.OrchestratorIdentityH\x00\x12<\n\x0fworkflow_engine\x18\x05 \x01(\x0b\x32!.aether.v1.WorkflowEngineIdentityH\x00\x12:\n\x0emetrics_bridge\x18\x06 \x01(\x0b\x32 .aether.v1.MetricsBridgeIdentityH\x00\x12+\n\x06\x62ridge\x18\x07 \x01(\x0b\x32\x19.aether.v1.BridgeIdentityH\x00\x12-\n\x07service\x18\x08 \x01(\x0b\x32\x1a.aether.v1.ServiceIdentityH\x00\x12?\n\x0b\x63redentials\x18\n \x03(\x0b\x32*.aether.v1.InitConnection.CredentialsEntry\x12\x19\n\x11resume_session_id\x18\x0b \x01(\t\x12\x33\n\nextensions\x18\x0c \x03(\x0b\x32\x1f.aether.v1.ExtensionDeclaration\x12\x16\n\x0e\x63lient_version\x18\x32 \x01(\t\x12\x12\n\nclient_sdk\x18\x33 \x01(\t\x12/\n\x11\x63lient_build_info\x18\x34 \x01(\x0b\x32\x14.aether.v1.BuildInfo\x1a\x32\n\x10\x43redentialsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b\x63lient_type\"J\n\tBuildInfo\x12\x0e\n\x06\x63ommit\x18\x01 \x01(\t\x12\x10\n\x08\x62uilt_at\x18\x02 \x01(\t\x12\x0f\n\x07runtime\x18\x03 \x01(\t\x12\n\n\x02os\x18\x04 \x01(\t\"[\n\x14\x45xtensionDeclaration\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x10\n\x08required\x18\x03 \x01(\x08\x12\x13\n\x0bjson_schema\x18\x04 \x01(\t\"`\n\x13NegotiatedExtension\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x11\n\tsupported\x18\x03 \x01(\x08\x12\x18\n\x10rejection_reason\x18\x04 \x01(\t\"-\n\x16WorkflowEngineIdentity\x12\x13\n\x0binstance_id\x18\x01 \x01(\t\",\n\x15MetricsBridgeIdentity\x12\x13\n\x0binstance_id\x18\x01 \x01(\t\"]\n\x14OrchestratorIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\x12\x1a\n\x12supported_profiles\x18\x03 \x03(\t\";\n\x0e\x42ridgeIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\"V\n\x0fServiceIdentity\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x11\n\tspecifier\x18\x02 \x01(\t\x12\x18\n\x10no_pool_consumer\x18\x03 \x01(\x08\"M\n\rAgentIdentity\x12\x11\n\tworkspace\x18\x01 \x01(\t\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12\x11\n\tspecifier\x18\x03 \x01(\t\"S\n\x0cTaskIdentity\x12\x11\n\tworkspace\x18\x01 \x01(\t\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12\x18\n\x10unique_specifier\x18\x03 \x01(\t\"2\n\x0cUserIdentity\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x11\n\twindow_id\x18\x02 \x01(\t\"<\n\x0cPrincipalRef\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\"\x9e\x01\n\x14\x41uthorizationContext\x12\x16\n\x0e\x61uthority_mode\x18\x01 \x01(\t\x12(\n\x07subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x10\n\x08grant_id\x18\x03 \x01(\t\x12\x32\n\x08resolved\x18\n \x01(\x0b\x32 .aether.v1.ResolvedAuthorityInfo\"\xbc\x01\n\x15ResolvedAuthorityInfo\x12-\n\x0croot_subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x03 \x01(\t\x12\x18\n\x10max_access_level\x18\x04 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\x05 \x03(\t\x12\x15\n\rexpires_at_ms\x18\x06 \x01(\x03\"\xb4\x02\n\x0bSendMessage\x12\x14\n\x0ctarget_topic\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x36\n\rauthorization\x18\x04 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rapp_workspace\x18\x05 \x01(\t\x12\x38\n\x0e\x63hecked_access\x18\x06 \x01(\x0b\x32 .aether.v1.ResourceAccessRequest\x12G\n\x16\x61uthority_continuation\x18\x07 \x01(\x0b\x32\'.aether.v1.AuthorityContinuationRequest\"\xb0\x01\n\x1a\x41uthorityContinuationScope\x12\x17\n\x0fworkspace_scope\x18\x01 \x03(\t\x12\x46\n\x0eresource_scope\x18\x02 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x03 \x03(\t\x12\x18\n\x10max_access_level\x18\x04 \x01(\x05\"\x91\x02\n\x1c\x41uthorityContinuationRequest\x12\x45\n\nscope_mode\x18\x01 \x01(\x0e\x32\x31.aether.v1.AuthorityContinuationRequest.ScopeMode\x12\x12\n\nbinding_id\x18\x02 \x01(\t\x12\x34\n\x05scope\x18\x03 \x01(\x0b\x32%.aether.v1.AuthorityContinuationScope\"`\n\tScopeMode\x12\x1a\n\x16SCOPE_MODE_UNSPECIFIED\x10\x00\x12\x1d\n\x19SCOPE_MODE_INHERIT_PARENT\x10\x01\x12\x18\n\x14SCOPE_MODE_ATTENUATE\x10\x02\"\xc4\x01\n\x06Metric\x12\x10\n\x08trace_id\x18\x01 \x01(\t\x12\'\n\x07\x65ntries\x18\x02 \x03(\x0b\x32\x16.aether.v1.MetricEntry\x12\x31\n\x08metadata\x18\x03 \x03(\x0b\x32\x1f.aether.v1.Metric.MetadataEntry\x12\x1b\n\x13\x63lient_timestamp_ms\x18\x04 \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"6\n\x0bMetricEntry\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04kind\x18\x02 \x01(\t\x12\x0b\n\x03qty\x18\x03 \x01(\x01\"+\n\x0fSwitchWorkspace\x12\x18\n\x10new_workspace_id\x18\x01 \x01(\t\"\x8a\x06\n\x0bKVOperation\x12)\n\x02op\x18\x01 \x01(\x0e\x32\x1d.aether.v1.KVOperation.OpType\x12+\n\x05scope\x18\x02 \x01(\x0e\x32\x1c.aether.v1.KVOperation.Scope\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\r\n\x05value\x18\x04 \x01(\x0c\x12\x0f\n\x07user_id\x18\x05 \x01(\t\x12\x11\n\tworkspace\x18\x06 \x01(\t\x12\x0b\n\x03ttl\x18\x07 \x01(\x03\x12\x12\n\nrequest_id\x18\x08 \x01(\t\x12\x36\n\rauthorization\x18\t \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x13\n\x0bguard_value\x18\n \x01(\x03\x12\x13\n\x0b\x64\x65lta_value\x18\x0b \x01(\x03\x12\x16\n\x0e\x65xpected_value\x18\x0c \x01(\x0c\x12\r\n\x05limit\x18\r \x01(\x05\x12\x0e\n\x06\x63ursor\x18\x0e \x01(\t\x12\x17\n\x0ftarget_identity\x18\x0f \x01(\t\"\xda\x01\n\x06OpType\x12\x07\n\x03GET\x10\x00\x12\x07\n\x03PUT\x10\x01\x12\x08\n\x04LIST\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03\x12\r\n\tINCREMENT\x10\x04\x12\r\n\tDECREMENT\x10\x05\x12\x10\n\x0cINCREMENT_IF\x10\x06\x12\x10\n\x0c\x44\x45\x43REMENT_IF\x10\x07\x12\n\n\x06SET_NX\x10\x08\x12\x13\n\x0f\x43OMPARE_AND_SET\x10\t\x12\x16\n\x12\x43OMPARE_AND_DELETE\x10\n\x12\x12\n\x0ePURGE_IDENTITY\x10\x0b\x12\x0b\n\x07SET_ADD\x10\x0c\x12\x0c\n\x08SET_CARD\x10\r\"\xb2\x01\n\x05Scope\x12\x15\n\x11SCOPE_UNSPECIFIED\x10\x00\x12\n\n\x06GLOBAL\x10\x01\x12\r\n\tWORKSPACE\x10\x02\x12\x08\n\x04USER\x10\x03\x12\x12\n\x0eUSER_WORKSPACE\x10\x04\x12\x14\n\x10GLOBAL_EXCLUSIVE\x10\x05\x12\x17\n\x13WORKSPACE_EXCLUSIVE\x10\x06\x12\x0f\n\x0bUSER_SHARED\x10\x07\x12\x19\n\x15USER_WORKSPACE_SHARED\x10\x08\"\xfd\x01\n\nKVResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05value\x18\x02 \x01(\x0c\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12\x30\n\x06kv_map\x18\x04 \x03(\x0b\x32 .aether.v1.KVResponse.KvMapEntry\x12\x12\n\nrequest_id\x18\x05 \x01(\t\x12\x15\n\rcounter_value\x18\x06 \x01(\x03\x12\x0f\n\x07\x61pplied\x18\x07 \x01(\x08\x12\x13\n\x0bnext_cursor\x18\x08 \x01(\t\x12\x10\n\x08has_more\x18\t \x01(\x08\x1a,\n\nKvMapEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\xab\x02\n\x0fIncomingMessage\x12\x14\n\x0csource_topic\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x32\n\x11on_behalf_subject\x18\x05 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x38\n\x0e\x61\x63\x63\x65ss_receipt\x18\x06 \x01(\x0b\x32 .aether.v1.AccessDecisionReceipt\x12\x42\n\x17\x66orwarded_authorization\x18\x07 \x01(\x0b\x32!.aether.v1.ForwardedAuthorization\"\xe1\x01\n\x16\x46orwardedAuthorization\x12\x36\n\rauthorization\x18\x01 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rroot_grant_id\x18\x02 \x01(\t\x12\x15\n\rexpires_at_ms\x18\x03 \x01(\x03\x12\x17\n\x0f\x64\x65livery_target\x18\x04 \x01(\t\x12\x12\n\nbinding_id\x18\x05 \x01(\t\x12\x34\n\x05scope\x18\x06 \x01(\x0b\x32%.aether.v1.AuthorityContinuationScope\"\xf0\x04\n\x0e\x43onfigSnapshot\x12\x31\n\x02kv\x18\x01 \x03(\x0b\x32!.aether.v1.ConfigSnapshot.KvEntryB\x02\x18\x01\x12>\n\tglobal_kv\x18\x02 \x03(\x0b\x32\'.aether.v1.ConfigSnapshot.GlobalKvEntryB\x02\x18\x01\x12@\n\x0ctask_context\x18\x03 \x03(\x0b\x32*.aether.v1.ConfigSnapshot.TaskContextEntry\x12S\n\x16workspace_exclusive_kv\x18\x04 \x03(\x0b\x32\x33.aether.v1.ConfigSnapshot.WorkspaceExclusiveKvEntry\x12M\n\x13global_exclusive_kv\x18\x05 \x03(\x0b\x32\x30.aether.v1.ConfigSnapshot.GlobalExclusiveKvEntry\x1a)\n\x07KvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a/\n\rGlobalKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x32\n\x10TaskContextEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a;\n\x19WorkspaceExclusiveKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\x1a\x38\n\x16GlobalExclusiveKvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\x81\x01\n\x06Signal\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x1c.aether.v1.Signal.SignalType\x12\x0e\n\x06reason\x18\x02 \x01(\t\";\n\nSignalType\x12\x14\n\x10\x46ORCE_DISCONNECT\x10\x00\x12\x17\n\x13GRACEFUL_DISCONNECT\x10\x01\"m\n\rErrorResponse\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\x11\n\tretryable\x18\x03 \x01(\x08\x12\x16\n\x0eretry_after_ms\x18\x04 \x01(\x03\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"\xe7\x01\n\x0bRetryPolicy\x12\x14\n\x0cmax_attempts\x18\x01 \x01(\x05\x12+\n\x07\x62\x61\x63koff\x18\x02 \x01(\x0e\x32\x1a.aether.v1.BackoffStrategy\x12\x18\n\x10initial_delay_ms\x18\x03 \x01(\x03\x12\x14\n\x0cmax_delay_ms\x18\x04 \x01(\x03\x12\x15\n\rjitter_factor\x18\x05 \x01(\x01\x12\x13\n\x0bschedule_ms\x18\x06 \x03(\x03\x12\x1e\n\x16retryable_status_codes\x18\x07 \x03(\x05\x12\x19\n\x11honor_retry_after\x18\x08 \x01(\x08\"f\n\x13TaskCompletionEvent\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x12\n\nevent_name\x18\x02 \x01(\t\x12*\n\x0bon_statuses\x18\x03 \x03(\x0e\x32\x15.aether.v1.TaskStatus\"\xdf\x07\n\x11\x43reateTaskRequest\x12\x11\n\ttask_type\x18\x01 \x01(\t\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\x36\n\x0f\x61ssignment_mode\x18\x03 \x01(\x0e\x32\x1d.aether.v1.TaskAssignmentMode\x12\x17\n\x0ftarget_agent_id\x18\x04 \x01(\t\x12V\n\x16launch_param_overrides\x18\x05 \x03(\x0b\x32\x36.aether.v1.CreateTaskRequest.LaunchParamOverridesEntry\x12<\n\x08metadata\x18\x06 \x03(\x0b\x32*.aether.v1.CreateTaskRequest.MetadataEntry\x12\x0f\n\x07payload\x18\x07 \x01(\x0c\x12\x1d\n\x15target_implementation\x18\x08 \x01(\t\x12\x36\n\rauthorization\x18\t \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x12\n\nrequest_id\x18\n \x01(\t\x12\x17\n\x0ftarget_identity\x18\x0b \x01(\t\x12(\n\ntask_class\x18\x0c \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x12\n\ncontext_id\x18\r \x01(\t\x12,\n\x0cretry_policy\x18\x0e \x01(\x0b\x32\x16.aether.v1.RetryPolicy\x12)\n\x08priority\x18\x0f \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x17\n\x0fidempotency_key\x18\x10 \x01(\t\x12\x16\n\x0e\x63orrelation_id\x18\x11 \x01(\t\x12\x14\n\x0croot_task_id\x18\x12 \x01(\t\x12\x38\n\x10\x63ompletion_event\x18\x13 \x01(\x0b\x32\x1e.aether.v1.TaskCompletionEvent\x12\x16\n\x0eparent_task_id\x18\x14 \x01(\t\x12=\n\x15target_offline_policy\x18\x15 \x01(\x0e\x32\x1e.aether.v1.TargetOfflinePolicy\x12*\n\"required_downstream_authority_hops\x18\x16 \x01(\r\x12\x1f\n\x17originating_schedule_id\x18\x17 \x01(\t\x1a;\n\x19LaunchParamOverridesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xca\x01\n\x12\x43reateTaskResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\t\x12\x12\n\nerror_code\x18\x04 \x01(\t\x12\x15\n\rerror_message\x18\x05 \x01(\t\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x07 \x01(\t\x12\x12\n\ntask_token\x18\x08 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\t \x01(\t\"\xbf\x04\n\x0eTaskAssignment\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x03 \x01(\t\x12\x39\n\x08metadata\x18\x04 \x03(\x0b\x32\'.aether.v1.TaskAssignment.MetadataEntry\x12\x13\n\x0b\x61ssigned_at\x18\x05 \x01(\x03\x12\x0f\n\x07profile\x18\x06 \x01(\t\x12\x42\n\rlaunch_params\x18\x07 \x03(\x0b\x32+.aether.v1.TaskAssignment.LaunchParamsEntry\x12\x1d\n\x15target_implementation\x18\x08 \x01(\t\x12\x11\n\tworkspace\x18\t \x01(\t\x12\x11\n\tspecifier\x18\n \x01(\t\x12\x0f\n\x07payload\x18\x0b \x01(\x0c\x12(\n\ntask_class\x18\x0c \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x16\n\x0e\x63heckpoint_key\x18\r \x01(\t\x12\x19\n\x11resume_session_id\x18\x0e \x01(\t\x12\x36\n\rauthorization\x18\x0f \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11LaunchParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb8\x01\n\x13\x43heckpointOperation\x12\x31\n\x02op\x18\x01 \x01(\x0e\x32%.aether.v1.CheckpointOperation.OpType\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x0b\n\x03ttl\x18\x04 \x01(\x03\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"2\n\x06OpType\x12\x08\n\x04SAVE\x10\x00\x12\x08\n\x04LOAD\x10\x01\x12\n\n\x06\x44\x45LETE\x10\x02\x12\x08\n\x04LIST\x10\x03\"v\n\x12\x43heckpointResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04keys\x18\x03 \x03(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x10\n\x08saved_at\x18\x05 \x01(\x03\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"\xec\x01\n\nAdminQuery\x12(\n\x02op\x18\x01 \x01(\x0e\x32\x1c.aether.v1.AdminQuery.OpType\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12+\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x1b.aether.v1.ConnectionFilter\x12\x12\n\nrequest_id\x18\x04 \x01(\t\"_\n\x06OpType\x12\x0e\n\nGET_HEALTH\x10\x00\x12\x0c\n\x08GET_INFO\x10\x01\x12\r\n\tGET_STATS\x10\x02\x12\x14\n\x10LIST_CONNECTIONS\x10\x03\x12\x12\n\x0eGET_CONNECTION\x10\x04\"l\n\x10\x43onnectionFilter\x12&\n\x04type\x18\x01 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\"\xf0\x01\n\x0e\x43onnectionInfo\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12&\n\x04type\x18\x02 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x10\n\x08identity\x18\x03 \x01(\t\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x16\n\x0eimplementation\x18\x05 \x01(\t\x12\x11\n\tspecifier\x18\x06 \x01(\t\x12\x14\n\x0c\x63onnected_at\x18\x07 \x01(\x03\x12\x10\n\x08\x64uration\x18\x08 \x01(\t\x12\x13\n\x0bremote_addr\x18\t \x01(\t\x12\x15\n\rlast_activity\x18\n \x01(\x03\"\xac\x02\n\rAdminResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12%\n\x06health\x18\x03 \x01(\x0b\x32\x15.aether.v1.HealthInfo\x12$\n\x04info\x18\x04 \x01(\x0b\x32\x16.aether.v1.GatewayInfo\x12&\n\x05stats\x18\x05 \x01(\x0b\x32\x17.aether.v1.GatewayStats\x12-\n\nconnection\x18\x06 \x01(\x0b\x32\x19.aether.v1.ConnectionInfo\x12.\n\x0b\x63onnections\x18\x07 \x03(\x0b\x32\x19.aether.v1.ConnectionInfo\x12\x13\n\x0btotal_count\x18\x08 \x01(\x05\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xea\x01\n\nHealthInfo\x12\'\n\x06status\x18\x01 \x01(\x0e\x32\x17.aether.v1.HealthStatus\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x31\n\x06\x63hecks\x18\x03 \x03(\x0b\x32!.aether.v1.HealthInfo.ChecksEntry\x12&\n\x05stats\x18\x04 \x01(\x0b\x32\x17.aether.v1.GatewayStats\x1a\x45\n\x0b\x43hecksEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12%\n\x05value\x18\x02 \x01(\x0b\x32\x16.aether.v1.HealthCheck:\x02\x38\x01\"[\n\x0bHealthCheck\x12,\n\x06status\x18\x01 \x01(\x0e\x32\x1c.aether.v1.HealthCheckStatus\x12\x0f\n\x07latency\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"\xb4\x01\n\x0bGatewayInfo\x12\x12\n\ngateway_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x12\n\nstarted_at\x18\x03 \x01(\x03\x12\x0e\n\x06uptime\x18\x04 \x01(\t\x12\x12\n\ngo_version\x18\x05 \x01(\t\x12\x16\n\x0enum_goroutines\x18\x06 \x01(\x05\x12\x17\n\x0fmemory_alloc_mb\x18\x07 \x01(\x01\x12\x17\n\x0fnum_connections\x18\x08 \x01(\x05\"\x9a\x03\n\x0cGatewayStats\x12\x19\n\x11\x61gent_connections\x18\x01 \x01(\x05\x12\x18\n\x10task_connections\x18\x02 \x01(\x05\x12\x18\n\x10user_connections\x18\x03 \x01(\x05\x12 \n\x18orchestrator_connections\x18\x04 \x01(\x05\x12!\n\x19workflow_engine_connected\x18\x05 \x01(\x08\x12 \n\x18metrics_bridge_connected\x18\x06 \x01(\x08\x12\x13\n\x0btotal_tasks\x18\x07 \x01(\x05\x12\x15\n\rpending_tasks\x18\x08 \x01(\x05\x12\x15\n\rrunning_tasks\x18\t \x01(\x05\x12\x17\n\x0f\x63ompleted_tasks\x18\n \x01(\x05\x12\x14\n\x0c\x66\x61iled_tasks\x18\x0b \x01(\x05\x12\x1b\n\x13messages_per_second\x18\x0c \x01(\x01\x12\x16\n\x0etotal_messages\x18\r \x01(\x03\x12\x15\n\ractive_timers\x18\x0e \x01(\x05\x12\x16\n\x0epending_timers\x18\x0f \x01(\x05\"\x8c\x02\n\x10SessionOperation\x12.\n\x02op\x18\x01 \x01(\x0e\x32\".aether.v1.SessionOperation.OpType\x12\x12\n\nsession_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12+\n\x06\x66ilter\x18\x05 \x01(\x0b\x32\x1b.aether.v1.ConnectionFilter\x12\x36\n\rauthorization\x18\x06 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"+\n\x06OpType\x12\x0e\n\nDISCONNECT\x10\x00\x12\x08\n\x04LIST\x10\x01\x12\x07\n\x03GET\x10\x02\"\xd3\x01\n\x18SessionOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12-\n\nconnection\x18\x05 \x01(\x0b\x32\x19.aether.v1.ConnectionInfo\x12.\n\x0b\x63onnections\x18\x06 \x03(\x0b\x32\x19.aether.v1.ConnectionInfo\x12\x13\n\x0btotal_count\x18\x07 \x01(\x05\"\x9d\x01\n\tTaskQuery\x12\'\n\x02op\x18\x01 \x01(\x0e\x32\x1b.aether.v1.TaskQuery.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12%\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x15.aether.v1.TaskFilter\x12\x12\n\nrequest_id\x18\x04 \x01(\t\"\x1b\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\"\xec\x05\n\nTaskFilter\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\x11\n\ttask_type\x18\x03 \x01(\t\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\x12\'\n\x08statuses\x18\x06 \x03(\x0e\x32\x15.aether.v1.TaskStatus\x12\x14\n\x0csubject_type\x18\x07 \x01(\t\x12\x12\n\nsubject_id\x18\x08 \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\t \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\n \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x0b \x01(\t\x12\x16\n\x0eparent_task_id\x18\x0c \x01(\t\x12(\n\ntask_class\x18\r \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x32\n\x14\x65xclude_task_classes\x18\x0e \x03(\x0e\x32\x14.aether.v1.TaskClass\x12\x12\n\ncontext_id\x18\x0f \x01(\t\x12/\n\x10\x65xclude_statuses\x18\x10 \x03(\x0e\x32\x15.aether.v1.TaskStatus\x12.\n\rcreator_actor\x18\x11 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12&\n\x1estatus_timestamp_after_unix_ms\x18\x12 \x01(\x03\x12\x12\n\npage_token\x18\x13 \x01(\t\x12\x1b\n\x13include_descendants\x18\x14 \x01(\x08\x12)\n\x08priority\x18\x15 \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12-\n\x0cmin_priority\x18\x16 \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x16\n\x0e\x63orrelation_id\x18\x17 \x01(\t\x12\x14\n\x0croot_task_id\x18\x18 \x01(\t\"\xc7\x07\n\x08TaskInfo\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12%\n\x06status\x18\x03 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x05 \x01(\t\x12\x13\n\x0b\x61ssigned_to\x18\x06 \x01(\t\x12\x12\n\ncreated_at\x18\x07 \x01(\x03\x12\x12\n\nstarted_at\x18\x08 \x01(\x03\x12\x14\n\x0c\x63ompleted_at\x18\t \x01(\x03\x12\x0f\n\x07\x61ttempt\x18\n \x01(\x05\x12\x14\n\x0cmax_attempts\x18\x0b \x01(\x05\x12\r\n\x05\x65rror\x18\x0c \x01(\t\x12\x33\n\x08metadata\x18\r \x03(\x0b\x32!.aether.v1.TaskInfo.MetadataEntry\x12\x16\n\x0e\x61uthority_mode\x18\x0e \x01(\t\x12\x14\n\x0csubject_type\x18\x0f \x01(\t\x12\x12\n\nsubject_id\x18\x10 \x01(\t\x12\x19\n\x11root_subject_type\x18\x11 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x12 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x13 \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x14 \x01(\t\x12!\n\x19parent_authority_grant_id\x18\x15 \x01(\t\x12\x18\n\x10\x63reator_actor_id\x18\x16 \x01(\t\x12\x16\n\x0eparent_task_id\x18\x17 \x01(\t\x12(\n\ntask_class\x18\x18 \x01(\x0e\x32\x14.aether.v1.TaskClass\x12\x17\n\x0f\x64isconnected_at\x18\x19 \x01(\x03\x12\x17\n\x0fgrace_window_ms\x18\x1a \x01(\x03\x12&\n\twait_spec\x18\x1b \x01(\x0b\x32\x13.aether.v1.WaitSpec\x12\x12\n\ndepends_on\x18\x1c \x03(\t\x12\x12\n\ncontext_id\x18\x1d \x01(\t\x12\x11\n\tpaused_at\x18\x1e \x01(\x03\x12)\n\x08priority\x18\x1f \x01(\x0e\x32\x17.aether.v1.TaskPriority\x12\x16\n\x0e\x63orrelation_id\x18 \x01(\t\x12\x14\n\x0croot_task_id\x18! \x01(\t\x12\x38\n\x10\x63ompletion_event\x18\" \x01(\x0b\x32\x1e.aether.v1.TaskCompletionEvent\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\x11TaskQueryResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12!\n\x04task\x18\x03 \x01(\x0b\x32\x13.aether.v1.TaskInfo\x12\"\n\x05tasks\x18\x04 \x03(\x0b\x32\x13.aether.v1.TaskInfo\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x17\n\x0fnext_page_token\x18\x07 \x01(\t\"\x8e\x02\n\rTaskOperation\x12+\n\x02op\x18\x01 \x01(\x0e\x32\x1f.aether.v1.TaskOperation.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrequest_id\x18\x04 \x01(\t\x12&\n\twait_spec\x18\x05 \x01(\x0b\x32\x13.aether.v1.WaitSpec\"s\n\x06OpType\x12\t\n\x05RETRY\x10\x00\x12\n\n\x06\x43\x41NCEL\x10\x01\x12\x0c\n\x08\x43OMPLETE\x10\x02\x12\x08\n\x04\x46\x41IL\x10\x03\x12\t\n\x05PAUSE\x10\x04\x12\x0c\n\x08WAIT_FOR\x10\x05\x12\n\n\x06RESUME\x10\x06\x12\n\n\x06REJECT\x10\x07\x12\t\n\x05\x43LAIM\x10\x08\"\xec\x02\n\x08WaitSpec\x12%\n\x06reason\x18\x01 \x01(\x0e\x32\x15.aether.v1.WaitReason\x12\x1a\n\x12\x65xpected_principal\x18\x02 \x01(\t\x12\x38\n\x0binput_match\x18\x03 \x03(\x0b\x32#.aether.v1.WaitSpec.InputMatchEntry\x12\x1c\n\x14\x61uthority_request_id\x18\x04 \x01(\t\x12\x12\n\ndepends_on\x18\x05 \x03(\t\x12\x13\n\x0bwake_on_any\x18\x06 \x01(\x08\x12\x12\n\ntimeout_ms\x18\x07 \x01(\x03\x12\x1e\n\x16scheduled_wake_unix_ms\x18\x08 \x01(\x03\x12\x35\n\x0bhibernation\x18\t \x01(\x0b\x32 .aether.v1.HibernationDescriptor\x1a\x31\n\x0fInputMatchEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x7f\n\x15HibernationDescriptor\x12\x16\n\x0e\x63heckpoint_key\x18\x01 \x01(\t\x12\x19\n\x11resume_session_id\x18\x02 \x01(\t\x12\x18\n\x10wake_event_types\x18\x03 \x03(\t\x12\x19\n\x11\x65scalation_policy\x18\x04 \x01(\t\"\x7f\n\x15TaskOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12!\n\x04task\x18\x04 \x01(\x0b\x32\x13.aether.v1.TaskInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"\xa0\x02\n\x12WorkspaceOperation\x12\x30\n\x02op\x18\x01 \x01(\x0e\x32$.aether.v1.WorkspaceOperation.OpType\x12\x14\n\x0cworkspace_id\x18\x02 \x01(\t\x12*\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x1a.aether.v1.WorkspaceFilter\x12+\n\tworkspace\x18\x04 \x01(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"U\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\n\n\x06\x43REATE\x10\x02\x12\n\n\x06UPDATE\x10\x03\x12\n\n\x06\x44\x45LETE\x10\x04\x12\x14\n\x10GET_MESSAGE_FLOW\x10\x05\"C\n\x0fWorkspaceFilter\x12\x11\n\ttenant_id\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\xd1\x02\n\rWorkspaceInfo\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x11\n\ttenant_id\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\x12\x38\n\x08metadata\x18\x07 \x03(\x0b\x32&.aether.v1.WorkspaceInfo.MetadataEntry\x12\x15\n\ractive_agents\x18\x08 \x01(\x05\x12\x14\n\x0c\x61\x63tive_tasks\x18\t \x01(\x05\x12\x14\n\x0c\x61\x63tive_users\x18\n \x01(\x05\x12\x16\n\x0etotal_messages\x18\x0b \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xfa\x01\n\x11WorkspaceResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12+\n\tworkspace\x18\x04 \x01(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12,\n\nworkspaces\x18\x05 \x03(\x0b\x32\x18.aether.v1.WorkspaceInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x30\n\x0cmessage_flow\x18\x07 \x01(\x0b\x32\x1a.aether.v1.MessageFlowInfo\x12\x12\n\nrequest_id\x18\x08 \x01(\t\"\x83\x01\n\x0fMessageFlowInfo\x12\x14\n\x0cworkspace_id\x18\x01 \x01(\t\x12\"\n\x05nodes\x18\x02 \x03(\x0b\x32\x13.aether.v1.FlowNode\x12\"\n\x05\x65\x64ges\x18\x03 \x03(\x0b\x32\x13.aether.v1.FlowEdge\x12\x12\n\nupdated_at\x18\x04 \x01(\x03\"\x97\x01\n\x08\x46lowNode\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12&\n\x04type\x18\x03 \x01(\x0e\x32\x18.aether.v1.PrincipalType\x12\x0e\n\x06status\x18\x04 \x01(\t\x12\x16\n\x0eimplementation\x18\x05 \x01(\t\x12\x11\n\tspecifier\x18\x06 \x01(\t\x12\r\n\x05topic\x18\x07 \x01(\t\"B\n\x08\x46lowEdge\x12\x0c\n\x04\x66rom\x18\x01 \x01(\t\x12\n\n\x02to\x18\x02 \x01(\t\x12\r\n\x05label\x18\x03 \x01(\t\x12\r\n\x05\x63ount\x18\x04 \x01(\x03\"\xdf\x02\n\x0e\x41gentOperation\x12,\n\x02op\x18\x01 \x01(\x0e\x32 .aether.v1.AgentOperation.OpType\x12\x16\n\x0eimplementation\x18\x02 \x01(\t\x12&\n\x06\x66ilter\x18\x03 \x01(\x0b\x32\x16.aether.v1.AgentFilter\x12/\n\x05\x61gent\x18\x04 \x01(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x33\n\rlaunch_params\x18\x05 \x01(\x0b\x32\x1c.aether.v1.AgentLaunchParams\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"e\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\x0c\n\x08REGISTER\x10\x02\x12\n\n\x06UPDATE\x10\x03\x12\n\n\x06\x44\x45LETE\x10\x04\x12\n\n\x06LAUNCH\x10\x05\x12\x16\n\x12LIST_ORCHESTRATORS\x10\x06\"J\n\x0b\x41gentFilter\x12\x1c\n\x14orchestrator_profile\x18\x01 \x01(\t\x12\r\n\x05limit\x18\x02 \x01(\x05\x12\x0e\n\x06offset\x18\x03 \x01(\x05\"\xde\x03\n\x15\x41gentRegistrationInfo\x12\x16\n\x0eimplementation\x18\x01 \x01(\t\x12\x1c\n\x14orchestrator_profile\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12I\n\rlaunch_params\x18\x04 \x03(\x0b\x32\x32.aether.v1.AgentRegistrationInfo.LaunchParamsEntry\x12\x15\n\rregistered_at\x18\x05 \x01(\x03\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\x12<\n\x0fresource_schema\x18\x07 \x03(\x0b\x32#.aether.v1.AgentResourceSchemaEntry\x12H\n\x0c\x63\x61pabilities\x18\x08 \x03(\x0b\x32\x32.aether.v1.AgentRegistrationInfo.CapabilitiesEntry\x12\x12\n\nextensions\x18\t \x03(\t\x1a\x33\n\x11LaunchParamsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a\x33\n\x11\x43\x61pabilitiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x08:\x02\x38\x01\"n\n\x18\x41gentResourceSchemaEntry\x12\x1c\n\x14resource_type_prefix\x18\x01 \x01(\t\x12\x18\n\x10permission_verbs\x18\x02 \x03(\t\x12\x1a\n\x12resource_id_schema\x18\x03 \x01(\t\"\xbb\x01\n\x11\x41gentLaunchParams\x12\x11\n\tspecifier\x18\x01 \x01(\t\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12I\n\x0fparam_overrides\x18\x03 \x03(\x0b\x32\x30.aether.v1.AgentLaunchParams.ParamOverridesEntry\x1a\x35\n\x13ParamOverridesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"S\n\x10OrchestratorInfo\x12\x17\n\x0forchestrator_id\x18\x01 \x01(\t\x12\x10\n\x08profiles\x18\x02 \x03(\t\x12\x14\n\x0c\x63onnected_at\x18\x03 \x01(\x03\"5\n\x11\x41gentLaunchResult\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xb5\x02\n\rAgentResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12/\n\x05\x61gent\x18\x04 \x01(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x30\n\x06\x61gents\x18\x05 \x03(\x0b\x32 .aether.v1.AgentRegistrationInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x32\n\rorchestrators\x18\x07 \x03(\x0b\x32\x1b.aether.v1.OrchestratorInfo\x12\x33\n\rlaunch_result\x18\x08 \x01(\x0b\x32\x1c.aether.v1.AgentLaunchResult\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xac\x0c\n\x0c\x41\x43LOperation\x12*\n\x02op\x18\x01 \x01(\x0e\x32\x1e.aether.v1.ACLOperation.OpType\x12\x0f\n\x07rule_id\x18\x02 \x01(\t\x12\x15\n\rrule_category\x18\x04 \x01(\t\x12\x16\n\x0eretention_days\x18\x05 \x01(\x05\x12-\n\x0brule_filter\x18\n \x01(\x0b\x32\x18.aether.v1.ACLRuleFilter\x12/\n\x0c\x61udit_filter\x18\x0b \x01(\x0b\x32\x19.aether.v1.ACLAuditFilter\x12\x31\n\rgrant_request\x18\x0c \x01(\x0b\x32\x1a.aether.v1.ACLGrantRequest\x12:\n\x10\x66\x61llback_request\x18\x0e \x01(\x0b\x32 .aether.v1.ACLSetFallbackRequest\x12\x12\n\nrequest_id\x18\x13 \x01(\t\x12\x0c\n\x04name\x18\x14 \x01(\t\x12*\n\tprincipal\x18\x15 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x31\n\rgroup_request\x18\x16 \x01(\x0b\x32\x1a.aether.v1.ACLGroupRequest\x12/\n\x0crole_request\x18\x17 \x01(\x0b\x32\x19.aether.v1.ACLRoleRequest\x12\x38\n\x0emember_request\x18\x18 \x01(\x0b\x32 .aether.v1.ACLGroupMemberRequest\x12?\n\x12\x61ssignment_request\x18\x19 \x01(\x0b\x32#.aether.v1.ACLRoleAssignmentRequest\x12\x15\n\rresource_type\x18\x1a \x01(\t\x12\x13\n\x0bresource_id\x18\x1b \x01(\t\x12\x16\n\x0erequired_level\x18\x1c \x01(\x05\x12\x36\n\rauthorization\x18\x1d \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"\xa3\x05\n\x06OpType\x12\x0e\n\nLIST_RULES\x10\x00\x12\x0c\n\x08GET_RULE\x10\x01\x12\t\n\x05GRANT\x10\x02\x12\n\n\x06REVOKE\x10\x03\x12\x0f\n\x0bQUERY_AUDIT\x10\x04\x12\x17\n\x13GET_FALLBACK_POLICY\x10\x08\x12\x17\n\x13SET_FALLBACK_POLICY\x10\t\x12\x13\n\x0f\x43LEANUP_EXPIRED\x10\n\x12\x16\n\x12\x43LEANUP_AUDIT_LOGS\x10\x0b\x12\x10\n\x0c\x43REATE_GROUP\x10\x11\x12\x10\n\x0c\x44\x45LETE_GROUP\x10\x12\x12\r\n\tGET_GROUP\x10\x13\x12\x0f\n\x0bLIST_GROUPS\x10\x14\x12\x14\n\x10\x41\x44\x44_GROUP_MEMBER\x10\x15\x12\x17\n\x13REMOVE_GROUP_MEMBER\x10\x16\x12\x16\n\x12LIST_GROUP_MEMBERS\x10\x17\x12\x0f\n\x0b\x43REATE_ROLE\x10\x18\x12\x0f\n\x0b\x44\x45LETE_ROLE\x10\x19\x12\x0c\n\x08GET_ROLE\x10\x1a\x12\x0e\n\nLIST_ROLES\x10\x1b\x12\x0f\n\x0b\x41SSIGN_ROLE\x10\x1c\x12\x11\n\rUNASSIGN_ROLE\x10\x1d\x12\x19\n\x15LIST_ROLE_ASSIGNMENTS\x10\x1e\x12\x19\n\x15LIST_PRINCIPAL_GROUPS\x10\x1f\x12\x18\n\x14LIST_PRINCIPAL_ROLES\x10 \x12\x12\n\x0e\x45XPLAIN_ACCESS\x10!\"\x04\x08\x05\x10\x05\"\x04\x08\x06\x10\x06\"\x04\x08\x07\x10\x07\"\x04\x08\x0c\x10\x0c\"\x04\x08\r\x10\r\"\x04\x08\x0e\x10\x0e\"\x04\x08\x0f\x10\x0f\"\x04\x08\x10\x10\x10*\x15LIST_AUTHORITY_GRANTS*\x13GET_AUTHORITY_GRANT*\x16\x43REATE_AUTHORITY_GRANT*\x15RENEW_AUTHORITY_GRANT*\x16REVOKE_AUTHORITY_GRANTJ\x04\x08\x03\x10\x04J\x04\x08\x06\x10\x07J\x04\x08\x07\x10\x08J\x04\x08\r\x10\x0eJ\x04\x08\x08\x10\tJ\x04\x08\x10\x10\x11J\x04\x08\x11\x10\x12J\x04\x08\x12\x10\x13R\x12\x61uthority_grant_idR\x16\x61uthority_grant_filterR\x17\x61uthority_grant_requestR\x1d\x61uthority_grant_renew_request\"\x88\x01\n\rACLRuleFilter\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\r\n\x05limit\x18\x05 \x01(\x05\x12\x0e\n\x06offset\x18\x06 \x01(\x05\"\xd4\x01\n\x0e\x41\x43LAuditFilter\x12\x12\n\nstart_time\x18\x01 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x02 \x01(\x03\x12\x16\n\x0eprincipal_type\x18\x03 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x04 \x01(\t\x12\x15\n\rresource_type\x18\x05 \x01(\t\x12\x13\n\x0bresource_id\x18\x06 \x01(\t\x12\x10\n\x08\x64\x65\x63ision\x18\x07 \x01(\t\x12\x11\n\tworkspace\x18\x08 \x01(\t\x12\r\n\x05limit\x18\t \x01(\x05\x12\x0e\n\x06offset\x18\n \x01(\x05\"\xb9\x01\n\x0f\x41\x43LGrantRequest\x12\x16\n\x0eprincipal_type\x18\x01 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x05 \x01(\x05\x12\x12\n\ngranted_by\x18\x06 \x01(\t\x12\x0e\n\x06reason\x18\x07 \x01(\t\x12\x12\n\nexpires_at\x18\x08 \x01(\x03\"a\n\x15\x41\x43LSetFallbackRequest\x12\x15\n\rrule_category\x18\x01 \x01(\t\x12\x1d\n\x15\x66\x61llback_access_level\x18\x02 \x01(\x05\x12\x12\n\nupdated_by\x18\x03 \x01(\t\"\xff\x01\n\x17\x41\x43LAuthorityGrantFilter\x12\x15\n\rroot_grant_id\x18\x01 \x01(\t\x12\x14\n\x0csubject_type\x18\x02 \x01(\t\x12\x12\n\nsubject_id\x18\x03 \x01(\t\x12\x15\n\rdelegate_type\x18\x04 \x01(\t\x12\x13\n\x0b\x64\x65legate_id\x18\x05 \x01(\t\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12\x17\n\x0finclude_revoked\x18\x08 \x01(\x08\x12\x13\n\x0b\x61\x63tive_only\x18\t \x01(\x08\x12\r\n\x05limit\x18\n \x01(\x05\x12\x0e\n\x06offset\x18\x0b \x01(\x05\"N\n#ACLAuthorityGrantResourceScopeEntry\x12\x15\n\rresource_type\x18\x01 \x01(\t\x12\x10\n\x08patterns\x18\x02 \x03(\t\"\xa9\x05\n\x18\x41\x43LAuthorityGrantRequest\x12(\n\x07subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fparent_grant_id\x18\x05 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x06 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\x07 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\x08 \x03(\t\x12\x46\n\x0eresource_scope\x18\t \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\n \x03(\t\x12\x18\n\x10max_access_level\x18\x0b \x01(\x05\x12\x15\n\raudience_type\x18\x0c \x01(\t\x12\x13\n\x0b\x61udience_id\x18\r \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x0e \x01(\x08\x12\x12\n\nexpires_at\x18\x0f \x01(\x03\x12\x17\n\x0frenewable_until\x18\x10 \x01(\x03\x12\x0e\n\x06reason\x18\x11 \x01(\t\x12\x43\n\x08metadata\x18\x12 \x03(\x0b\x32\x31.aether.v1.ACLAuthorityGrantRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x1d\x41\x43LRenewAuthorityGrantRequest\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x12\n\nexpires_at\x18\x02 \x01(\x03\x12\x16\n\x0e\x65xtend_seconds\x18\x03 \x01(\x05\"\xf5\x01\n\x0b\x41\x43LRuleInfo\x12\x0f\n\x07rule_id\x18\x01 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x02 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x03 \x01(\t\x12\x15\n\rresource_type\x18\x04 \x01(\t\x12\x13\n\x0bresource_id\x18\x05 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x06 \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x07 \x01(\t\x12\x12\n\ngranted_by\x18\x08 \x01(\t\x12\x12\n\ngranted_at\x18\t \x01(\x03\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x0e\n\x06reason\x18\x0b \x01(\t\"\xac\x01\n\x15\x41\x43LFallbackPolicyInfo\x12\x11\n\tpolicy_id\x18\x01 \x01(\t\x12\x15\n\rrule_category\x18\x02 \x01(\t\x12\x1d\n\x15\x66\x61llback_access_level\x18\x03 \x01(\x05\x12\"\n\x1a\x66\x61llback_access_level_name\x18\x04 \x01(\t\x12\x12\n\nupdated_by\x18\x05 \x01(\t\x12\x12\n\nupdated_at\x18\x06 \x01(\x03\"\xc3\x03\n\x11\x41\x43LAuditEntryInfo\x12\x10\n\x08\x61udit_id\x18\x01 \x01(\x03\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x10\n\x08\x64\x65\x63ision\x18\x03 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x04 \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x05 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x06 \x01(\t\x12\x14\n\x0cprincipal_id\x18\x07 \x01(\t\x12\x15\n\rresource_type\x18\x08 \x01(\t\x12\x13\n\x0bresource_id\x18\t \x01(\t\x12\x11\n\toperation\x18\n \x01(\t\x12\x11\n\tworkspace\x18\x0b \x01(\t\x12\x0f\n\x07rule_id\x18\r \x01(\t\x12\x18\n\x10\x66\x61llback_applied\x18\x0e \x01(\x08\x12\x12\n\ngateway_id\x18\x0f \x01(\t\x12\x12\n\nsession_id\x18\x10 \x01(\t\x12<\n\x08metadata\x18\x11 \x03(\x0b\x32*.aether.v1.ACLAuditEntryInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01J\x04\x08\x0c\x10\r\"\xb4\x06\n\x15\x41\x43LAuthorityGrantInfo\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x15\n\rroot_grant_id\x18\x02 \x01(\t\x12(\n\x07subject\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x05 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x06 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fparent_grant_id\x18\x07 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x08 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\t \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\n \x03(\t\x12\x46\n\x0eresource_scope\x18\x0b \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x0c \x03(\t\x12\x18\n\x10max_access_level\x18\r \x01(\x05\x12\x19\n\x11\x61\x63\x63\x65ss_level_name\x18\x0e \x01(\t\x12\x15\n\raudience_type\x18\x0f \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x10 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x11 \x01(\x08\x12\x12\n\nexpires_at\x18\x12 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x13 \x01(\x03\x12\x12\n\nrenewed_at\x18\x14 \x01(\x03\x12\x0f\n\x07revoked\x18\x15 \x01(\x08\x12\x12\n\nrevoked_at\x18\x16 \x01(\x03\x12\x0e\n\x06reason\x18\x17 \x01(\t\x12@\n\x08metadata\x18\x18 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantInfo.MetadataEntry\x12\x12\n\ncreated_at\x18\x19 \x01(\x03\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\":\n\x10\x41\x43LCleanupResult\x12\x15\n\rdeleted_count\x18\x01 \x01(\x03\x12\x0f\n\x07message\x18\x02 \x01(\t\"\xb5\x01\n\x0f\x41\x43LGroupRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x12\n\ncreated_by\x18\x03 \x01(\t\x12:\n\x08metadata\x18\x04 \x03(\x0b\x32(.aether.v1.ACLGroupRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb3\x01\n\x0e\x41\x43LRoleRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x12\n\ncreated_by\x18\x03 \x01(\t\x12\x39\n\x08metadata\x18\x04 \x03(\x0b\x32\'.aether.v1.ACLRoleRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"g\n\x15\x41\x43LGroupMemberRequest\x12\x13\n\x0bmember_type\x18\x01 \x01(\t\x12\x11\n\tmember_id\x18\x02 \x01(\t\x12\x12\n\ngranted_by\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\"n\n\x18\x41\x43LRoleAssignmentRequest\x12\x15\n\rassignee_type\x18\x01 \x01(\t\x12\x13\n\x0b\x61ssignee_id\x18\x02 \x01(\t\x12\x12\n\ngranted_by\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x03\"\xdb\x01\n\x0c\x41\x43LGroupInfo\x12\x10\n\x08group_id\x18\x01 \x01(\t\x12\x12\n\ngroup_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x12\n\ncreated_by\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x37\n\x08metadata\x18\x06 \x03(\x0b\x32%.aether.v1.ACLGroupInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd7\x01\n\x0b\x41\x43LRoleInfo\x12\x0f\n\x07role_id\x18\x01 \x01(\t\x12\x11\n\trole_name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x12\n\ncreated_by\x18\x04 \x01(\t\x12\x12\n\ncreated_at\x18\x05 \x01(\x03\x12\x36\n\x08metadata\x18\x06 \x03(\x0b\x32$.aether.v1.ACLRoleInfo.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8c\x01\n\x12\x41\x43LGroupMemberInfo\x12\x12\n\ngroup_name\x18\x01 \x01(\t\x12\x13\n\x0bmember_type\x18\x02 \x01(\t\x12\x11\n\tmember_id\x18\x03 \x01(\t\x12\x12\n\ngranted_by\x18\x04 \x01(\t\x12\x12\n\ngranted_at\x18\x05 \x01(\x03\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\"\x92\x01\n\x15\x41\x43LRoleAssignmentInfo\x12\x11\n\trole_name\x18\x01 \x01(\t\x12\x15\n\rassignee_type\x18\x02 \x01(\t\x12\x13\n\x0b\x61ssignee_id\x18\x03 \x01(\t\x12\x12\n\ngranted_by\x18\x04 \x01(\t\x12\x12\n\ngranted_at\x18\x05 \x01(\x03\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\"v\n\x19\x41\x43LAccessContributionInfo\x12\x0f\n\x07subject\x18\x01 \x01(\t\x12\x0f\n\x07rule_id\x18\x02 \x01(\t\x12\x14\n\x0c\x61\x63\x63\x65ss_level\x18\x03 \x01(\x05\x12\x10\n\x08resource\x18\x04 \x01(\t\x12\x0f\n\x07\x65xpired\x18\x05 \x01(\x08\"\xe9\x01\n\x18\x41\x43LAccessExplanationInfo\x12\x11\n\tprincipal\x18\x01 \x01(\t\x12\x10\n\x08subjects\x18\x02 \x03(\t\x12;\n\rcontributions\x18\x03 \x03(\x0b\x32$.aether.v1.ACLAccessContributionInfo\x12\x0f\n\x07\x61llowed\x18\x04 \x01(\x08\x12\x10\n\x08\x64\x65\x63ision\x18\x05 \x01(\t\x12\x1e\n\x16\x65\x66\x66\x65\x63tive_access_level\x18\x06 \x01(\x05\x12\x18\n\x10\x66\x61llback_applied\x18\x07 \x01(\x08\x12\x0e\n\x06reason\x18\x08 \x01(\t\"\xdd\x06\n\x0b\x41\x43LResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12$\n\x04rule\x18\x04 \x01(\x0b\x32\x16.aether.v1.ACLRuleInfo\x12%\n\x05rules\x18\x05 \x03(\x0b\x32\x16.aether.v1.ACLRuleInfo\x12\x13\n\x0btotal_rules\x18\x06 \x01(\x05\x12\x39\n\x0f\x66\x61llback_policy\x18\x08 \x01(\x0b\x32 .aether.v1.ACLFallbackPolicyInfo\x12\x33\n\raudit_entries\x18\t \x03(\x0b\x32\x1c.aether.v1.ACLAuditEntryInfo\x12\x1b\n\x13total_audit_entries\x18\n \x01(\x05\x12\x33\n\x0e\x63leanup_result\x18\x0b \x01(\x0b\x32\x1b.aether.v1.ACLCleanupResult\x12\x39\n\x0f\x61uthority_grant\x18\r \x01(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12:\n\x10\x61uthority_grants\x18\x0e \x03(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\x1e\n\x16total_authority_grants\x18\x0f \x01(\x05\x12\x12\n\nrequest_id\x18\x0c \x01(\t\x12&\n\x05group\x18\x10 \x01(\x0b\x32\x17.aether.v1.ACLGroupInfo\x12\'\n\x06groups\x18\x11 \x03(\x0b\x32\x17.aether.v1.ACLGroupInfo\x12$\n\x04role\x18\x12 \x01(\x0b\x32\x16.aether.v1.ACLRoleInfo\x12%\n\x05roles\x18\x13 \x03(\x0b\x32\x16.aether.v1.ACLRoleInfo\x12\x34\n\rgroup_members\x18\x14 \x03(\x0b\x32\x1d.aether.v1.ACLGroupMemberInfo\x12:\n\x10role_assignments\x18\x15 \x03(\x0b\x32 .aether.v1.ACLRoleAssignmentInfo\x12\x38\n\x0b\x65xplanation\x18\x16 \x01(\x0b\x32#.aether.v1.ACLAccessExplanationInfoJ\x04\x08\x07\x10\x08\"\xd3\x05\n\x17\x41uthorityGrantOperation\x12\x35\n\x02op\x18\x01 \x01(\x0e\x32).aether.v1.AuthorityGrantOperation.OpType\x12\x10\n\x08grant_id\x18\x02 \x01(\t\x12\x42\n\x10\x65xchange_request\x18\x03 \x01(\x0b\x32(.aether.v1.AuthorityGrantExchangeRequest\x12>\n\x0e\x64\x65rive_request\x18\x04 \x01(\x0b\x32&.aether.v1.AuthorityGrantDeriveRequest\x12?\n\rrenew_request\x18\x05 \x01(\x0b\x32(.aether.v1.ACLRenewAuthorityGrantRequest\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12:\n\x0clist_request\x18\x07 \x01(\x0b\x32$.aether.v1.AuthorityGrantListRequest\x12M\n\x16\x62\x61tch_exchange_request\x18\x08 \x01(\x0b\x32-.aether.v1.AuthorityGrantBatchExchangeRequest\x12R\n\x19\x64\x65rive_for_target_request\x18\t \x01(\x0b\x32/.aether.v1.AuthorityGrantDeriveForTargetRequest\x12\x1c\n\x14workflow_schedule_id\x18\n \x01(\t\"\x98\x01\n\x06OpType\x12\x0c\n\x08\x45XCHANGE\x10\x00\x12\n\n\x06\x44\x45RIVE\x10\x01\x12\x07\n\x03GET\x10\x02\x12\t\n\x05RENEW\x10\x03\x12\n\n\x06REVOKE\x10\x04\x12\x12\n\x0eLIST_MY_GRANTS\x10\x05\x12\x15\n\x11LIST_GRANTS_ON_ME\x10\x06\x12\x12\n\x0e\x42\x41TCH_EXCHANGE\x10\x07\x12\x15\n\x11\x44\x45RIVE_FOR_TARGET\x10\x08\"\x85\x04\n\x1d\x41uthorityGrantExchangeRequest\x12\x19\n\x11source_session_id\x18\x01 \x01(\t\x12\x17\n\x0fworkspace_scope\x18\x02 \x03(\t\x12\x46\n\x0eresource_scope\x18\x03 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x04 \x03(\t\x12\x18\n\x10max_access_level\x18\x05 \x01(\x05\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x08 \x01(\x08\x12\x12\n\nexpires_at\x18\t \x01(\x03\x12\x17\n\x0frenewable_until\x18\n \x01(\x03\x12\x14\n\x0cmay_delegate\x18\x0b \x01(\x08\x12\x16\n\x0eremaining_hops\x18\x0c \x01(\x05\x12\x0e\n\x06reason\x18\r \x01(\t\x12H\n\x08metadata\x18\x0e \x03(\x0b\x32\x36.aether.v1.AuthorityGrantExchangeRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xaa\x04\n\x1b\x41uthorityGrantDeriveRequest\x12\x17\n\x0fparent_grant_id\x18\x01 \x01(\t\x12)\n\x08\x64\x65legate\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x17\n\x0fworkspace_scope\x18\x03 \x03(\t\x12\x46\n\x0eresource_scope\x18\x04 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x05 \x03(\t\x12\x18\n\x10max_access_level\x18\x06 \x01(\x05\x12\x15\n\raudience_type\x18\x07 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x08 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\t \x01(\x08\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x17\n\x0frenewable_until\x18\x0b \x01(\x03\x12\x14\n\x0cmay_delegate\x18\x0c \x01(\x08\x12\x16\n\x0eremaining_hops\x18\r \x01(\x05\x12\x0e\n\x06reason\x18\x0e \x01(\t\x12\x46\n\x08metadata\x18\x0f \x03(\x0b\x32\x34.aether.v1.AuthorityGrantDeriveRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xef\x01\n\x16\x41uthorityGrantResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12/\n\x05grant\x18\x04 \x01(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\x12\n\nrequest_id\x18\x05 \x01(\t\x12\x30\n\x06grants\x18\x06 \x03(\x0b\x32 .aether.v1.ACLAuthorityGrantInfo\x12\r\n\x05total\x18\x07 \x01(\x05\x12\x1e\n\x16\x63\x61\x63he_hint_ttl_seconds\x18\x08 \x01(\x05\"\x7f\n\x19\x41uthorityGrantListRequest\x12\x15\n\raudience_type\x18\x01 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x02 \x01(\t\x12\x17\n\x0finclude_revoked\x18\x03 \x01(\x08\x12\r\n\x05limit\x18\x04 \x01(\x05\x12\x0e\n\x06offset\x18\x05 \x01(\x05\"}\n\"AuthorityGrantBatchExchangeRequest\x12:\n\x08requests\x18\x01 \x03(\x0b\x32(.aether.v1.AuthorityGrantExchangeRequest\x12\x1b\n\x13stop_on_first_error\x18\x02 \x01(\x08\"\xb2\x02\n$AuthorityGrantDeriveForTargetRequest\x12\x17\n\x0fparent_grant_id\x18\x01 \x01(\t\x12\'\n\x06target\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x03 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x04 \x01(\t\x12\x17\n\x0foperation_scope\x18\x05 \x03(\t\x12\x18\n\x10max_access_level\x18\x06 \x01(\x05\x12\x12\n\nexpires_at\x18\x07 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x08 \x01(\x03\x12\x14\n\x0cmay_delegate\x18\t \x01(\x08\x12\x16\n\x0eremaining_hops\x18\n \x01(\x05\x12\x0e\n\x06reason\x18\x0b \x01(\t\"\xc3\x01\n\x11\x41uthorityIdentity\x12(\n\x07subject\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12)\n\x08\x64\x65legate\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12*\n\tissued_by\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\"\xd1\x01\n\rAuthoritySpan\x12\x17\n\x0fworkspace_scope\x18\x01 \x03(\t\x12\x18\n\x10max_access_level\x18\x02 \x01(\x05\x12\x15\n\raudience_type\x18\x03 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x04 \x01(\t\x12#\n\x1bvalid_while_audience_active\x18\x05 \x01(\x08\x12\x12\n\nexpires_at\x18\x06 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x07 \x01(\x03\x12\x0f\n\x07revoked\x18\x08 \x01(\x08\"x\n\x18\x41uthorityGrantRevocation\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x15\n\rroot_grant_id\x18\x02 \x01(\t\x12\x0e\n\x06reason\x18\x03 \x01(\t\x12\x12\n\nrevoked_at\x18\x04 \x01(\x03\x12\x0f\n\x07\x63\x61scade\x18\x05 \x01(\x08\"_\n\x1d\x41uthorityRequestRoutingTarget\x12*\n\tprincipal\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x12\n\ncapability\x18\x02 \x01(\t\"M\n\"AuthorityRequestResourceScopeEntry\x12\x15\n\rresource_type\x18\x01 \x01(\t\x12\x10\n\x08patterns\x18\x02 \x03(\t\"\xc7\x06\n\x10\x41uthorityRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x31\n\x06status\x18\x02 \x01(\x0e\x32!.aether.v1.AuthorityRequestStatus\x12\x31\n\x10requesting_actor\x18\x03 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12/\n\x0etarget_subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x1f\n\x17\x64\x65sired_workspace_scope\x18\x05 \x03(\t\x12M\n\x16\x64\x65sired_resource_scope\x18\x06 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17\x64\x65sired_operation_scope\x18\x07 \x03(\t\x12\x36\n\x16requested_access_level\x18\x08 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12\"\n\x1arequested_duration_seconds\x18\t \x01(\x03\x12\x15\n\raudience_type\x18\n \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x0b \x01(\t\x12@\n\x0erouting_target\x18\x0c \x01(\x0b\x32(.aether.v1.AuthorityRequestRoutingTarget\x12\x0e\n\x06reason\x18\r \x01(\t\x12\x0f\n\x07task_id\x18\x0e \x01(\t\x12;\n\x08metadata\x18\x0f \x03(\x0b\x32).aether.v1.AuthorityRequest.MetadataEntry\x12\x12\n\ncreated_at\x18\x10 \x01(\x03\x12\x12\n\nexpires_at\x18\x11 \x01(\x03\x12\x13\n\x0bresolved_at\x18\x12 \x01(\x03\x12\x18\n\x10granted_grant_id\x18\x13 \x01(\t\x12,\n\x0bresolved_by\x18\x14 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x19\n\x11resolution_reason\x18\x15 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xfa\x04\n\x1d\x43reateAuthorityRequestPayload\x12\x31\n\x10requesting_actor\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12/\n\x0etarget_subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x1f\n\x17\x64\x65sired_workspace_scope\x18\x03 \x03(\t\x12M\n\x16\x64\x65sired_resource_scope\x18\x04 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17\x64\x65sired_operation_scope\x18\x05 \x03(\t\x12\x36\n\x16requested_access_level\x18\x06 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12\"\n\x1arequested_duration_seconds\x18\x07 \x01(\x03\x12\x15\n\raudience_type\x18\x08 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\t \x01(\t\x12@\n\x0erouting_target\x18\n \x01(\x0b\x32(.aether.v1.AuthorityRequestRoutingTarget\x12\x0e\n\x06reason\x18\x0b \x01(\t\x12\x0f\n\x07task_id\x18\x0c \x01(\t\x12H\n\x08metadata\x18\r \x03(\x0b\x32\x36.aether.v1.CreateAuthorityRequestPayload.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xca\x03\n\x1eResolveAuthorityRequestPayload\x12\x44\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32\x32.aether.v1.ResolveAuthorityRequestPayload.Decision\x12\x1f\n\x17granted_workspace_scope\x18\x02 \x03(\t\x12M\n\x16granted_resource_scope\x18\x03 \x03(\x0b\x32-.aether.v1.AuthorityRequestResourceScopeEntry\x12\x1f\n\x17granted_operation_scope\x18\x04 \x03(\t\x12\x34\n\x14granted_access_level\x18\x05 \x01(\x0e\x32\x16.aether.v1.AccessLevel\x12 \n\x18granted_duration_seconds\x18\x06 \x01(\x03\x12\x0e\n\x06reason\x18\x07 \x01(\t\x12\x14\n\x0cmay_delegate\x18\x08 \x01(\x08\x12\x16\n\x0eremaining_hops\x18\t \x01(\x05\";\n\x08\x44\x65\x63ision\x12\x18\n\x14\x44\x45\x43ISION_UNSPECIFIED\x10\x00\x12\x0b\n\x07\x41PPROVE\x10\x01\x12\x08\n\x04\x44\x45NY\x10\x02\"\xa0\x01\n\x1a\x41uthorityRequestListFilter\x12\x31\n\x06status\x18\x01 \x01(\x0e\x32!.aether.v1.AuthorityRequestStatus\x12\x11\n\tworkspace\x18\x02 \x01(\t\x12\r\n\x05limit\x18\x03 \x01(\x05\x12\x0e\n\x06offset\x18\x04 \x01(\x05\x12\x1d\n\x15matching_capabilities\x18\x05 \x03(\t\"\xb5\x03\n\x19\x41uthorityRequestOperation\x12\x37\n\x02op\x18\x01 \x01(\x0e\x32+.aether.v1.AuthorityRequestOperation.OpType\x12\x12\n\nrequest_id\x18\x02 \x01(\t\x12\x38\n\x06\x63reate\x18\x03 \x01(\x0b\x32(.aether.v1.CreateAuthorityRequestPayload\x12:\n\x07resolve\x18\x04 \x01(\x0b\x32).aether.v1.ResolveAuthorityRequestPayload\x12:\n\x0blist_filter\x18\x05 \x01(\x0b\x32%.aether.v1.AuthorityRequestListFilter\x12\x19\n\x11\x63lient_request_id\x18\x06 \x01(\t\x12\x0e\n\x06reason\x18\x07 \x01(\t\"n\n\x06OpType\x12$\n AUTHORITY_REQUEST_OP_UNSPECIFIED\x10\x00\x12\n\n\x06\x43REATE\x10\x01\x12\x07\n\x03GET\x10\x02\x12\x10\n\x0cLIST_PENDING\x10\x03\x12\x0b\n\x07RESOLVE\x10\x04\x12\n\n\x06\x43\x41NCEL\x10\x05\"\xd0\x01\n!AuthorityRequestOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_request_id\x18\x03 \x01(\t\x12,\n\x07request\x18\x04 \x01(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12-\n\x08requests\x18\x05 \x03(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\"\x8b\x03\n\x15\x41uthorityRequestEvent\x12>\n\nevent_type\x18\x01 \x01(\x0e\x32*.aether.v1.AuthorityRequestEvent.EventType\x12,\n\x07request\x18\x02 \x01(\x0b\x32\x1b.aether.v1.AuthorityRequest\x12\x12\n\nemitted_at\x18\x03 \x01(\x03\"\xef\x01\n\tEventType\x12\'\n#AUTHORITY_REQUEST_EVENT_UNSPECIFIED\x10\x00\x12#\n\x1f\x41UTHORITY_REQUEST_EVENT_CREATED\x10\x01\x12$\n AUTHORITY_REQUEST_EVENT_APPROVED\x10\x02\x12\"\n\x1e\x41UTHORITY_REQUEST_EVENT_DENIED\x10\x03\x12#\n\x1f\x41UTHORITY_REQUEST_EVENT_EXPIRED\x10\x04\x12%\n!AUTHORITY_REQUEST_EVENT_CANCELLED\x10\x05\"\x84\x02\n\x0eTokenOperation\x12,\n\x02op\x18\x01 \x01(\x0e\x32 .aether.v1.TokenOperation.OpType\x12\x10\n\x08token_id\x18\x02 \x01(\t\x12\x35\n\x0e\x63reate_request\x18\x03 \x01(\x0b\x32\x1d.aether.v1.TokenCreateRequest\x12&\n\x06\x66ilter\x18\x04 \x01(\x0b\x32\x16.aether.v1.TokenFilter\x12\x12\n\nrequest_id\x18\x05 \x01(\t\"?\n\x06OpType\x12\x08\n\x04LIST\x10\x00\x12\x07\n\x03GET\x10\x01\x12\n\n\x06\x43REATE\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03\x12\n\n\x06REVOKE\x10\x04\"\x94\x01\n\x12TokenCreateRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x02 \x01(\t\x12\x1a\n\x12workspace_patterns\x18\x03 \x03(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\x12\x18\n\x10\x65xpires_in_hours\x18\x05 \x01(\x05\x12\x12\n\ncreated_by\x18\x06 \x01(\t\"E\n\x0bTokenFilter\x12\r\n\x05limit\x18\x01 \x01(\x05\x12\x0e\n\x06offset\x18\x02 \x01(\x05\x12\x17\n\x0finclude_revoked\x18\x03 \x01(\x08\"\xf4\x01\n\tTokenInfo\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x16\n\x0eprincipal_type\x18\x03 \x01(\t\x12\x1a\n\x12workspace_patterns\x18\x04 \x03(\t\x12\x0e\n\x06scopes\x18\x05 \x03(\t\x12\x12\n\ncreated_by\x18\x06 \x01(\t\x12\x12\n\nexpires_at\x18\x07 \x01(\x03\x12\x14\n\x0clast_used_at\x18\x08 \x01(\x03\x12\x0f\n\x07revoked\x18\t \x01(\x08\x12\x12\n\nrevoked_at\x18\n \x01(\x03\x12\x12\n\ncreated_at\x18\x0b \x01(\x03\x12\x12\n\nupdated_at\x18\x0c \x01(\x03\"\xfa\x01\n\rTokenResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12#\n\x05token\x18\x04 \x01(\x0b\x32\x14.aether.v1.TokenInfo\x12$\n\x06tokens\x18\x05 \x03(\x0b\x32\x14.aether.v1.TokenInfo\x12\x13\n\x0btotal_count\x18\x06 \x01(\x05\x12\x17\n\x0fplaintext_token\x18\x07 \x01(\t\x12+\n\rcreated_token\x18\x08 \x01(\x0b\x32\x14.aether.v1.TokenInfo\x12\x12\n\nrequest_id\x18\t \x01(\t\"\xb6\x02\n\x0eProgressReport\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\r\n\x05state\x18\x02 \x01(\t\x12\x12\n\ncompletion\x18\x03 \x01(\x01\x12\x0f\n\x07summary\x18\x04 \x01(\t\x12%\n\x04step\x18\x05 \x01(\x0b\x32\x17.aether.v1.ProgressStep\x12\x11\n\trecipient\x18\x06 \x01(\t\x12\x12\n\nrequest_id\x18\x07 \x01(\t\x12\x39\n\x08metadata\x18\x08 \x03(\x0b\x32\'.aether.v1.ProgressReport.MetadataEntry\x12%\n\x04kind\x18\t \x01(\x0e\x32\x17.aether.v1.ProgressKind\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"f\n\x0cProgressStep\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65tail\x18\x02 \x01(\t\x12\x10\n\x08sequence\x18\x03 \x01(\x05\x12\x13\n\x0btotal_steps\x18\x04 \x01(\x05\x12\x11\n\tstep_type\x18\x05 \x01(\t\"\xef\x02\n\x0eProgressUpdate\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\t\x12\x12\n\ncompletion\x18\x04 \x01(\x01\x12\x0f\n\x07summary\x18\x05 \x01(\t\x12%\n\x04step\x18\x06 \x01(\x0b\x32\x17.aether.v1.ProgressStep\x12\x14\n\x0ctimestamp_ms\x18\x07 \x01(\x03\x12\x11\n\tworkspace\x18\x08 \x01(\t\x12\x12\n\nrequest_id\x18\t \x01(\t\x12\x39\n\x08metadata\x18\n \x03(\x0b\x32\'.aether.v1.ProgressUpdate.MetadataEntry\x12\x11\n\trecipient\x18\x0b \x01(\t\x12%\n\x04kind\x18\x0c \x01(\x0e\x32\x17.aether.v1.ProgressKind\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe0\x02\n\x1eWorkflowScheduleAuthorityScope\x12\x17\n\x0fworkspace_scope\x18\x01 \x03(\t\x12\x46\n\x0eresource_scope\x18\x02 \x03(\x0b\x32..aether.v1.ACLAuthorityGrantResourceScopeEntry\x12\x17\n\x0foperation_scope\x18\x03 \x03(\t\x12\x18\n\x10max_access_level\x18\x04 \x01(\x05\x12\x12\n\nexpires_at\x18\x05 \x01(\x03\x12\x17\n\x0frenewable_until\x18\x06 \x01(\x03\x12$\n\x1crequired_task_authority_hops\x18\x07 \x01(\r\x12?\n\rlifetime_mode\x18\x08 \x01(\x0e\x32(.aether.v1.WorkflowAuthorityLifetimeMode\x12\x16\n\x0epolicy_version\x18\t \x01(\r\"\xfc\x02\n\x16WorkflowRequestContext\x12&\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12(\n\x07subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x18\n\x10\x61\x63tor_session_id\x18\x03 \x01(\t\x12?\n\x16schedule_authorization\x18\x04 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rroot_grant_id\x18\x05 \x01(\t\x12\x17\n\x0fsource_grant_id\x18\x06 \x01(\t\x12\x15\n\rexpires_at_ms\x18\x07 \x01(\x03\x12\x15\n\rpolicy_digest\x18\x08 \x01(\t\x12?\n\rlifetime_mode\x18\t \x01(\x0e\x32(.aether.v1.WorkflowAuthorityLifetimeMode\x12\x16\n\x0epolicy_version\x18\n \x01(\r\"\x9a\x07\n\x11WorkflowOperation\x12/\n\x02op\x18\x01 \x01(\x0e\x32#.aether.v1.WorkflowOperation.OpType\x12\n\n\x02id\x18\x02 \x01(\t\x12\x14\n\x0csecondary_id\x18\x03 \x01(\t\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\x12\x12\n\nrequest_id\x18\x06 \x01(\t\x12\x15\n\rstatus_filter\x18\x07 \x01(\t\x12\x36\n\rauthorization\x18\x08 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12K\n\x18schedule_authority_scope\x18\t \x01(\x0b\x32).aether.v1.WorkflowScheduleAuthorityScope\x12:\n\x0frequest_context\x18\n \x01(\x0b\x32!.aether.v1.WorkflowRequestContext\"\xa4\x04\n\x06OpType\x12\x0e\n\nLIST_RULES\x10\x00\x12\x0c\n\x08GET_RULE\x10\x01\x12\x0f\n\x0b\x43REATE_RULE\x10\x02\x12\x0f\n\x0bUPDATE_RULE\x10\x03\x12\x0f\n\x0b\x44\x45LETE_RULE\x10\x04\x12\x12\n\x0eLIST_WORKFLOWS\x10\x05\x12\x10\n\x0cGET_WORKFLOW\x10\x06\x12\x13\n\x0f\x43REATE_WORKFLOW\x10\x07\x12\x13\n\x0f\x44\x45LETE_WORKFLOW\x10\x08\x12\x12\n\x0eLIST_SCHEDULES\x10\t\x12\x13\n\x0f\x43REATE_SCHEDULE\x10\n\x12\x13\n\x0f\x44\x45LETE_SCHEDULE\x10\x0b\x12\x13\n\x0fLIST_EXECUTIONS\x10\x0c\x12\x11\n\rGET_EXECUTION\x10\r\x12\x14\n\x10\x43\x41NCEL_EXECUTION\x10\x0e\x12\x17\n\x13LIST_STATE_MACHINES\x10\x0f\x12\x15\n\x11GET_STATE_MACHINE\x10\x10\x12\x18\n\x14\x43REATE_STATE_MACHINE\x10\x11\x12\x18\n\x14\x44\x45LETE_STATE_MACHINE\x10\x12\x12\x15\n\x11LIST_SM_INSTANCES\x10\x13\x12\x13\n\x0fGET_SM_INSTANCE\x10\x14\x12\x16\n\x12\x43REATE_SM_INSTANCE\x10\x15\x12\x11\n\rSEND_SM_EVENT\x10\x16\x12\x13\n\x0fUPSERT_SCHEDULE\x10\x17\x12\x0e\n\nLIST_JOINS\x10\x18\x12\x0c\n\x08GET_JOIN\x10\x19\x12\x0f\n\x0b\x43\x41NCEL_JOIN\x10\x1a\"z\n\x10WorkflowResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\x12\x12\n\nrequest_id\x18\x06 \x01(\t\"\xa8\x03\n\x0fMessageEnvelope\x12\x0e\n\x06source\x18\x01 \x01(\t\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\x12,\n\x0cmessage_type\x18\x03 \x01(\x0e\x32\x16.aether.v1.MessageType\x12\x14\n\x0ctimestamp_ms\x18\x04 \x01(\x03\x12:\n\x08metadata\x18\x05 \x03(\x0b\x32(.aether.v1.MessageEnvelope.MetadataEntry\x12\x11\n\tworkspace\x18\x06 \x01(\t\x12\x32\n\x11on_behalf_subject\x18\x07 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x38\n\x0e\x61\x63\x63\x65ss_receipt\x18\x08 \x01(\x0b\x32 .aether.v1.AccessDecisionReceipt\x12\x42\n\x17\x66orwarded_authorization\x18\t \x01(\x0b\x32!.aether.v1.ForwardedAuthorization\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf7\x03\n\nAuditQuery\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x12\n\nstart_time\x18\x02 \x01(\x03\x12\x10\n\x08\x65nd_time\x18\x03 \x01(\x03\x12\x12\n\nevent_type\x18\x04 \x01(\t\x12\x12\n\nactor_type\x18\x05 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x06 \x01(\t\x12\x15\n\rresource_type\x18\x07 \x01(\t\x12\x13\n\x0bresource_id\x18\x08 \x01(\t\x12\x11\n\toperation\x18\t \x01(\t\x12\x11\n\tworkspace\x18\n \x01(\t\x12\x15\n\ronly_failures\x18\x0b \x01(\x08\x12\r\n\x05limit\x18\x0c \x01(\x05\x12\x0e\n\x06offset\x18\r \x01(\x05\x12\x14\n\x0csubject_type\x18\x0e \x01(\t\x12\x12\n\nsubject_id\x18\x0f \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\x10 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x11 \x01(\t\x12\x36\n\rauthorization\x18\x12 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x1b\n\x13\x65xclude_actor_types\x18\x13 \x03(\t\x12\x1a\n\x12\x65xclude_workspaces\x18\x14 \x03(\t\x12\x1e\n\x16\x65xclude_service_direct\x18\x15 \x01(\x08\"\x85\x01\n\x12\x41uditQueryResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12&\n\x07\x65ntries\x18\x04 \x03(\x0b\x32\x15.aether.v1.AuditEntry\x12\x13\n\x0btotal_count\x18\x05 \x01(\x05\"\x8a\x04\n\nAuditEntry\x12\x10\n\x08\x61udit_id\x18\x01 \x01(\x03\x12\x11\n\ttimestamp\x18\x02 \x01(\x03\x12\x12\n\nevent_type\x18\x03 \x01(\t\x12\x12\n\nactor_type\x18\x04 \x01(\t\x12\x10\n\x08\x61\x63tor_id\x18\x05 \x01(\t\x12\x15\n\rresource_type\x18\x06 \x01(\t\x12\x13\n\x0bresource_id\x18\x07 \x01(\t\x12\x11\n\toperation\x18\x08 \x01(\t\x12\x11\n\tworkspace\x18\t \x01(\t\x12\x12\n\nsession_id\x18\n \x01(\t\x12\x12\n\ngateway_id\x18\x0b \x01(\t\x12\x0f\n\x07success\x18\x0c \x01(\x08\x12\x15\n\rerror_message\x18\r \x01(\t\x12\x15\n\rmetadata_json\x18\x0e \x01(\t\x12\x14\n\x0csubject_type\x18\x0f \x01(\t\x12\x12\n\nsubject_id\x18\x10 \x01(\t\x12\x19\n\x11root_subject_type\x18\x11 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x12 \x01(\t\x12\x16\n\x0e\x61uthority_mode\x18\x13 \x01(\t\x12\x1f\n\x17root_authority_grant_id\x18\x14 \x01(\t\x12\x1a\n\x12\x61uthority_grant_id\x18\x15 \x01(\t\x12!\n\x19parent_authority_grant_id\x18\x16 \x01(\t\x12\x0e\n\x06source\x18\x17 \x01(\t\"\xb7\x02\n\x17SubmitAuditEventRequest\x12\x12\n\nevent_type\x18\x01 \x01(\t\x12\x11\n\toperation\x18\x02 \x01(\t\x12\x15\n\rresource_type\x18\x03 \x01(\t\x12\x13\n\x0bresource_id\x18\x04 \x01(\t\x12\x11\n\tworkspace\x18\x05 \x01(\t\x12\x0f\n\x07success\x18\x06 \x01(\x08\x12\x15\n\rerror_message\x18\x07 \x01(\t\x12\x42\n\x08metadata\x18\x08 \x03(\x0b\x32\x30.aether.v1.SubmitAuditEventRequest.MetadataEntry\x12\x19\n\x11\x63lient_request_id\x18\t \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"q\n\x18SubmitAuditEventResponse\x12\x19\n\x11\x63lient_request_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\x12\n\nerror_code\x18\x03 \x01(\t\x12\x15\n\rerror_message\x18\x04 \x01(\t\"\xf2\x04\n\x10ProxyHttpRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x02 \x01(\t\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x0c\n\x04path\x18\x04 \x01(\t\x12\x39\n\x07headers\x18\x05 \x03(\x0b\x32(.aether.v1.ProxyHttpRequest.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x06 \x01(\x0c\x12\x14\n\x0c\x62ody_chunked\x18\x07 \x01(\x08\x12\x36\n\rauthorization\x18\x08 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x15\n\rapp_workspace\x18\t \x01(\t\x12\x12\n\ntimeout_ms\x18\n \x01(\x03\x12\x18\n\x10\x66ollow_redirects\x18\x0b \x01(\x08\x12\x14\n\x0c\x62\x61\x63kend_name\x18\x0c \x01(\t\x12$\n\x1cstream_response_indefinitely\x18\r \x01(\x08\x12\x1e\n\x16stream_idle_timeout_ms\x18\x0e \x01(\x03\x12\x1f\n\x17max_response_body_bytes\x18\x0f \x01(\x03\x12\x19\n\x11proxy_chain_depth\x18\x10 \x01(\r\x12\x38\n\x0e\x63hecked_access\x18\x11 \x01(\x0b\x32 .aether.v1.ResourceAccessRequest\x12\x38\n\x0e\x61\x63\x63\x65ss_receipt\x18\x12 \x01(\x0b\x32 .aether.v1.AccessDecisionReceipt\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf2\x01\n\x11ProxyHttpResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x13\n\x0bstatus_code\x18\x02 \x01(\x05\x12:\n\x07headers\x18\x03 \x03(\x0b\x32).aether.v1.ProxyHttpResponse.HeadersEntry\x12\x0c\n\x04\x62ody\x18\x04 \x01(\x0c\x12\x14\n\x0c\x62ody_chunked\x18\x05 \x01(\x08\x12$\n\x05\x65rror\x18\x06 \x01(\x0b\x32\x15.aether.v1.ProxyError\x1a.\n\x0cHeadersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"d\n\x12ProxyHttpBodyChunk\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x12\n\nis_request\x18\x02 \x01(\x08\x12\x0b\n\x03seq\x18\x03 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0b\n\x03\x66in\x18\x05 \x01(\x08\"\xe2\x01\n\nProxyError\x12(\n\x04kind\x18\x01 \x01(\x0e\x32\x1a.aether.v1.ProxyError.Kind\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x98\x01\n\x04Kind\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x44IAL_FAILED\x10\x01\x12\x0b\n\x07TIMEOUT\x10\x02\x12\x12\n\x0eUPSTREAM_RESET\x10\x03\x12\x0e\n\nACL_DENIED\x10\x04\x12\x17\n\x13SIDECAR_UNAVAILABLE\x10\x05\x12\x15\n\x11PAYLOAD_TOO_LARGE\x10\x06\x12\x11\n\rDECODE_FAILED\x10\x07\"\xbd\x03\n\nTunnelOpen\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x14\n\x0ctarget_topic\x18\x02 \x01(\t\x12\x30\n\x08protocol\x18\x03 \x01(\x0e\x32\x1e.aether.v1.TunnelOpen.Protocol\x12\x13\n\x0bremote_hint\x18\x04 \x01(\t\x12\x35\n\x08metadata\x18\x05 \x03(\x0b\x32#.aether.v1.TunnelOpen.MetadataEntry\x12\x36\n\rauthorization\x18\x06 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\x12\x17\n\x0fidle_timeout_ms\x18\x07 \x01(\x03\x12\x11\n\tmax_bytes\x18\x08 \x01(\x03\x12\x15\n\rsession_token\x18\t \x01(\t\x12\x14\n\x0c\x62\x61\x63kend_name\x18\n \x01(\t\x12\x19\n\x11proxy_chain_depth\x18\x0b \x01(\r\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x08Protocol\x12\x07\n\x03TCP\x10\x00\x12\x07\n\x03UDP\x10\x01\x12\r\n\tWEBSOCKET\x10\x02\"G\n\nTunnelData\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x0b\n\x03\x66in\x18\x04 \x01(\x08\"\xad\x01\n\x0bTunnelClose\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12-\n\x06reason\x18\x02 \x01(\x0e\x32\x1d.aether.v1.TunnelClose.Reason\x12\x0e\n\x06\x64\x65tail\x18\x03 \x01(\t\"L\n\x06Reason\x12\n\n\x06NORMAL\x10\x00\x12\x0e\n\nPEER_RESET\x10\x01\x12\x10\n\x0cIDLE_TIMEOUT\x10\x02\x12\t\n\x05QUOTA\x10\x03\x12\t\n\x05\x45RROR\x10\x04\"@\n\tTunnelAck\x12\x11\n\ttunnel_id\x18\x01 \x01(\t\x12\x0f\n\x07\x61\x63k_seq\x18\x02 \x01(\r\x12\x0f\n\x07\x63redits\x18\x03 \x01(\r\"\xbd\x01\n\x17ResolveAuthorityRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12&\n\x05\x61\x63tor\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x10\n\x08grant_id\x18\x03 \x01(\t\x12(\n\x07subject\x18\x04 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x15\n\raudience_type\x18\x05 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x06 \x01(\t\"z\n\x18ResolveAuthorityResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12/\n\tauthority\x18\x04 \x01(\x0b\x32\x1c.aether.v1.ResolvedAuthority\"\x93\x01\n\x11ResolvedAuthority\x12&\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12(\n\x07subject\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12,\n\x05grant\x18\x03 \x01(\x0b\x32\x1d.aether.v1.AuthorityGrantInfo\"\x88\x02\n\x12\x41uthorityGrantInfo\x12\x10\n\x08grant_id\x18\x01 \x01(\t\x12\x14\n\x0csubject_type\x18\x02 \x01(\t\x12\x12\n\nsubject_id\x18\x03 \x01(\t\x12\x19\n\x11root_subject_type\x18\x04 \x01(\t\x12\x17\n\x0froot_subject_id\x18\x05 \x01(\t\x12\x15\n\raudience_type\x18\x06 \x01(\t\x12\x13\n\x0b\x61udience_id\x18\x07 \x01(\t\x12\x18\n\x10max_access_level\x18\x08 \x01(\x05\x12\x17\n\x0fworkspace_scope\x18\t \x03(\t\x12\x12\n\nexpires_at\x18\n \x01(\x03\x12\x0f\n\x07revoked\x18\x0b \x01(\x08\"Y\n\x17\x43onnectionStatusRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12*\n\tprincipal\x18\x02 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\"r\n\x18\x43onnectionStatusResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\n\n\x02ok\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x11\n\tconnected\x18\x04 \x01(\x08\x12\x14\n\x0clast_seen_at\x18\x05 \x01(\x03\"\x9d\x02\n\x19TaskSubscriptionOperation\x12\x37\n\x02op\x18\x01 \x01(\x0e\x32+.aether.v1.TaskSubscriptionOperation.OpType\x12\x0f\n\x07task_id\x18\x02 \x01(\t\x12\x11\n\trecursive\x18\x03 \x01(\x08\x12\x19\n\x11\x63lient_request_id\x18\x04 \x01(\t\x12\x1f\n\x17start_timestamp_unix_ms\x18\x05 \x01(\x03\x12\x17\n\x0fsubscription_id\x18\x06 \x01(\t\"N\n\x06OpType\x12$\n TASK_SUBSCRIPTION_OP_UNSPECIFIED\x10\x00\x12\r\n\tSUBSCRIBE\x10\x01\x12\x0f\n\x0bUNSUBSCRIBE\x10\x02\"\x88\x01\n!TaskSubscriptionOperationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x19\n\x11\x63lient_request_id\x18\x03 \x01(\t\x12\x0f\n\x07task_id\x18\x04 \x01(\t\x12\x17\n\x0fsubscription_id\x18\x05 \x01(\t\"\xfb\x02\n\tTaskEvent\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x1a\n\x12\x65mitted_at_unix_ms\x18\x02 \x01(\x03\x12\x11\n\tworkspace\x18\x03 \x01(\t\x12\x16\n\x0eparent_task_id\x18\x04 \x01(\t\x12\x17\n\x0fsubscription_id\x18\x05 \x01(\t\x12;\n\x0estatus_changed\x18\n \x01(\x0b\x32!.aether.v1.TaskStatusChangedEventH\x00\x12\x30\n\x08progress\x18\x0b \x01(\x0b\x32\x1c.aether.v1.TaskProgressEventH\x00\x12=\n\x0f\x63hild_lifecycle\x18\x0c \x01(\x0b\x32\".aether.v1.TaskChildLifecycleEventH\x00\x12\x46\n\x11\x61uthority_request\x18\r \x01(\x0b\x32).aether.v1.TaskAuthorityRequestEventRelayH\x00\x42\x07\n\x05\x65vent\"~\n\x16TaskStatusChangedEvent\x12*\n\x0b\x66rom_status\x18\x01 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12(\n\tto_status\x18\x02 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\xb4\x01\n\x11TaskProgressEvent\x12\r\n\x05state\x18\x01 \x01(\t\x12\x10\n\x08progress\x18\x02 \x01(\x01\x12\x0f\n\x07message\x18\x03 \x01(\t\x12<\n\x08metadata\x18\x04 \x03(\x0b\x32*.aether.v1.TaskProgressEvent.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"p\n\x17TaskChildLifecycleEvent\x12\x15\n\rchild_task_id\x18\x01 \x01(\t\x12+\n\x0c\x63hild_status\x18\x02 \x01(\x0e\x32\x15.aether.v1.TaskStatus\x12\x11\n\tlifecycle\x18\x03 \x01(\t\"Q\n\x1eTaskAuthorityRequestEventRelay\x12/\n\x05\x65vent\x18\x01 \x01(\x0b\x32 .aether.v1.AuthorityRequestEvent\"\xa0\x01\n\x15ResourceAccessRequest\x12\x15\n\rresource_type\x18\x01 \x01(\t\x12\x13\n\x0bresource_id\x18\x02 \x01(\t\x12\x11\n\toperation\x18\x03 \x01(\t\x12\x11\n\tworkspace\x18\x04 \x01(\t\x12\x1d\n\x15required_access_level\x18\x05 \x01(\x05\x12\x16\n\x0e\x63orrelation_id\x18\x06 \x01(\t\"\xc2\x03\n\x15\x41\x63\x63\x65ssDecisionReceipt\x12\x13\n\x0b\x64\x65\x63ision_id\x18\x01 \x01(\t\x12\x31\n\x07request\x18\x02 \x01(\x0b\x32 .aether.v1.ResourceAccessRequest\x12\x0f\n\x07\x61llowed\x18\x03 \x01(\x08\x12\x10\n\x08\x64\x65\x63ision\x18\x04 \x01(\t\x12\x1e\n\x16\x65\x66\x66\x65\x63tive_access_level\x18\x05 \x01(\x05\x12&\n\x05\x61\x63tor\x18\x06 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12(\n\x07subject\x18\x07 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12-\n\x0croot_subject\x18\x08 \x01(\x0b\x32\x17.aether.v1.PrincipalRef\x12\x16\n\x0e\x61uthority_mode\x18\t \x01(\t\x12\x10\n\x08grant_id\x18\n \x01(\t\x12\x15\n\rroot_grant_id\x18\x0b \x01(\t\x12\x17\n\x0f\x65valuated_at_ms\x18\x0c \x01(\x03\x12\x15\n\rexpires_at_ms\x18\r \x01(\x03\x12\x13\n\x0b\x64\x65nial_code\x18\x0e \x01(\t\x12\x17\n\x0f\x64\x65livery_target\x18\x0f \x01(\t\"\x94\x01\n\x14\x41\x63\x63\x65ssCheckOperation\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x30\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x0b\x32 .aether.v1.ResourceAccessRequest\x12\x36\n\rauthorization\x18\x03 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"}\n\x13\x41\x63\x63\x65ssCheckResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x32\n\x08\x64\x65\x63ision\x18\x04 \x01(\x0b\x32 .aether.v1.AccessDecisionReceipt\"\x99\x01\n\x19\x42\x61tchAccessCheckOperation\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x30\n\x06\x61\x63\x63\x65ss\x18\x02 \x03(\x0b\x32 .aether.v1.ResourceAccessRequest\x12\x36\n\rauthorization\x18\x03 \x01(\x0b\x32\x1f.aether.v1.AuthorizationContext\"\x83\x01\n\x18\x42\x61tchAccessCheckResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\t\x12\x0f\n\x07success\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\x12\x33\n\tdecisions\x18\x04 \x03(\x0b\x32 .aether.v1.AccessDecisionReceipt*t\n\x0bMessageType\x12\x1c\n\x18MESSAGE_TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04\x43HAT\x10\x01\x12\x0b\n\x07\x43ONTROL\x10\x02\x12\r\n\tTOOL_CALL\x10\x03\x12\t\n\x05\x45VENT\x10\x04\x12\n\n\x06METRIC\x10\x05\x12\n\n\x06OPAQUE\x10\x06*\xf2\x01\n\rPrincipalType\x12\x1e\n\x1aPRINCIPAL_TYPE_UNSPECIFIED\x10\x00\x12\x13\n\x0fPRINCIPAL_AGENT\x10\x01\x12\x12\n\x0ePRINCIPAL_TASK\x10\x02\x12\x12\n\x0ePRINCIPAL_USER\x10\x03\x12\x1a\n\x16PRINCIPAL_ORCHESTRATOR\x10\x04\x12\x1d\n\x19PRINCIPAL_WORKFLOW_ENGINE\x10\x05\x12\x1c\n\x18PRINCIPAL_METRICS_BRIDGE\x10\x06\x12\x14\n\x10PRINCIPAL_BRIDGE\x10\x07\x12\x15\n\x11PRINCIPAL_SERVICE\x10\x08*\xc4\x02\n\nTaskStatus\x12\x1b\n\x17TASK_STATUS_UNSPECIFIED\x10\x00\x12\x16\n\x12TASK_STATUS_QUEUED\x10\x01\x12\x17\n\x13TASK_STATUS_RUNNING\x10\x02\x12\x19\n\x15TASK_STATUS_COMPLETED\x10\x03\x12\x16\n\x12TASK_STATUS_FAILED\x10\x04\x12\x19\n\x15TASK_STATUS_CANCELLED\x10\x05\x12\x1d\n\x19TASK_STATUS_WAITING_INPUT\x10\x06\x12!\n\x1dTASK_STATUS_WAITING_AUTHORITY\x10\x07\x12\"\n\x1eTASK_STATUS_WAITING_DEPENDENCY\x10\x08\x12\x1a\n\x16TASK_STATUS_HIBERNATED\x10\t\x12\x18\n\x14TASK_STATUS_REJECTED\x10\n*\x81\x01\n\x0cHealthStatus\x12\x1d\n\x19HEALTH_STATUS_UNSPECIFIED\x10\x00\x12\x19\n\x15HEALTH_STATUS_HEALTHY\x10\x01\x12\x1a\n\x16HEALTH_STATUS_DEGRADED\x10\x02\x12\x1b\n\x17HEALTH_STATUS_UNHEALTHY\x10\x03*s\n\x11HealthCheckStatus\x12#\n\x1fHEALTH_CHECK_STATUS_UNSPECIFIED\x10\x00\x12\x1a\n\x16HEALTH_CHECK_STATUS_OK\x10\x01\x12\x1d\n\x19HEALTH_CHECK_STATUS_ERROR\x10\x02*\xc3\x01\n\x0b\x41\x63\x63\x65ssLevel\x12\x1c\n\x18\x41\x43\x43\x45SS_LEVEL_UNSPECIFIED\x10\x00\x12\x15\n\x11\x41\x43\x43\x45SS_LEVEL_NONE\x10\x01\x12\x15\n\x11\x41\x43\x43\x45SS_LEVEL_READ\x10\x02\x12\x1a\n\x16\x41\x43\x43\x45SS_LEVEL_READWRITE\x10\x03\x12\x17\n\x13\x41\x43\x43\x45SS_LEVEL_MANAGE\x10\x04\x12\x16\n\x12\x41\x43\x43\x45SS_LEVEL_ADMIN\x10\x05\x12\x1b\n\x17\x41\x43\x43\x45SS_LEVEL_SUPERADMIN\x10\x06*=\n\x12TaskAssignmentMode\x12\x0f\n\x0bSELF_ASSIGN\x10\x00\x12\x0c\n\x08TARGETED\x10\x01\x12\x08\n\x04POOL\x10\x02*t\n\tTaskClass\x12\x1a\n\x16TASK_CLASS_UNSPECIFIED\x10\x00\x12\x1a\n\x16TASK_CLASS_INTERACTIVE\x10\x01\x12\x19\n\x15TASK_CLASS_BACKGROUND\x10\x02\x12\x14\n\x10TASK_CLASS_BATCH\x10\x03*\xa9\x01\n\x0cTaskPriority\x12\x1d\n\x19TASK_PRIORITY_UNSPECIFIED\x10\x00\x12\x16\n\x12TASK_PRIORITY_XLOW\x10\n\x12\x15\n\x11TASK_PRIORITY_LOW\x10\x14\x12\x18\n\x14TASK_PRIORITY_NORMAL\x10\x1e\x12\x16\n\x12TASK_PRIORITY_HIGH\x10(\x12\x19\n\x15TASK_PRIORITY_PREEMPT\x10\x32*\x99\x01\n\x0f\x42\x61\x63koffStrategy\x12 \n\x1c\x42\x41\x43KOFF_STRATEGY_UNSPECIFIED\x10\x00\x12\x1a\n\x16\x42\x41\x43KOFF_STRATEGY_FIXED\x10\x01\x12 \n\x1c\x42\x41\x43KOFF_STRATEGY_EXPONENTIAL\x10\x02\x12&\n\"BACKOFF_STRATEGY_EXPLICIT_SCHEDULE\x10\x03*\xa6\x01\n\x13TargetOfflinePolicy\x12%\n!TARGET_OFFLINE_POLICY_UNSPECIFIED\x10\x00\x12%\n!TARGET_OFFLINE_POLICY_ORCHESTRATE\x10\x01\x12\x1f\n\x1bTARGET_OFFLINE_POLICY_QUEUE\x10\x02\x12 \n\x1cTARGET_OFFLINE_POLICY_REJECT\x10\x03*\x94\x01\n\nWaitReason\x12\x1b\n\x17WAIT_REASON_UNSPECIFIED\x10\x00\x12\x15\n\x11WAIT_REASON_INPUT\x10\x01\x12\x19\n\x15WAIT_REASON_AUTHORITY\x10\x02\x12\x1a\n\x16WAIT_REASON_DEPENDENCY\x10\x03\x12\x1b\n\x17WAIT_REASON_HIBERNATION\x10\x04*\x82\x02\n\x16\x41uthorityRequestStatus\x12(\n$AUTHORITY_REQUEST_STATUS_UNSPECIFIED\x10\x00\x12$\n AUTHORITY_REQUEST_STATUS_PENDING\x10\x01\x12%\n!AUTHORITY_REQUEST_STATUS_APPROVED\x10\x02\x12#\n\x1f\x41UTHORITY_REQUEST_STATUS_DENIED\x10\x03\x12$\n AUTHORITY_REQUEST_STATUS_EXPIRED\x10\x04\x12&\n\"AUTHORITY_REQUEST_STATUS_CANCELLED\x10\x05*t\n\x0cProgressKind\x12\x1d\n\x19PROGRESS_KIND_UNSPECIFIED\x10\x00\x12\x16\n\x12PROGRESS_KIND_CHAT\x10\x01\x12\x15\n\x11PROGRESS_KIND_APP\x10\x02\x12\x16\n\x12PROGRESS_KIND_TASK\x10\x03*v\n\x1dWorkflowAuthorityLifetimeMode\x12,\n(WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND\x10\x00\x12\'\n#WORKFLOW_AUTHORITY_LIFETIME_DURABLE\x10\x01\x32X\n\rAetherGateway\x12G\n\x07\x43onnect\x12\x1a.aether.v1.UpstreamMessage\x1a\x1c.aether.v1.DownstreamMessage(\x01\x30\x01\x42-Z+github.com/scitrera/aether/api/proto;aetherb\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -112,450 +112,478 @@
_globals['_TUNNELOPEN_METADATAENTRY']._serialized_options = b'8\001'
_globals['_TASKPROGRESSEVENT_METADATAENTRY']._loaded_options = None
_globals['_TASKPROGRESSEVENT_METADATAENTRY']._serialized_options = b'8\001'
- _globals['_MESSAGETYPE']._serialized_start=41933
- _globals['_MESSAGETYPE']._serialized_end=42049
- _globals['_PRINCIPALTYPE']._serialized_start=42052
- _globals['_PRINCIPALTYPE']._serialized_end=42294
- _globals['_TASKSTATUS']._serialized_start=42297
- _globals['_TASKSTATUS']._serialized_end=42621
- _globals['_HEALTHSTATUS']._serialized_start=42624
- _globals['_HEALTHSTATUS']._serialized_end=42753
- _globals['_HEALTHCHECKSTATUS']._serialized_start=42755
- _globals['_HEALTHCHECKSTATUS']._serialized_end=42870
- _globals['_ACCESSLEVEL']._serialized_start=42873
- _globals['_ACCESSLEVEL']._serialized_end=43068
- _globals['_TASKASSIGNMENTMODE']._serialized_start=43070
- _globals['_TASKASSIGNMENTMODE']._serialized_end=43131
- _globals['_TASKCLASS']._serialized_start=43133
- _globals['_TASKCLASS']._serialized_end=43249
- _globals['_TASKPRIORITY']._serialized_start=43252
- _globals['_TASKPRIORITY']._serialized_end=43421
- _globals['_BACKOFFSTRATEGY']._serialized_start=43424
- _globals['_BACKOFFSTRATEGY']._serialized_end=43577
- _globals['_WAITREASON']._serialized_start=43580
- _globals['_WAITREASON']._serialized_end=43728
- _globals['_AUTHORITYREQUESTSTATUS']._serialized_start=43731
- _globals['_AUTHORITYREQUESTSTATUS']._serialized_end=43989
- _globals['_PROGRESSKIND']._serialized_start=43991
- _globals['_PROGRESSKIND']._serialized_end=44107
+ _globals['_MESSAGETYPE']._serialized_start=45746
+ _globals['_MESSAGETYPE']._serialized_end=45862
+ _globals['_PRINCIPALTYPE']._serialized_start=45865
+ _globals['_PRINCIPALTYPE']._serialized_end=46107
+ _globals['_TASKSTATUS']._serialized_start=46110
+ _globals['_TASKSTATUS']._serialized_end=46434
+ _globals['_HEALTHSTATUS']._serialized_start=46437
+ _globals['_HEALTHSTATUS']._serialized_end=46566
+ _globals['_HEALTHCHECKSTATUS']._serialized_start=46568
+ _globals['_HEALTHCHECKSTATUS']._serialized_end=46683
+ _globals['_ACCESSLEVEL']._serialized_start=46686
+ _globals['_ACCESSLEVEL']._serialized_end=46881
+ _globals['_TASKASSIGNMENTMODE']._serialized_start=46883
+ _globals['_TASKASSIGNMENTMODE']._serialized_end=46944
+ _globals['_TASKCLASS']._serialized_start=46946
+ _globals['_TASKCLASS']._serialized_end=47062
+ _globals['_TASKPRIORITY']._serialized_start=47065
+ _globals['_TASKPRIORITY']._serialized_end=47234
+ _globals['_BACKOFFSTRATEGY']._serialized_start=47237
+ _globals['_BACKOFFSTRATEGY']._serialized_end=47390
+ _globals['_TARGETOFFLINEPOLICY']._serialized_start=47393
+ _globals['_TARGETOFFLINEPOLICY']._serialized_end=47559
+ _globals['_WAITREASON']._serialized_start=47562
+ _globals['_WAITREASON']._serialized_end=47710
+ _globals['_AUTHORITYREQUESTSTATUS']._serialized_start=47713
+ _globals['_AUTHORITYREQUESTSTATUS']._serialized_end=47971
+ _globals['_PROGRESSKIND']._serialized_start=47973
+ _globals['_PROGRESSKIND']._serialized_end=48089
+ _globals['_WORKFLOWAUTHORITYLIFETIMEMODE']._serialized_start=48091
+ _globals['_WORKFLOWAUTHORITYLIFETIMEMODE']._serialized_end=48209
_globals['_UPSTREAMMESSAGE']._serialized_start=28
- _globals['_UPSTREAMMESSAGE']._serialized_end=1741
- _globals['_DOWNSTREAMMESSAGE']._serialized_start=1744
- _globals['_DOWNSTREAMMESSAGE']._serialized_end=3850
- _globals['_TASKHIBERNATED']._serialized_start=3852
- _globals['_TASKHIBERNATED']._serialized_end=3939
- _globals['_CONNECTIONACK']._serialized_start=3942
- _globals['_CONNECTIONACK']._serialized_end=4252
- _globals['_INITCONNECTION']._serialized_start=4255
- _globals['_INITCONNECTION']._serialized_end=4972
- _globals['_INITCONNECTION_CREDENTIALSENTRY']._serialized_start=4907
- _globals['_INITCONNECTION_CREDENTIALSENTRY']._serialized_end=4957
- _globals['_BUILDINFO']._serialized_start=4974
- _globals['_BUILDINFO']._serialized_end=5048
- _globals['_EXTENSIONDECLARATION']._serialized_start=5050
- _globals['_EXTENSIONDECLARATION']._serialized_end=5141
- _globals['_NEGOTIATEDEXTENSION']._serialized_start=5143
- _globals['_NEGOTIATEDEXTENSION']._serialized_end=5239
- _globals['_WORKFLOWENGINEIDENTITY']._serialized_start=5241
- _globals['_WORKFLOWENGINEIDENTITY']._serialized_end=5286
- _globals['_METRICSBRIDGEIDENTITY']._serialized_start=5288
- _globals['_METRICSBRIDGEIDENTITY']._serialized_end=5332
- _globals['_ORCHESTRATORIDENTITY']._serialized_start=5334
- _globals['_ORCHESTRATORIDENTITY']._serialized_end=5427
- _globals['_BRIDGEIDENTITY']._serialized_start=5429
- _globals['_BRIDGEIDENTITY']._serialized_end=5488
- _globals['_SERVICEIDENTITY']._serialized_start=5490
- _globals['_SERVICEIDENTITY']._serialized_end=5576
- _globals['_AGENTIDENTITY']._serialized_start=5578
- _globals['_AGENTIDENTITY']._serialized_end=5655
- _globals['_TASKIDENTITY']._serialized_start=5657
- _globals['_TASKIDENTITY']._serialized_end=5740
- _globals['_USERIDENTITY']._serialized_start=5742
- _globals['_USERIDENTITY']._serialized_end=5792
- _globals['_PRINCIPALREF']._serialized_start=5794
- _globals['_PRINCIPALREF']._serialized_end=5854
- _globals['_AUTHORIZATIONCONTEXT']._serialized_start=5857
- _globals['_AUTHORIZATIONCONTEXT']._serialized_end=6015
- _globals['_RESOLVEDAUTHORITYINFO']._serialized_start=6018
- _globals['_RESOLVEDAUTHORITYINFO']._serialized_end=6206
- _globals['_SENDMESSAGE']._serialized_start=6209
- _globals['_SENDMESSAGE']._serialized_end=6386
- _globals['_METRIC']._serialized_start=6389
- _globals['_METRIC']._serialized_end=6585
- _globals['_METRIC_METADATAENTRY']._serialized_start=6538
- _globals['_METRIC_METADATAENTRY']._serialized_end=6585
- _globals['_METRICENTRY']._serialized_start=6587
- _globals['_METRICENTRY']._serialized_end=6641
- _globals['_SWITCHWORKSPACE']._serialized_start=6643
- _globals['_SWITCHWORKSPACE']._serialized_end=6686
- _globals['_KVOPERATION']._serialized_start=6689
- _globals['_KVOPERATION']._serialized_end=7467
- _globals['_KVOPERATION_OPTYPE']._serialized_start=7068
- _globals['_KVOPERATION_OPTYPE']._serialized_end=7286
- _globals['_KVOPERATION_SCOPE']._serialized_start=7289
- _globals['_KVOPERATION_SCOPE']._serialized_end=7467
- _globals['_KVRESPONSE']._serialized_start=7470
- _globals['_KVRESPONSE']._serialized_end=7723
- _globals['_KVRESPONSE_KVMAPENTRY']._serialized_start=7679
- _globals['_KVRESPONSE_KVMAPENTRY']._serialized_end=7723
- _globals['_INCOMINGMESSAGE']._serialized_start=7726
- _globals['_INCOMINGMESSAGE']._serialized_end=7899
- _globals['_CONFIGSNAPSHOT']._serialized_start=7902
- _globals['_CONFIGSNAPSHOT']._serialized_end=8526
- _globals['_CONFIGSNAPSHOT_KVENTRY']._serialized_start=8265
- _globals['_CONFIGSNAPSHOT_KVENTRY']._serialized_end=8306
- _globals['_CONFIGSNAPSHOT_GLOBALKVENTRY']._serialized_start=8308
- _globals['_CONFIGSNAPSHOT_GLOBALKVENTRY']._serialized_end=8355
- _globals['_CONFIGSNAPSHOT_TASKCONTEXTENTRY']._serialized_start=8357
- _globals['_CONFIGSNAPSHOT_TASKCONTEXTENTRY']._serialized_end=8407
- _globals['_CONFIGSNAPSHOT_WORKSPACEEXCLUSIVEKVENTRY']._serialized_start=8409
- _globals['_CONFIGSNAPSHOT_WORKSPACEEXCLUSIVEKVENTRY']._serialized_end=8468
- _globals['_CONFIGSNAPSHOT_GLOBALEXCLUSIVEKVENTRY']._serialized_start=8470
- _globals['_CONFIGSNAPSHOT_GLOBALEXCLUSIVEKVENTRY']._serialized_end=8526
- _globals['_SIGNAL']._serialized_start=8529
- _globals['_SIGNAL']._serialized_end=8658
- _globals['_SIGNAL_SIGNALTYPE']._serialized_start=8599
- _globals['_SIGNAL_SIGNALTYPE']._serialized_end=8658
- _globals['_ERRORRESPONSE']._serialized_start=8660
- _globals['_ERRORRESPONSE']._serialized_end=8769
- _globals['_RETRYPOLICY']._serialized_start=8772
- _globals['_RETRYPOLICY']._serialized_end=9003
- _globals['_TASKCOMPLETIONEVENT']._serialized_start=9005
- _globals['_TASKCOMPLETIONEVENT']._serialized_end=9107
- _globals['_CREATETASKREQUEST']._serialized_start=9110
- _globals['_CREATETASKREQUEST']._serialized_end=9937
- _globals['_CREATETASKREQUEST_LAUNCHPARAMOVERRIDESENTRY']._serialized_start=9829
- _globals['_CREATETASKREQUEST_LAUNCHPARAMOVERRIDESENTRY']._serialized_end=9888
- _globals['_CREATETASKREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_CREATETASKREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_CREATETASKRESPONSE']._serialized_start=9940
- _globals['_CREATETASKRESPONSE']._serialized_end=10142
- _globals['_TASKASSIGNMENT']._serialized_start=10145
- _globals['_TASKASSIGNMENT']._serialized_end=10664
- _globals['_TASKASSIGNMENT_METADATAENTRY']._serialized_start=6538
- _globals['_TASKASSIGNMENT_METADATAENTRY']._serialized_end=6585
- _globals['_TASKASSIGNMENT_LAUNCHPARAMSENTRY']._serialized_start=10613
- _globals['_TASKASSIGNMENT_LAUNCHPARAMSENTRY']._serialized_end=10664
- _globals['_CHECKPOINTOPERATION']._serialized_start=10667
- _globals['_CHECKPOINTOPERATION']._serialized_end=10851
- _globals['_CHECKPOINTOPERATION_OPTYPE']._serialized_start=10801
- _globals['_CHECKPOINTOPERATION_OPTYPE']._serialized_end=10851
- _globals['_CHECKPOINTRESPONSE']._serialized_start=10853
- _globals['_CHECKPOINTRESPONSE']._serialized_end=10971
- _globals['_ADMINQUERY']._serialized_start=10974
- _globals['_ADMINQUERY']._serialized_end=11210
- _globals['_ADMINQUERY_OPTYPE']._serialized_start=11115
- _globals['_ADMINQUERY_OPTYPE']._serialized_end=11210
- _globals['_CONNECTIONFILTER']._serialized_start=11212
- _globals['_CONNECTIONFILTER']._serialized_end=11320
- _globals['_CONNECTIONINFO']._serialized_start=11323
- _globals['_CONNECTIONINFO']._serialized_end=11563
- _globals['_ADMINRESPONSE']._serialized_start=11566
- _globals['_ADMINRESPONSE']._serialized_end=11866
- _globals['_HEALTHINFO']._serialized_start=11869
- _globals['_HEALTHINFO']._serialized_end=12103
- _globals['_HEALTHINFO_CHECKSENTRY']._serialized_start=12034
- _globals['_HEALTHINFO_CHECKSENTRY']._serialized_end=12103
- _globals['_HEALTHCHECK']._serialized_start=12105
- _globals['_HEALTHCHECK']._serialized_end=12196
- _globals['_GATEWAYINFO']._serialized_start=12199
- _globals['_GATEWAYINFO']._serialized_end=12379
- _globals['_GATEWAYSTATS']._serialized_start=12382
- _globals['_GATEWAYSTATS']._serialized_end=12792
- _globals['_SESSIONOPERATION']._serialized_start=12795
- _globals['_SESSIONOPERATION']._serialized_end=13063
- _globals['_SESSIONOPERATION_OPTYPE']._serialized_start=13020
- _globals['_SESSIONOPERATION_OPTYPE']._serialized_end=13063
- _globals['_SESSIONOPERATIONRESPONSE']._serialized_start=13066
- _globals['_SESSIONOPERATIONRESPONSE']._serialized_end=13277
- _globals['_TASKQUERY']._serialized_start=13280
- _globals['_TASKQUERY']._serialized_end=13437
- _globals['_TASKQUERY_OPTYPE']._serialized_start=13410
- _globals['_TASKQUERY_OPTYPE']._serialized_end=13437
- _globals['_TASKFILTER']._serialized_start=13440
- _globals['_TASKFILTER']._serialized_end=14188
- _globals['_TASKINFO']._serialized_start=14191
- _globals['_TASKINFO']._serialized_end=15158
- _globals['_TASKINFO_METADATAENTRY']._serialized_start=6538
- _globals['_TASKINFO_METADATAENTRY']._serialized_end=6585
- _globals['_TASKQUERYRESPONSE']._serialized_start=15161
- _globals['_TASKQUERYRESPONSE']._serialized_end=15349
- _globals['_TASKOPERATION']._serialized_start=15352
- _globals['_TASKOPERATION']._serialized_end=15622
- _globals['_TASKOPERATION_OPTYPE']._serialized_start=15507
- _globals['_TASKOPERATION_OPTYPE']._serialized_end=15622
- _globals['_WAITSPEC']._serialized_start=15625
- _globals['_WAITSPEC']._serialized_end=15989
- _globals['_WAITSPEC_INPUTMATCHENTRY']._serialized_start=15940
- _globals['_WAITSPEC_INPUTMATCHENTRY']._serialized_end=15989
- _globals['_HIBERNATIONDESCRIPTOR']._serialized_start=15991
- _globals['_HIBERNATIONDESCRIPTOR']._serialized_end=16118
- _globals['_TASKOPERATIONRESPONSE']._serialized_start=16120
- _globals['_TASKOPERATIONRESPONSE']._serialized_end=16247
- _globals['_WORKSPACEOPERATION']._serialized_start=16250
- _globals['_WORKSPACEOPERATION']._serialized_end=16538
- _globals['_WORKSPACEOPERATION_OPTYPE']._serialized_start=16453
- _globals['_WORKSPACEOPERATION_OPTYPE']._serialized_end=16538
- _globals['_WORKSPACEFILTER']._serialized_start=16540
- _globals['_WORKSPACEFILTER']._serialized_end=16607
- _globals['_WORKSPACEINFO']._serialized_start=16610
- _globals['_WORKSPACEINFO']._serialized_end=16947
- _globals['_WORKSPACEINFO_METADATAENTRY']._serialized_start=6538
- _globals['_WORKSPACEINFO_METADATAENTRY']._serialized_end=6585
- _globals['_WORKSPACERESPONSE']._serialized_start=16950
- _globals['_WORKSPACERESPONSE']._serialized_end=17200
- _globals['_MESSAGEFLOWINFO']._serialized_start=17203
- _globals['_MESSAGEFLOWINFO']._serialized_end=17334
- _globals['_FLOWNODE']._serialized_start=17337
- _globals['_FLOWNODE']._serialized_end=17488
- _globals['_FLOWEDGE']._serialized_start=17490
- _globals['_FLOWEDGE']._serialized_end=17556
- _globals['_AGENTOPERATION']._serialized_start=17559
- _globals['_AGENTOPERATION']._serialized_end=17910
- _globals['_AGENTOPERATION_OPTYPE']._serialized_start=17809
- _globals['_AGENTOPERATION_OPTYPE']._serialized_end=17910
- _globals['_AGENTFILTER']._serialized_start=17912
- _globals['_AGENTFILTER']._serialized_end=17986
- _globals['_AGENTREGISTRATIONINFO']._serialized_start=17989
- _globals['_AGENTREGISTRATIONINFO']._serialized_end=18467
- _globals['_AGENTREGISTRATIONINFO_LAUNCHPARAMSENTRY']._serialized_start=10613
- _globals['_AGENTREGISTRATIONINFO_LAUNCHPARAMSENTRY']._serialized_end=10664
- _globals['_AGENTREGISTRATIONINFO_CAPABILITIESENTRY']._serialized_start=18416
- _globals['_AGENTREGISTRATIONINFO_CAPABILITIESENTRY']._serialized_end=18467
- _globals['_AGENTRESOURCESCHEMAENTRY']._serialized_start=18469
- _globals['_AGENTRESOURCESCHEMAENTRY']._serialized_end=18579
- _globals['_AGENTLAUNCHPARAMS']._serialized_start=18582
- _globals['_AGENTLAUNCHPARAMS']._serialized_end=18769
- _globals['_AGENTLAUNCHPARAMS_PARAMOVERRIDESENTRY']._serialized_start=18716
- _globals['_AGENTLAUNCHPARAMS_PARAMOVERRIDESENTRY']._serialized_end=18769
- _globals['_ORCHESTRATORINFO']._serialized_start=18771
- _globals['_ORCHESTRATORINFO']._serialized_end=18854
- _globals['_AGENTLAUNCHRESULT']._serialized_start=18856
- _globals['_AGENTLAUNCHRESULT']._serialized_end=18909
- _globals['_AGENTRESPONSE']._serialized_start=18912
- _globals['_AGENTRESPONSE']._serialized_end=19221
- _globals['_ACLOPERATION']._serialized_start=19224
- _globals['_ACLOPERATION']._serialized_end=20804
- _globals['_ACLOPERATION_OPTYPE']._serialized_start=19981
- _globals['_ACLOPERATION_OPTYPE']._serialized_end=20656
- _globals['_ACLRULEFILTER']._serialized_start=20807
- _globals['_ACLRULEFILTER']._serialized_end=20943
- _globals['_ACLAUDITFILTER']._serialized_start=20946
- _globals['_ACLAUDITFILTER']._serialized_end=21158
- _globals['_ACLGRANTREQUEST']._serialized_start=21161
- _globals['_ACLGRANTREQUEST']._serialized_end=21346
- _globals['_ACLSETFALLBACKREQUEST']._serialized_start=21348
- _globals['_ACLSETFALLBACKREQUEST']._serialized_end=21445
- _globals['_ACLAUTHORITYGRANTFILTER']._serialized_start=21448
- _globals['_ACLAUTHORITYGRANTFILTER']._serialized_end=21703
- _globals['_ACLAUTHORITYGRANTRESOURCESCOPEENTRY']._serialized_start=21705
- _globals['_ACLAUTHORITYGRANTRESOURCESCOPEENTRY']._serialized_end=21783
- _globals['_ACLAUTHORITYGRANTREQUEST']._serialized_start=21786
- _globals['_ACLAUTHORITYGRANTREQUEST']._serialized_end=22467
- _globals['_ACLAUTHORITYGRANTREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_ACLAUTHORITYGRANTREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_ACLRENEWAUTHORITYGRANTREQUEST']._serialized_start=22469
- _globals['_ACLRENEWAUTHORITYGRANTREQUEST']._serialized_end=22562
- _globals['_ACLRULEINFO']._serialized_start=22565
- _globals['_ACLRULEINFO']._serialized_end=22810
- _globals['_ACLFALLBACKPOLICYINFO']._serialized_start=22813
- _globals['_ACLFALLBACKPOLICYINFO']._serialized_end=22985
- _globals['_ACLAUDITENTRYINFO']._serialized_start=22988
- _globals['_ACLAUDITENTRYINFO']._serialized_end=23439
- _globals['_ACLAUDITENTRYINFO_METADATAENTRY']._serialized_start=6538
- _globals['_ACLAUDITENTRYINFO_METADATAENTRY']._serialized_end=6585
- _globals['_ACLAUTHORITYGRANTINFO']._serialized_start=23442
- _globals['_ACLAUTHORITYGRANTINFO']._serialized_end=24262
- _globals['_ACLAUTHORITYGRANTINFO_METADATAENTRY']._serialized_start=6538
- _globals['_ACLAUTHORITYGRANTINFO_METADATAENTRY']._serialized_end=6585
- _globals['_ACLCLEANUPRESULT']._serialized_start=24264
- _globals['_ACLCLEANUPRESULT']._serialized_end=24322
- _globals['_ACLGROUPREQUEST']._serialized_start=24325
- _globals['_ACLGROUPREQUEST']._serialized_end=24506
- _globals['_ACLGROUPREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_ACLGROUPREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_ACLROLEREQUEST']._serialized_start=24509
- _globals['_ACLROLEREQUEST']._serialized_end=24688
- _globals['_ACLROLEREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_ACLROLEREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_ACLGROUPMEMBERREQUEST']._serialized_start=24690
- _globals['_ACLGROUPMEMBERREQUEST']._serialized_end=24793
- _globals['_ACLROLEASSIGNMENTREQUEST']._serialized_start=24795
- _globals['_ACLROLEASSIGNMENTREQUEST']._serialized_end=24905
- _globals['_ACLGROUPINFO']._serialized_start=24908
- _globals['_ACLGROUPINFO']._serialized_end=25127
- _globals['_ACLGROUPINFO_METADATAENTRY']._serialized_start=6538
- _globals['_ACLGROUPINFO_METADATAENTRY']._serialized_end=6585
- _globals['_ACLROLEINFO']._serialized_start=25130
- _globals['_ACLROLEINFO']._serialized_end=25345
- _globals['_ACLROLEINFO_METADATAENTRY']._serialized_start=6538
- _globals['_ACLROLEINFO_METADATAENTRY']._serialized_end=6585
- _globals['_ACLGROUPMEMBERINFO']._serialized_start=25348
- _globals['_ACLGROUPMEMBERINFO']._serialized_end=25488
- _globals['_ACLROLEASSIGNMENTINFO']._serialized_start=25491
- _globals['_ACLROLEASSIGNMENTINFO']._serialized_end=25637
- _globals['_ACLACCESSCONTRIBUTIONINFO']._serialized_start=25639
- _globals['_ACLACCESSCONTRIBUTIONINFO']._serialized_end=25757
- _globals['_ACLACCESSEXPLANATIONINFO']._serialized_start=25760
- _globals['_ACLACCESSEXPLANATIONINFO']._serialized_end=25993
- _globals['_ACLRESPONSE']._serialized_start=25996
- _globals['_ACLRESPONSE']._serialized_end=26857
- _globals['_AUTHORITYGRANTOPERATION']._serialized_start=26860
- _globals['_AUTHORITYGRANTOPERATION']._serialized_end=27553
- _globals['_AUTHORITYGRANTOPERATION_OPTYPE']._serialized_start=27401
- _globals['_AUTHORITYGRANTOPERATION_OPTYPE']._serialized_end=27553
- _globals['_AUTHORITYGRANTEXCHANGEREQUEST']._serialized_start=27556
- _globals['_AUTHORITYGRANTEXCHANGEREQUEST']._serialized_end=28073
- _globals['_AUTHORITYGRANTEXCHANGEREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_AUTHORITYGRANTEXCHANGEREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_AUTHORITYGRANTDERIVEREQUEST']._serialized_start=28076
- _globals['_AUTHORITYGRANTDERIVEREQUEST']._serialized_end=28630
- _globals['_AUTHORITYGRANTDERIVEREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_AUTHORITYGRANTDERIVEREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_AUTHORITYGRANTRESPONSE']._serialized_start=28633
- _globals['_AUTHORITYGRANTRESPONSE']._serialized_end=28872
- _globals['_AUTHORITYGRANTLISTREQUEST']._serialized_start=28874
- _globals['_AUTHORITYGRANTLISTREQUEST']._serialized_end=29001
- _globals['_AUTHORITYGRANTBATCHEXCHANGEREQUEST']._serialized_start=29003
- _globals['_AUTHORITYGRANTBATCHEXCHANGEREQUEST']._serialized_end=29128
- _globals['_AUTHORITYGRANTDERIVEFORTARGETREQUEST']._serialized_start=29131
- _globals['_AUTHORITYGRANTDERIVEFORTARGETREQUEST']._serialized_end=29437
- _globals['_AUTHORITYIDENTITY']._serialized_start=29440
- _globals['_AUTHORITYIDENTITY']._serialized_end=29635
- _globals['_AUTHORITYSPAN']._serialized_start=29638
- _globals['_AUTHORITYSPAN']._serialized_end=29847
- _globals['_AUTHORITYGRANTREVOCATION']._serialized_start=29849
- _globals['_AUTHORITYGRANTREVOCATION']._serialized_end=29969
- _globals['_AUTHORITYREQUESTROUTINGTARGET']._serialized_start=29971
- _globals['_AUTHORITYREQUESTROUTINGTARGET']._serialized_end=30066
- _globals['_AUTHORITYREQUESTRESOURCESCOPEENTRY']._serialized_start=30068
- _globals['_AUTHORITYREQUESTRESOURCESCOPEENTRY']._serialized_end=30145
- _globals['_AUTHORITYREQUEST']._serialized_start=30148
- _globals['_AUTHORITYREQUEST']._serialized_end=30987
- _globals['_AUTHORITYREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_AUTHORITYREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_CREATEAUTHORITYREQUESTPAYLOAD']._serialized_start=30990
- _globals['_CREATEAUTHORITYREQUESTPAYLOAD']._serialized_end=31624
- _globals['_CREATEAUTHORITYREQUESTPAYLOAD_METADATAENTRY']._serialized_start=6538
- _globals['_CREATEAUTHORITYREQUESTPAYLOAD_METADATAENTRY']._serialized_end=6585
- _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD']._serialized_start=31627
- _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD']._serialized_end=32085
- _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD_DECISION']._serialized_start=32026
- _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD_DECISION']._serialized_end=32085
- _globals['_AUTHORITYREQUESTLISTFILTER']._serialized_start=32088
- _globals['_AUTHORITYREQUESTLISTFILTER']._serialized_end=32248
- _globals['_AUTHORITYREQUESTOPERATION']._serialized_start=32251
- _globals['_AUTHORITYREQUESTOPERATION']._serialized_end=32688
- _globals['_AUTHORITYREQUESTOPERATION_OPTYPE']._serialized_start=32578
- _globals['_AUTHORITYREQUESTOPERATION_OPTYPE']._serialized_end=32688
- _globals['_AUTHORITYREQUESTOPERATIONRESPONSE']._serialized_start=32691
- _globals['_AUTHORITYREQUESTOPERATIONRESPONSE']._serialized_end=32899
- _globals['_AUTHORITYREQUESTEVENT']._serialized_start=32902
- _globals['_AUTHORITYREQUESTEVENT']._serialized_end=33297
- _globals['_AUTHORITYREQUESTEVENT_EVENTTYPE']._serialized_start=33058
- _globals['_AUTHORITYREQUESTEVENT_EVENTTYPE']._serialized_end=33297
- _globals['_TOKENOPERATION']._serialized_start=33300
- _globals['_TOKENOPERATION']._serialized_end=33560
- _globals['_TOKENOPERATION_OPTYPE']._serialized_start=33497
- _globals['_TOKENOPERATION_OPTYPE']._serialized_end=33560
- _globals['_TOKENCREATEREQUEST']._serialized_start=33563
- _globals['_TOKENCREATEREQUEST']._serialized_end=33711
- _globals['_TOKENFILTER']._serialized_start=33713
- _globals['_TOKENFILTER']._serialized_end=33782
- _globals['_TOKENINFO']._serialized_start=33785
- _globals['_TOKENINFO']._serialized_end=34029
- _globals['_TOKENRESPONSE']._serialized_start=34032
- _globals['_TOKENRESPONSE']._serialized_end=34282
- _globals['_PROGRESSREPORT']._serialized_start=34285
- _globals['_PROGRESSREPORT']._serialized_end=34595
- _globals['_PROGRESSREPORT_METADATAENTRY']._serialized_start=6538
- _globals['_PROGRESSREPORT_METADATAENTRY']._serialized_end=6585
- _globals['_PROGRESSSTEP']._serialized_start=34597
- _globals['_PROGRESSSTEP']._serialized_end=34699
- _globals['_PROGRESSUPDATE']._serialized_start=34702
- _globals['_PROGRESSUPDATE']._serialized_end=35069
- _globals['_PROGRESSUPDATE_METADATAENTRY']._serialized_start=6538
- _globals['_PROGRESSUPDATE_METADATAENTRY']._serialized_end=6585
- _globals['_WORKFLOWOPERATION']._serialized_start=35072
- _globals['_WORKFLOWOPERATION']._serialized_end=35801
- _globals['_WORKFLOWOPERATION_OPTYPE']._serialized_start=35253
- _globals['_WORKFLOWOPERATION_OPTYPE']._serialized_end=35801
- _globals['_WORKFLOWRESPONSE']._serialized_start=35803
- _globals['_WORKFLOWRESPONSE']._serialized_end=35925
- _globals['_MESSAGEENVELOPE']._serialized_start=35928
- _globals['_MESSAGEENVELOPE']._serialized_end=36226
- _globals['_MESSAGEENVELOPE_METADATAENTRY']._serialized_start=6538
- _globals['_MESSAGEENVELOPE_METADATAENTRY']._serialized_end=6585
- _globals['_AUDITQUERY']._serialized_start=36229
- _globals['_AUDITQUERY']._serialized_end=36732
- _globals['_AUDITQUERYRESPONSE']._serialized_start=36735
- _globals['_AUDITQUERYRESPONSE']._serialized_end=36868
- _globals['_AUDITENTRY']._serialized_start=36871
- _globals['_AUDITENTRY']._serialized_end=37393
- _globals['_SUBMITAUDITEVENTREQUEST']._serialized_start=37396
- _globals['_SUBMITAUDITEVENTREQUEST']._serialized_end=37707
- _globals['_SUBMITAUDITEVENTREQUEST_METADATAENTRY']._serialized_start=6538
- _globals['_SUBMITAUDITEVENTREQUEST_METADATAENTRY']._serialized_end=6585
- _globals['_SUBMITAUDITEVENTRESPONSE']._serialized_start=37709
- _globals['_SUBMITAUDITEVENTRESPONSE']._serialized_end=37822
- _globals['_PROXYHTTPREQUEST']._serialized_start=37825
- _globals['_PROXYHTTPREQUEST']._serialized_end=38335
- _globals['_PROXYHTTPREQUEST_HEADERSENTRY']._serialized_start=38289
- _globals['_PROXYHTTPREQUEST_HEADERSENTRY']._serialized_end=38335
- _globals['_PROXYHTTPRESPONSE']._serialized_start=38338
- _globals['_PROXYHTTPRESPONSE']._serialized_end=38580
- _globals['_PROXYHTTPRESPONSE_HEADERSENTRY']._serialized_start=38289
- _globals['_PROXYHTTPRESPONSE_HEADERSENTRY']._serialized_end=38335
- _globals['_PROXYHTTPBODYCHUNK']._serialized_start=38582
- _globals['_PROXYHTTPBODYCHUNK']._serialized_end=38682
- _globals['_PROXYERROR']._serialized_start=38685
- _globals['_PROXYERROR']._serialized_end=38911
- _globals['_PROXYERROR_KIND']._serialized_start=38759
- _globals['_PROXYERROR_KIND']._serialized_end=38911
- _globals['_TUNNELOPEN']._serialized_start=38914
- _globals['_TUNNELOPEN']._serialized_end=39359
- _globals['_TUNNELOPEN_METADATAENTRY']._serialized_start=6538
- _globals['_TUNNELOPEN_METADATAENTRY']._serialized_end=6585
- _globals['_TUNNELOPEN_PROTOCOL']._serialized_start=39316
- _globals['_TUNNELOPEN_PROTOCOL']._serialized_end=39359
- _globals['_TUNNELDATA']._serialized_start=39361
- _globals['_TUNNELDATA']._serialized_end=39432
- _globals['_TUNNELCLOSE']._serialized_start=39435
- _globals['_TUNNELCLOSE']._serialized_end=39608
- _globals['_TUNNELCLOSE_REASON']._serialized_start=39532
- _globals['_TUNNELCLOSE_REASON']._serialized_end=39608
- _globals['_TUNNELACK']._serialized_start=39610
- _globals['_TUNNELACK']._serialized_end=39674
- _globals['_RESOLVEAUTHORITYREQUEST']._serialized_start=39677
- _globals['_RESOLVEAUTHORITYREQUEST']._serialized_end=39866
- _globals['_RESOLVEAUTHORITYRESPONSE']._serialized_start=39868
- _globals['_RESOLVEAUTHORITYRESPONSE']._serialized_end=39990
- _globals['_RESOLVEDAUTHORITY']._serialized_start=39993
- _globals['_RESOLVEDAUTHORITY']._serialized_end=40140
- _globals['_AUTHORITYGRANTINFO']._serialized_start=40143
- _globals['_AUTHORITYGRANTINFO']._serialized_end=40407
- _globals['_CONNECTIONSTATUSREQUEST']._serialized_start=40409
- _globals['_CONNECTIONSTATUSREQUEST']._serialized_end=40498
- _globals['_CONNECTIONSTATUSRESPONSE']._serialized_start=40500
- _globals['_CONNECTIONSTATUSRESPONSE']._serialized_end=40614
- _globals['_TASKSUBSCRIPTIONOPERATION']._serialized_start=40617
- _globals['_TASKSUBSCRIPTIONOPERATION']._serialized_end=40902
- _globals['_TASKSUBSCRIPTIONOPERATION_OPTYPE']._serialized_start=40824
- _globals['_TASKSUBSCRIPTIONOPERATION_OPTYPE']._serialized_end=40902
- _globals['_TASKSUBSCRIPTIONOPERATIONRESPONSE']._serialized_start=40905
- _globals['_TASKSUBSCRIPTIONOPERATIONRESPONSE']._serialized_end=41041
- _globals['_TASKEVENT']._serialized_start=41044
- _globals['_TASKEVENT']._serialized_end=41423
- _globals['_TASKSTATUSCHANGEDEVENT']._serialized_start=41425
- _globals['_TASKSTATUSCHANGEDEVENT']._serialized_end=41551
- _globals['_TASKPROGRESSEVENT']._serialized_start=41554
- _globals['_TASKPROGRESSEVENT']._serialized_end=41734
- _globals['_TASKPROGRESSEVENT_METADATAENTRY']._serialized_start=6538
- _globals['_TASKPROGRESSEVENT_METADATAENTRY']._serialized_end=6585
- _globals['_TASKCHILDLIFECYCLEEVENT']._serialized_start=41736
- _globals['_TASKCHILDLIFECYCLEEVENT']._serialized_end=41848
- _globals['_TASKAUTHORITYREQUESTEVENTRELAY']._serialized_start=41850
- _globals['_TASKAUTHORITYREQUESTEVENTRELAY']._serialized_end=41931
- _globals['_AETHERGATEWAY']._serialized_start=44109
- _globals['_AETHERGATEWAY']._serialized_end=44197
+ _globals['_UPSTREAMMESSAGE']._serialized_end=1866
+ _globals['_DOWNSTREAMMESSAGE']._serialized_start=1869
+ _globals['_DOWNSTREAMMESSAGE']._serialized_end=4116
+ _globals['_TASKHIBERNATED']._serialized_start=4118
+ _globals['_TASKHIBERNATED']._serialized_end=4205
+ _globals['_CONNECTIONACK']._serialized_start=4208
+ _globals['_CONNECTIONACK']._serialized_end=4518
+ _globals['_INITCONNECTION']._serialized_start=4521
+ _globals['_INITCONNECTION']._serialized_end=5238
+ _globals['_INITCONNECTION_CREDENTIALSENTRY']._serialized_start=5173
+ _globals['_INITCONNECTION_CREDENTIALSENTRY']._serialized_end=5223
+ _globals['_BUILDINFO']._serialized_start=5240
+ _globals['_BUILDINFO']._serialized_end=5314
+ _globals['_EXTENSIONDECLARATION']._serialized_start=5316
+ _globals['_EXTENSIONDECLARATION']._serialized_end=5407
+ _globals['_NEGOTIATEDEXTENSION']._serialized_start=5409
+ _globals['_NEGOTIATEDEXTENSION']._serialized_end=5505
+ _globals['_WORKFLOWENGINEIDENTITY']._serialized_start=5507
+ _globals['_WORKFLOWENGINEIDENTITY']._serialized_end=5552
+ _globals['_METRICSBRIDGEIDENTITY']._serialized_start=5554
+ _globals['_METRICSBRIDGEIDENTITY']._serialized_end=5598
+ _globals['_ORCHESTRATORIDENTITY']._serialized_start=5600
+ _globals['_ORCHESTRATORIDENTITY']._serialized_end=5693
+ _globals['_BRIDGEIDENTITY']._serialized_start=5695
+ _globals['_BRIDGEIDENTITY']._serialized_end=5754
+ _globals['_SERVICEIDENTITY']._serialized_start=5756
+ _globals['_SERVICEIDENTITY']._serialized_end=5842
+ _globals['_AGENTIDENTITY']._serialized_start=5844
+ _globals['_AGENTIDENTITY']._serialized_end=5921
+ _globals['_TASKIDENTITY']._serialized_start=5923
+ _globals['_TASKIDENTITY']._serialized_end=6006
+ _globals['_USERIDENTITY']._serialized_start=6008
+ _globals['_USERIDENTITY']._serialized_end=6058
+ _globals['_PRINCIPALREF']._serialized_start=6060
+ _globals['_PRINCIPALREF']._serialized_end=6120
+ _globals['_AUTHORIZATIONCONTEXT']._serialized_start=6123
+ _globals['_AUTHORIZATIONCONTEXT']._serialized_end=6281
+ _globals['_RESOLVEDAUTHORITYINFO']._serialized_start=6284
+ _globals['_RESOLVEDAUTHORITYINFO']._serialized_end=6472
+ _globals['_SENDMESSAGE']._serialized_start=6475
+ _globals['_SENDMESSAGE']._serialized_end=6783
+ _globals['_AUTHORITYCONTINUATIONSCOPE']._serialized_start=6786
+ _globals['_AUTHORITYCONTINUATIONSCOPE']._serialized_end=6962
+ _globals['_AUTHORITYCONTINUATIONREQUEST']._serialized_start=6965
+ _globals['_AUTHORITYCONTINUATIONREQUEST']._serialized_end=7238
+ _globals['_AUTHORITYCONTINUATIONREQUEST_SCOPEMODE']._serialized_start=7142
+ _globals['_AUTHORITYCONTINUATIONREQUEST_SCOPEMODE']._serialized_end=7238
+ _globals['_METRIC']._serialized_start=7241
+ _globals['_METRIC']._serialized_end=7437
+ _globals['_METRIC_METADATAENTRY']._serialized_start=7390
+ _globals['_METRIC_METADATAENTRY']._serialized_end=7437
+ _globals['_METRICENTRY']._serialized_start=7439
+ _globals['_METRICENTRY']._serialized_end=7493
+ _globals['_SWITCHWORKSPACE']._serialized_start=7495
+ _globals['_SWITCHWORKSPACE']._serialized_end=7538
+ _globals['_KVOPERATION']._serialized_start=7541
+ _globals['_KVOPERATION']._serialized_end=8319
+ _globals['_KVOPERATION_OPTYPE']._serialized_start=7920
+ _globals['_KVOPERATION_OPTYPE']._serialized_end=8138
+ _globals['_KVOPERATION_SCOPE']._serialized_start=8141
+ _globals['_KVOPERATION_SCOPE']._serialized_end=8319
+ _globals['_KVRESPONSE']._serialized_start=8322
+ _globals['_KVRESPONSE']._serialized_end=8575
+ _globals['_KVRESPONSE_KVMAPENTRY']._serialized_start=8531
+ _globals['_KVRESPONSE_KVMAPENTRY']._serialized_end=8575
+ _globals['_INCOMINGMESSAGE']._serialized_start=8578
+ _globals['_INCOMINGMESSAGE']._serialized_end=8877
+ _globals['_FORWARDEDAUTHORIZATION']._serialized_start=8880
+ _globals['_FORWARDEDAUTHORIZATION']._serialized_end=9105
+ _globals['_CONFIGSNAPSHOT']._serialized_start=9108
+ _globals['_CONFIGSNAPSHOT']._serialized_end=9732
+ _globals['_CONFIGSNAPSHOT_KVENTRY']._serialized_start=9471
+ _globals['_CONFIGSNAPSHOT_KVENTRY']._serialized_end=9512
+ _globals['_CONFIGSNAPSHOT_GLOBALKVENTRY']._serialized_start=9514
+ _globals['_CONFIGSNAPSHOT_GLOBALKVENTRY']._serialized_end=9561
+ _globals['_CONFIGSNAPSHOT_TASKCONTEXTENTRY']._serialized_start=9563
+ _globals['_CONFIGSNAPSHOT_TASKCONTEXTENTRY']._serialized_end=9613
+ _globals['_CONFIGSNAPSHOT_WORKSPACEEXCLUSIVEKVENTRY']._serialized_start=9615
+ _globals['_CONFIGSNAPSHOT_WORKSPACEEXCLUSIVEKVENTRY']._serialized_end=9674
+ _globals['_CONFIGSNAPSHOT_GLOBALEXCLUSIVEKVENTRY']._serialized_start=9676
+ _globals['_CONFIGSNAPSHOT_GLOBALEXCLUSIVEKVENTRY']._serialized_end=9732
+ _globals['_SIGNAL']._serialized_start=9735
+ _globals['_SIGNAL']._serialized_end=9864
+ _globals['_SIGNAL_SIGNALTYPE']._serialized_start=9805
+ _globals['_SIGNAL_SIGNALTYPE']._serialized_end=9864
+ _globals['_ERRORRESPONSE']._serialized_start=9866
+ _globals['_ERRORRESPONSE']._serialized_end=9975
+ _globals['_RETRYPOLICY']._serialized_start=9978
+ _globals['_RETRYPOLICY']._serialized_end=10209
+ _globals['_TASKCOMPLETIONEVENT']._serialized_start=10211
+ _globals['_TASKCOMPLETIONEVENT']._serialized_end=10313
+ _globals['_CREATETASKREQUEST']._serialized_start=10316
+ _globals['_CREATETASKREQUEST']._serialized_end=11307
+ _globals['_CREATETASKREQUEST_LAUNCHPARAMOVERRIDESENTRY']._serialized_start=11199
+ _globals['_CREATETASKREQUEST_LAUNCHPARAMOVERRIDESENTRY']._serialized_end=11258
+ _globals['_CREATETASKREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_CREATETASKREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_CREATETASKRESPONSE']._serialized_start=11310
+ _globals['_CREATETASKRESPONSE']._serialized_end=11512
+ _globals['_TASKASSIGNMENT']._serialized_start=11515
+ _globals['_TASKASSIGNMENT']._serialized_end=12090
+ _globals['_TASKASSIGNMENT_METADATAENTRY']._serialized_start=7390
+ _globals['_TASKASSIGNMENT_METADATAENTRY']._serialized_end=7437
+ _globals['_TASKASSIGNMENT_LAUNCHPARAMSENTRY']._serialized_start=12039
+ _globals['_TASKASSIGNMENT_LAUNCHPARAMSENTRY']._serialized_end=12090
+ _globals['_CHECKPOINTOPERATION']._serialized_start=12093
+ _globals['_CHECKPOINTOPERATION']._serialized_end=12277
+ _globals['_CHECKPOINTOPERATION_OPTYPE']._serialized_start=12227
+ _globals['_CHECKPOINTOPERATION_OPTYPE']._serialized_end=12277
+ _globals['_CHECKPOINTRESPONSE']._serialized_start=12279
+ _globals['_CHECKPOINTRESPONSE']._serialized_end=12397
+ _globals['_ADMINQUERY']._serialized_start=12400
+ _globals['_ADMINQUERY']._serialized_end=12636
+ _globals['_ADMINQUERY_OPTYPE']._serialized_start=12541
+ _globals['_ADMINQUERY_OPTYPE']._serialized_end=12636
+ _globals['_CONNECTIONFILTER']._serialized_start=12638
+ _globals['_CONNECTIONFILTER']._serialized_end=12746
+ _globals['_CONNECTIONINFO']._serialized_start=12749
+ _globals['_CONNECTIONINFO']._serialized_end=12989
+ _globals['_ADMINRESPONSE']._serialized_start=12992
+ _globals['_ADMINRESPONSE']._serialized_end=13292
+ _globals['_HEALTHINFO']._serialized_start=13295
+ _globals['_HEALTHINFO']._serialized_end=13529
+ _globals['_HEALTHINFO_CHECKSENTRY']._serialized_start=13460
+ _globals['_HEALTHINFO_CHECKSENTRY']._serialized_end=13529
+ _globals['_HEALTHCHECK']._serialized_start=13531
+ _globals['_HEALTHCHECK']._serialized_end=13622
+ _globals['_GATEWAYINFO']._serialized_start=13625
+ _globals['_GATEWAYINFO']._serialized_end=13805
+ _globals['_GATEWAYSTATS']._serialized_start=13808
+ _globals['_GATEWAYSTATS']._serialized_end=14218
+ _globals['_SESSIONOPERATION']._serialized_start=14221
+ _globals['_SESSIONOPERATION']._serialized_end=14489
+ _globals['_SESSIONOPERATION_OPTYPE']._serialized_start=14446
+ _globals['_SESSIONOPERATION_OPTYPE']._serialized_end=14489
+ _globals['_SESSIONOPERATIONRESPONSE']._serialized_start=14492
+ _globals['_SESSIONOPERATIONRESPONSE']._serialized_end=14703
+ _globals['_TASKQUERY']._serialized_start=14706
+ _globals['_TASKQUERY']._serialized_end=14863
+ _globals['_TASKQUERY_OPTYPE']._serialized_start=14836
+ _globals['_TASKQUERY_OPTYPE']._serialized_end=14863
+ _globals['_TASKFILTER']._serialized_start=14866
+ _globals['_TASKFILTER']._serialized_end=15614
+ _globals['_TASKINFO']._serialized_start=15617
+ _globals['_TASKINFO']._serialized_end=16584
+ _globals['_TASKINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_TASKINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_TASKQUERYRESPONSE']._serialized_start=16587
+ _globals['_TASKQUERYRESPONSE']._serialized_end=16775
+ _globals['_TASKOPERATION']._serialized_start=16778
+ _globals['_TASKOPERATION']._serialized_end=17048
+ _globals['_TASKOPERATION_OPTYPE']._serialized_start=16933
+ _globals['_TASKOPERATION_OPTYPE']._serialized_end=17048
+ _globals['_WAITSPEC']._serialized_start=17051
+ _globals['_WAITSPEC']._serialized_end=17415
+ _globals['_WAITSPEC_INPUTMATCHENTRY']._serialized_start=17366
+ _globals['_WAITSPEC_INPUTMATCHENTRY']._serialized_end=17415
+ _globals['_HIBERNATIONDESCRIPTOR']._serialized_start=17417
+ _globals['_HIBERNATIONDESCRIPTOR']._serialized_end=17544
+ _globals['_TASKOPERATIONRESPONSE']._serialized_start=17546
+ _globals['_TASKOPERATIONRESPONSE']._serialized_end=17673
+ _globals['_WORKSPACEOPERATION']._serialized_start=17676
+ _globals['_WORKSPACEOPERATION']._serialized_end=17964
+ _globals['_WORKSPACEOPERATION_OPTYPE']._serialized_start=17879
+ _globals['_WORKSPACEOPERATION_OPTYPE']._serialized_end=17964
+ _globals['_WORKSPACEFILTER']._serialized_start=17966
+ _globals['_WORKSPACEFILTER']._serialized_end=18033
+ _globals['_WORKSPACEINFO']._serialized_start=18036
+ _globals['_WORKSPACEINFO']._serialized_end=18373
+ _globals['_WORKSPACEINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_WORKSPACEINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_WORKSPACERESPONSE']._serialized_start=18376
+ _globals['_WORKSPACERESPONSE']._serialized_end=18626
+ _globals['_MESSAGEFLOWINFO']._serialized_start=18629
+ _globals['_MESSAGEFLOWINFO']._serialized_end=18760
+ _globals['_FLOWNODE']._serialized_start=18763
+ _globals['_FLOWNODE']._serialized_end=18914
+ _globals['_FLOWEDGE']._serialized_start=18916
+ _globals['_FLOWEDGE']._serialized_end=18982
+ _globals['_AGENTOPERATION']._serialized_start=18985
+ _globals['_AGENTOPERATION']._serialized_end=19336
+ _globals['_AGENTOPERATION_OPTYPE']._serialized_start=19235
+ _globals['_AGENTOPERATION_OPTYPE']._serialized_end=19336
+ _globals['_AGENTFILTER']._serialized_start=19338
+ _globals['_AGENTFILTER']._serialized_end=19412
+ _globals['_AGENTREGISTRATIONINFO']._serialized_start=19415
+ _globals['_AGENTREGISTRATIONINFO']._serialized_end=19893
+ _globals['_AGENTREGISTRATIONINFO_LAUNCHPARAMSENTRY']._serialized_start=12039
+ _globals['_AGENTREGISTRATIONINFO_LAUNCHPARAMSENTRY']._serialized_end=12090
+ _globals['_AGENTREGISTRATIONINFO_CAPABILITIESENTRY']._serialized_start=19842
+ _globals['_AGENTREGISTRATIONINFO_CAPABILITIESENTRY']._serialized_end=19893
+ _globals['_AGENTRESOURCESCHEMAENTRY']._serialized_start=19895
+ _globals['_AGENTRESOURCESCHEMAENTRY']._serialized_end=20005
+ _globals['_AGENTLAUNCHPARAMS']._serialized_start=20008
+ _globals['_AGENTLAUNCHPARAMS']._serialized_end=20195
+ _globals['_AGENTLAUNCHPARAMS_PARAMOVERRIDESENTRY']._serialized_start=20142
+ _globals['_AGENTLAUNCHPARAMS_PARAMOVERRIDESENTRY']._serialized_end=20195
+ _globals['_ORCHESTRATORINFO']._serialized_start=20197
+ _globals['_ORCHESTRATORINFO']._serialized_end=20280
+ _globals['_AGENTLAUNCHRESULT']._serialized_start=20282
+ _globals['_AGENTLAUNCHRESULT']._serialized_end=20335
+ _globals['_AGENTRESPONSE']._serialized_start=20338
+ _globals['_AGENTRESPONSE']._serialized_end=20647
+ _globals['_ACLOPERATION']._serialized_start=20650
+ _globals['_ACLOPERATION']._serialized_end=22230
+ _globals['_ACLOPERATION_OPTYPE']._serialized_start=21407
+ _globals['_ACLOPERATION_OPTYPE']._serialized_end=22082
+ _globals['_ACLRULEFILTER']._serialized_start=22233
+ _globals['_ACLRULEFILTER']._serialized_end=22369
+ _globals['_ACLAUDITFILTER']._serialized_start=22372
+ _globals['_ACLAUDITFILTER']._serialized_end=22584
+ _globals['_ACLGRANTREQUEST']._serialized_start=22587
+ _globals['_ACLGRANTREQUEST']._serialized_end=22772
+ _globals['_ACLSETFALLBACKREQUEST']._serialized_start=22774
+ _globals['_ACLSETFALLBACKREQUEST']._serialized_end=22871
+ _globals['_ACLAUTHORITYGRANTFILTER']._serialized_start=22874
+ _globals['_ACLAUTHORITYGRANTFILTER']._serialized_end=23129
+ _globals['_ACLAUTHORITYGRANTRESOURCESCOPEENTRY']._serialized_start=23131
+ _globals['_ACLAUTHORITYGRANTRESOURCESCOPEENTRY']._serialized_end=23209
+ _globals['_ACLAUTHORITYGRANTREQUEST']._serialized_start=23212
+ _globals['_ACLAUTHORITYGRANTREQUEST']._serialized_end=23893
+ _globals['_ACLAUTHORITYGRANTREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLAUTHORITYGRANTREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLRENEWAUTHORITYGRANTREQUEST']._serialized_start=23895
+ _globals['_ACLRENEWAUTHORITYGRANTREQUEST']._serialized_end=23988
+ _globals['_ACLRULEINFO']._serialized_start=23991
+ _globals['_ACLRULEINFO']._serialized_end=24236
+ _globals['_ACLFALLBACKPOLICYINFO']._serialized_start=24239
+ _globals['_ACLFALLBACKPOLICYINFO']._serialized_end=24411
+ _globals['_ACLAUDITENTRYINFO']._serialized_start=24414
+ _globals['_ACLAUDITENTRYINFO']._serialized_end=24865
+ _globals['_ACLAUDITENTRYINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLAUDITENTRYINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLAUTHORITYGRANTINFO']._serialized_start=24868
+ _globals['_ACLAUTHORITYGRANTINFO']._serialized_end=25688
+ _globals['_ACLAUTHORITYGRANTINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLAUTHORITYGRANTINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLCLEANUPRESULT']._serialized_start=25690
+ _globals['_ACLCLEANUPRESULT']._serialized_end=25748
+ _globals['_ACLGROUPREQUEST']._serialized_start=25751
+ _globals['_ACLGROUPREQUEST']._serialized_end=25932
+ _globals['_ACLGROUPREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLGROUPREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLROLEREQUEST']._serialized_start=25935
+ _globals['_ACLROLEREQUEST']._serialized_end=26114
+ _globals['_ACLROLEREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLROLEREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLGROUPMEMBERREQUEST']._serialized_start=26116
+ _globals['_ACLGROUPMEMBERREQUEST']._serialized_end=26219
+ _globals['_ACLROLEASSIGNMENTREQUEST']._serialized_start=26221
+ _globals['_ACLROLEASSIGNMENTREQUEST']._serialized_end=26331
+ _globals['_ACLGROUPINFO']._serialized_start=26334
+ _globals['_ACLGROUPINFO']._serialized_end=26553
+ _globals['_ACLGROUPINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLGROUPINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLROLEINFO']._serialized_start=26556
+ _globals['_ACLROLEINFO']._serialized_end=26771
+ _globals['_ACLROLEINFO_METADATAENTRY']._serialized_start=7390
+ _globals['_ACLROLEINFO_METADATAENTRY']._serialized_end=7437
+ _globals['_ACLGROUPMEMBERINFO']._serialized_start=26774
+ _globals['_ACLGROUPMEMBERINFO']._serialized_end=26914
+ _globals['_ACLROLEASSIGNMENTINFO']._serialized_start=26917
+ _globals['_ACLROLEASSIGNMENTINFO']._serialized_end=27063
+ _globals['_ACLACCESSCONTRIBUTIONINFO']._serialized_start=27065
+ _globals['_ACLACCESSCONTRIBUTIONINFO']._serialized_end=27183
+ _globals['_ACLACCESSEXPLANATIONINFO']._serialized_start=27186
+ _globals['_ACLACCESSEXPLANATIONINFO']._serialized_end=27419
+ _globals['_ACLRESPONSE']._serialized_start=27422
+ _globals['_ACLRESPONSE']._serialized_end=28283
+ _globals['_AUTHORITYGRANTOPERATION']._serialized_start=28286
+ _globals['_AUTHORITYGRANTOPERATION']._serialized_end=29009
+ _globals['_AUTHORITYGRANTOPERATION_OPTYPE']._serialized_start=28857
+ _globals['_AUTHORITYGRANTOPERATION_OPTYPE']._serialized_end=29009
+ _globals['_AUTHORITYGRANTEXCHANGEREQUEST']._serialized_start=29012
+ _globals['_AUTHORITYGRANTEXCHANGEREQUEST']._serialized_end=29529
+ _globals['_AUTHORITYGRANTEXCHANGEREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_AUTHORITYGRANTEXCHANGEREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_AUTHORITYGRANTDERIVEREQUEST']._serialized_start=29532
+ _globals['_AUTHORITYGRANTDERIVEREQUEST']._serialized_end=30086
+ _globals['_AUTHORITYGRANTDERIVEREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_AUTHORITYGRANTDERIVEREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_AUTHORITYGRANTRESPONSE']._serialized_start=30089
+ _globals['_AUTHORITYGRANTRESPONSE']._serialized_end=30328
+ _globals['_AUTHORITYGRANTLISTREQUEST']._serialized_start=30330
+ _globals['_AUTHORITYGRANTLISTREQUEST']._serialized_end=30457
+ _globals['_AUTHORITYGRANTBATCHEXCHANGEREQUEST']._serialized_start=30459
+ _globals['_AUTHORITYGRANTBATCHEXCHANGEREQUEST']._serialized_end=30584
+ _globals['_AUTHORITYGRANTDERIVEFORTARGETREQUEST']._serialized_start=30587
+ _globals['_AUTHORITYGRANTDERIVEFORTARGETREQUEST']._serialized_end=30893
+ _globals['_AUTHORITYIDENTITY']._serialized_start=30896
+ _globals['_AUTHORITYIDENTITY']._serialized_end=31091
+ _globals['_AUTHORITYSPAN']._serialized_start=31094
+ _globals['_AUTHORITYSPAN']._serialized_end=31303
+ _globals['_AUTHORITYGRANTREVOCATION']._serialized_start=31305
+ _globals['_AUTHORITYGRANTREVOCATION']._serialized_end=31425
+ _globals['_AUTHORITYREQUESTROUTINGTARGET']._serialized_start=31427
+ _globals['_AUTHORITYREQUESTROUTINGTARGET']._serialized_end=31522
+ _globals['_AUTHORITYREQUESTRESOURCESCOPEENTRY']._serialized_start=31524
+ _globals['_AUTHORITYREQUESTRESOURCESCOPEENTRY']._serialized_end=31601
+ _globals['_AUTHORITYREQUEST']._serialized_start=31604
+ _globals['_AUTHORITYREQUEST']._serialized_end=32443
+ _globals['_AUTHORITYREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_AUTHORITYREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_CREATEAUTHORITYREQUESTPAYLOAD']._serialized_start=32446
+ _globals['_CREATEAUTHORITYREQUESTPAYLOAD']._serialized_end=33080
+ _globals['_CREATEAUTHORITYREQUESTPAYLOAD_METADATAENTRY']._serialized_start=7390
+ _globals['_CREATEAUTHORITYREQUESTPAYLOAD_METADATAENTRY']._serialized_end=7437
+ _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD']._serialized_start=33083
+ _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD']._serialized_end=33541
+ _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD_DECISION']._serialized_start=33482
+ _globals['_RESOLVEAUTHORITYREQUESTPAYLOAD_DECISION']._serialized_end=33541
+ _globals['_AUTHORITYREQUESTLISTFILTER']._serialized_start=33544
+ _globals['_AUTHORITYREQUESTLISTFILTER']._serialized_end=33704
+ _globals['_AUTHORITYREQUESTOPERATION']._serialized_start=33707
+ _globals['_AUTHORITYREQUESTOPERATION']._serialized_end=34144
+ _globals['_AUTHORITYREQUESTOPERATION_OPTYPE']._serialized_start=34034
+ _globals['_AUTHORITYREQUESTOPERATION_OPTYPE']._serialized_end=34144
+ _globals['_AUTHORITYREQUESTOPERATIONRESPONSE']._serialized_start=34147
+ _globals['_AUTHORITYREQUESTOPERATIONRESPONSE']._serialized_end=34355
+ _globals['_AUTHORITYREQUESTEVENT']._serialized_start=34358
+ _globals['_AUTHORITYREQUESTEVENT']._serialized_end=34753
+ _globals['_AUTHORITYREQUESTEVENT_EVENTTYPE']._serialized_start=34514
+ _globals['_AUTHORITYREQUESTEVENT_EVENTTYPE']._serialized_end=34753
+ _globals['_TOKENOPERATION']._serialized_start=34756
+ _globals['_TOKENOPERATION']._serialized_end=35016
+ _globals['_TOKENOPERATION_OPTYPE']._serialized_start=34953
+ _globals['_TOKENOPERATION_OPTYPE']._serialized_end=35016
+ _globals['_TOKENCREATEREQUEST']._serialized_start=35019
+ _globals['_TOKENCREATEREQUEST']._serialized_end=35167
+ _globals['_TOKENFILTER']._serialized_start=35169
+ _globals['_TOKENFILTER']._serialized_end=35238
+ _globals['_TOKENINFO']._serialized_start=35241
+ _globals['_TOKENINFO']._serialized_end=35485
+ _globals['_TOKENRESPONSE']._serialized_start=35488
+ _globals['_TOKENRESPONSE']._serialized_end=35738
+ _globals['_PROGRESSREPORT']._serialized_start=35741
+ _globals['_PROGRESSREPORT']._serialized_end=36051
+ _globals['_PROGRESSREPORT_METADATAENTRY']._serialized_start=7390
+ _globals['_PROGRESSREPORT_METADATAENTRY']._serialized_end=7437
+ _globals['_PROGRESSSTEP']._serialized_start=36053
+ _globals['_PROGRESSSTEP']._serialized_end=36155
+ _globals['_PROGRESSUPDATE']._serialized_start=36158
+ _globals['_PROGRESSUPDATE']._serialized_end=36525
+ _globals['_PROGRESSUPDATE_METADATAENTRY']._serialized_start=7390
+ _globals['_PROGRESSUPDATE_METADATAENTRY']._serialized_end=7437
+ _globals['_WORKFLOWSCHEDULEAUTHORITYSCOPE']._serialized_start=36528
+ _globals['_WORKFLOWSCHEDULEAUTHORITYSCOPE']._serialized_end=36880
+ _globals['_WORKFLOWREQUESTCONTEXT']._serialized_start=36883
+ _globals['_WORKFLOWREQUESTCONTEXT']._serialized_end=37263
+ _globals['_WORKFLOWOPERATION']._serialized_start=37266
+ _globals['_WORKFLOWOPERATION']._serialized_end=38188
+ _globals['_WORKFLOWOPERATION_OPTYPE']._serialized_start=37640
+ _globals['_WORKFLOWOPERATION_OPTYPE']._serialized_end=38188
+ _globals['_WORKFLOWRESPONSE']._serialized_start=38190
+ _globals['_WORKFLOWRESPONSE']._serialized_end=38312
+ _globals['_MESSAGEENVELOPE']._serialized_start=38315
+ _globals['_MESSAGEENVELOPE']._serialized_end=38739
+ _globals['_MESSAGEENVELOPE_METADATAENTRY']._serialized_start=7390
+ _globals['_MESSAGEENVELOPE_METADATAENTRY']._serialized_end=7437
+ _globals['_AUDITQUERY']._serialized_start=38742
+ _globals['_AUDITQUERY']._serialized_end=39245
+ _globals['_AUDITQUERYRESPONSE']._serialized_start=39248
+ _globals['_AUDITQUERYRESPONSE']._serialized_end=39381
+ _globals['_AUDITENTRY']._serialized_start=39384
+ _globals['_AUDITENTRY']._serialized_end=39906
+ _globals['_SUBMITAUDITEVENTREQUEST']._serialized_start=39909
+ _globals['_SUBMITAUDITEVENTREQUEST']._serialized_end=40220
+ _globals['_SUBMITAUDITEVENTREQUEST_METADATAENTRY']._serialized_start=7390
+ _globals['_SUBMITAUDITEVENTREQUEST_METADATAENTRY']._serialized_end=7437
+ _globals['_SUBMITAUDITEVENTRESPONSE']._serialized_start=40222
+ _globals['_SUBMITAUDITEVENTRESPONSE']._serialized_end=40335
+ _globals['_PROXYHTTPREQUEST']._serialized_start=40338
+ _globals['_PROXYHTTPREQUEST']._serialized_end=40964
+ _globals['_PROXYHTTPREQUEST_HEADERSENTRY']._serialized_start=40918
+ _globals['_PROXYHTTPREQUEST_HEADERSENTRY']._serialized_end=40964
+ _globals['_PROXYHTTPRESPONSE']._serialized_start=40967
+ _globals['_PROXYHTTPRESPONSE']._serialized_end=41209
+ _globals['_PROXYHTTPRESPONSE_HEADERSENTRY']._serialized_start=40918
+ _globals['_PROXYHTTPRESPONSE_HEADERSENTRY']._serialized_end=40964
+ _globals['_PROXYHTTPBODYCHUNK']._serialized_start=41211
+ _globals['_PROXYHTTPBODYCHUNK']._serialized_end=41311
+ _globals['_PROXYERROR']._serialized_start=41314
+ _globals['_PROXYERROR']._serialized_end=41540
+ _globals['_PROXYERROR_KIND']._serialized_start=41388
+ _globals['_PROXYERROR_KIND']._serialized_end=41540
+ _globals['_TUNNELOPEN']._serialized_start=41543
+ _globals['_TUNNELOPEN']._serialized_end=41988
+ _globals['_TUNNELOPEN_METADATAENTRY']._serialized_start=7390
+ _globals['_TUNNELOPEN_METADATAENTRY']._serialized_end=7437
+ _globals['_TUNNELOPEN_PROTOCOL']._serialized_start=41945
+ _globals['_TUNNELOPEN_PROTOCOL']._serialized_end=41988
+ _globals['_TUNNELDATA']._serialized_start=41990
+ _globals['_TUNNELDATA']._serialized_end=42061
+ _globals['_TUNNELCLOSE']._serialized_start=42064
+ _globals['_TUNNELCLOSE']._serialized_end=42237
+ _globals['_TUNNELCLOSE_REASON']._serialized_start=42161
+ _globals['_TUNNELCLOSE_REASON']._serialized_end=42237
+ _globals['_TUNNELACK']._serialized_start=42239
+ _globals['_TUNNELACK']._serialized_end=42303
+ _globals['_RESOLVEAUTHORITYREQUEST']._serialized_start=42306
+ _globals['_RESOLVEAUTHORITYREQUEST']._serialized_end=42495
+ _globals['_RESOLVEAUTHORITYRESPONSE']._serialized_start=42497
+ _globals['_RESOLVEAUTHORITYRESPONSE']._serialized_end=42619
+ _globals['_RESOLVEDAUTHORITY']._serialized_start=42622
+ _globals['_RESOLVEDAUTHORITY']._serialized_end=42769
+ _globals['_AUTHORITYGRANTINFO']._serialized_start=42772
+ _globals['_AUTHORITYGRANTINFO']._serialized_end=43036
+ _globals['_CONNECTIONSTATUSREQUEST']._serialized_start=43038
+ _globals['_CONNECTIONSTATUSREQUEST']._serialized_end=43127
+ _globals['_CONNECTIONSTATUSRESPONSE']._serialized_start=43129
+ _globals['_CONNECTIONSTATUSRESPONSE']._serialized_end=43243
+ _globals['_TASKSUBSCRIPTIONOPERATION']._serialized_start=43246
+ _globals['_TASKSUBSCRIPTIONOPERATION']._serialized_end=43531
+ _globals['_TASKSUBSCRIPTIONOPERATION_OPTYPE']._serialized_start=43453
+ _globals['_TASKSUBSCRIPTIONOPERATION_OPTYPE']._serialized_end=43531
+ _globals['_TASKSUBSCRIPTIONOPERATIONRESPONSE']._serialized_start=43534
+ _globals['_TASKSUBSCRIPTIONOPERATIONRESPONSE']._serialized_end=43670
+ _globals['_TASKEVENT']._serialized_start=43673
+ _globals['_TASKEVENT']._serialized_end=44052
+ _globals['_TASKSTATUSCHANGEDEVENT']._serialized_start=44054
+ _globals['_TASKSTATUSCHANGEDEVENT']._serialized_end=44180
+ _globals['_TASKPROGRESSEVENT']._serialized_start=44183
+ _globals['_TASKPROGRESSEVENT']._serialized_end=44363
+ _globals['_TASKPROGRESSEVENT_METADATAENTRY']._serialized_start=7390
+ _globals['_TASKPROGRESSEVENT_METADATAENTRY']._serialized_end=7437
+ _globals['_TASKCHILDLIFECYCLEEVENT']._serialized_start=44365
+ _globals['_TASKCHILDLIFECYCLEEVENT']._serialized_end=44477
+ _globals['_TASKAUTHORITYREQUESTEVENTRELAY']._serialized_start=44479
+ _globals['_TASKAUTHORITYREQUESTEVENTRELAY']._serialized_end=44560
+ _globals['_RESOURCEACCESSREQUEST']._serialized_start=44563
+ _globals['_RESOURCEACCESSREQUEST']._serialized_end=44723
+ _globals['_ACCESSDECISIONRECEIPT']._serialized_start=44726
+ _globals['_ACCESSDECISIONRECEIPT']._serialized_end=45176
+ _globals['_ACCESSCHECKOPERATION']._serialized_start=45179
+ _globals['_ACCESSCHECKOPERATION']._serialized_end=45327
+ _globals['_ACCESSCHECKRESPONSE']._serialized_start=45329
+ _globals['_ACCESSCHECKRESPONSE']._serialized_end=45454
+ _globals['_BATCHACCESSCHECKOPERATION']._serialized_start=45457
+ _globals['_BATCHACCESSCHECKOPERATION']._serialized_end=45610
+ _globals['_BATCHACCESSCHECKRESPONSE']._serialized_start=45613
+ _globals['_BATCHACCESSCHECKRESPONSE']._serialized_end=45744
+ _globals['_AETHERGATEWAY']._serialized_start=48211
+ _globals['_AETHERGATEWAY']._serialized_end=48299
# @@protoc_insertion_point(module_scope)
diff --git a/sdk/python-client/scitrera_aether_client/proto/aether_pb2.pyi b/sdk/python-client/scitrera_aether_client/proto/aether_pb2.pyi
index 447ac35..0973342 100644
--- a/sdk/python-client/scitrera_aether_client/proto/aether_pb2.pyi
+++ b/sdk/python-client/scitrera_aether_client/proto/aether_pb2.pyi
@@ -95,6 +95,13 @@ class BackoffStrategy(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
BACKOFF_STRATEGY_EXPONENTIAL: _ClassVar[BackoffStrategy]
BACKOFF_STRATEGY_EXPLICIT_SCHEDULE: _ClassVar[BackoffStrategy]
+class TargetOfflinePolicy(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
+ __slots__ = ()
+ TARGET_OFFLINE_POLICY_UNSPECIFIED: _ClassVar[TargetOfflinePolicy]
+ TARGET_OFFLINE_POLICY_ORCHESTRATE: _ClassVar[TargetOfflinePolicy]
+ TARGET_OFFLINE_POLICY_QUEUE: _ClassVar[TargetOfflinePolicy]
+ TARGET_OFFLINE_POLICY_REJECT: _ClassVar[TargetOfflinePolicy]
+
class WaitReason(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
WAIT_REASON_UNSPECIFIED: _ClassVar[WaitReason]
@@ -118,6 +125,11 @@ class ProgressKind(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
PROGRESS_KIND_CHAT: _ClassVar[ProgressKind]
PROGRESS_KIND_APP: _ClassVar[ProgressKind]
PROGRESS_KIND_TASK: _ClassVar[ProgressKind]
+
+class WorkflowAuthorityLifetimeMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
+ __slots__ = ()
+ WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND: _ClassVar[WorkflowAuthorityLifetimeMode]
+ WORKFLOW_AUTHORITY_LIFETIME_DURABLE: _ClassVar[WorkflowAuthorityLifetimeMode]
MESSAGE_TYPE_UNSPECIFIED: MessageType
CHAT: MessageType
CONTROL: MessageType
@@ -176,6 +188,10 @@ BACKOFF_STRATEGY_UNSPECIFIED: BackoffStrategy
BACKOFF_STRATEGY_FIXED: BackoffStrategy
BACKOFF_STRATEGY_EXPONENTIAL: BackoffStrategy
BACKOFF_STRATEGY_EXPLICIT_SCHEDULE: BackoffStrategy
+TARGET_OFFLINE_POLICY_UNSPECIFIED: TargetOfflinePolicy
+TARGET_OFFLINE_POLICY_ORCHESTRATE: TargetOfflinePolicy
+TARGET_OFFLINE_POLICY_QUEUE: TargetOfflinePolicy
+TARGET_OFFLINE_POLICY_REJECT: TargetOfflinePolicy
WAIT_REASON_UNSPECIFIED: WaitReason
WAIT_REASON_INPUT: WaitReason
WAIT_REASON_AUTHORITY: WaitReason
@@ -191,9 +207,11 @@ PROGRESS_KIND_UNSPECIFIED: ProgressKind
PROGRESS_KIND_CHAT: ProgressKind
PROGRESS_KIND_APP: ProgressKind
PROGRESS_KIND_TASK: ProgressKind
+WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND: WorkflowAuthorityLifetimeMode
+WORKFLOW_AUTHORITY_LIFETIME_DURABLE: WorkflowAuthorityLifetimeMode
class UpstreamMessage(_message.Message):
- __slots__ = ("init", "send", "switch_workspace", "kv_op", "create_task", "checkpoint_op", "admin_query", "session_op", "task_query", "task_op", "workspace_op", "agent_op", "acl_op", "progress", "workflow_op", "workflow_response", "token_op", "audit_query", "authority_grant_op", "proxy_http_request", "proxy_http_body_chunk", "tunnel_open", "tunnel_data", "tunnel_close", "proxy_http_response", "tunnel_ack", "resolve_authority_request", "connection_status_request", "submit_audit_event", "authority_request_op", "task_subscription_op", "active_extensions")
+ __slots__ = ("init", "send", "switch_workspace", "kv_op", "create_task", "checkpoint_op", "admin_query", "session_op", "task_query", "task_op", "workspace_op", "agent_op", "acl_op", "progress", "workflow_op", "workflow_response", "token_op", "audit_query", "authority_grant_op", "proxy_http_request", "proxy_http_body_chunk", "tunnel_open", "tunnel_data", "tunnel_close", "proxy_http_response", "tunnel_ack", "resolve_authority_request", "connection_status_request", "submit_audit_event", "authority_request_op", "task_subscription_op", "access_check", "batch_access_check", "active_extensions")
INIT_FIELD_NUMBER: _ClassVar[int]
SEND_FIELD_NUMBER: _ClassVar[int]
SWITCH_WORKSPACE_FIELD_NUMBER: _ClassVar[int]
@@ -225,6 +243,8 @@ class UpstreamMessage(_message.Message):
SUBMIT_AUDIT_EVENT_FIELD_NUMBER: _ClassVar[int]
AUTHORITY_REQUEST_OP_FIELD_NUMBER: _ClassVar[int]
TASK_SUBSCRIPTION_OP_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_CHECK_FIELD_NUMBER: _ClassVar[int]
+ BATCH_ACCESS_CHECK_FIELD_NUMBER: _ClassVar[int]
ACTIVE_EXTENSIONS_FIELD_NUMBER: _ClassVar[int]
init: InitConnection
send: SendMessage
@@ -257,11 +277,13 @@ class UpstreamMessage(_message.Message):
submit_audit_event: SubmitAuditEventRequest
authority_request_op: AuthorityRequestOperation
task_subscription_op: TaskSubscriptionOperation
+ access_check: AccessCheckOperation
+ batch_access_check: BatchAccessCheckOperation
active_extensions: _containers.RepeatedScalarFieldContainer[str]
- def __init__(self, init: _Optional[_Union[InitConnection, _Mapping]] = ..., send: _Optional[_Union[SendMessage, _Mapping]] = ..., switch_workspace: _Optional[_Union[SwitchWorkspace, _Mapping]] = ..., kv_op: _Optional[_Union[KVOperation, _Mapping]] = ..., create_task: _Optional[_Union[CreateTaskRequest, _Mapping]] = ..., checkpoint_op: _Optional[_Union[CheckpointOperation, _Mapping]] = ..., admin_query: _Optional[_Union[AdminQuery, _Mapping]] = ..., session_op: _Optional[_Union[SessionOperation, _Mapping]] = ..., task_query: _Optional[_Union[TaskQuery, _Mapping]] = ..., task_op: _Optional[_Union[TaskOperation, _Mapping]] = ..., workspace_op: _Optional[_Union[WorkspaceOperation, _Mapping]] = ..., agent_op: _Optional[_Union[AgentOperation, _Mapping]] = ..., acl_op: _Optional[_Union[ACLOperation, _Mapping]] = ..., progress: _Optional[_Union[ProgressReport, _Mapping]] = ..., workflow_op: _Optional[_Union[WorkflowOperation, _Mapping]] = ..., workflow_response: _Optional[_Union[WorkflowResponse, _Mapping]] = ..., token_op: _Optional[_Union[TokenOperation, _Mapping]] = ..., audit_query: _Optional[_Union[AuditQuery, _Mapping]] = ..., authority_grant_op: _Optional[_Union[AuthorityGrantOperation, _Mapping]] = ..., proxy_http_request: _Optional[_Union[ProxyHttpRequest, _Mapping]] = ..., proxy_http_body_chunk: _Optional[_Union[ProxyHttpBodyChunk, _Mapping]] = ..., tunnel_open: _Optional[_Union[TunnelOpen, _Mapping]] = ..., tunnel_data: _Optional[_Union[TunnelData, _Mapping]] = ..., tunnel_close: _Optional[_Union[TunnelClose, _Mapping]] = ..., proxy_http_response: _Optional[_Union[ProxyHttpResponse, _Mapping]] = ..., tunnel_ack: _Optional[_Union[TunnelAck, _Mapping]] = ..., resolve_authority_request: _Optional[_Union[ResolveAuthorityRequest, _Mapping]] = ..., connection_status_request: _Optional[_Union[ConnectionStatusRequest, _Mapping]] = ..., submit_audit_event: _Optional[_Union[SubmitAuditEventRequest, _Mapping]] = ..., authority_request_op: _Optional[_Union[AuthorityRequestOperation, _Mapping]] = ..., task_subscription_op: _Optional[_Union[TaskSubscriptionOperation, _Mapping]] = ..., active_extensions: _Optional[_Iterable[str]] = ...) -> None: ...
+ def __init__(self, init: _Optional[_Union[InitConnection, _Mapping]] = ..., send: _Optional[_Union[SendMessage, _Mapping]] = ..., switch_workspace: _Optional[_Union[SwitchWorkspace, _Mapping]] = ..., kv_op: _Optional[_Union[KVOperation, _Mapping]] = ..., create_task: _Optional[_Union[CreateTaskRequest, _Mapping]] = ..., checkpoint_op: _Optional[_Union[CheckpointOperation, _Mapping]] = ..., admin_query: _Optional[_Union[AdminQuery, _Mapping]] = ..., session_op: _Optional[_Union[SessionOperation, _Mapping]] = ..., task_query: _Optional[_Union[TaskQuery, _Mapping]] = ..., task_op: _Optional[_Union[TaskOperation, _Mapping]] = ..., workspace_op: _Optional[_Union[WorkspaceOperation, _Mapping]] = ..., agent_op: _Optional[_Union[AgentOperation, _Mapping]] = ..., acl_op: _Optional[_Union[ACLOperation, _Mapping]] = ..., progress: _Optional[_Union[ProgressReport, _Mapping]] = ..., workflow_op: _Optional[_Union[WorkflowOperation, _Mapping]] = ..., workflow_response: _Optional[_Union[WorkflowResponse, _Mapping]] = ..., token_op: _Optional[_Union[TokenOperation, _Mapping]] = ..., audit_query: _Optional[_Union[AuditQuery, _Mapping]] = ..., authority_grant_op: _Optional[_Union[AuthorityGrantOperation, _Mapping]] = ..., proxy_http_request: _Optional[_Union[ProxyHttpRequest, _Mapping]] = ..., proxy_http_body_chunk: _Optional[_Union[ProxyHttpBodyChunk, _Mapping]] = ..., tunnel_open: _Optional[_Union[TunnelOpen, _Mapping]] = ..., tunnel_data: _Optional[_Union[TunnelData, _Mapping]] = ..., tunnel_close: _Optional[_Union[TunnelClose, _Mapping]] = ..., proxy_http_response: _Optional[_Union[ProxyHttpResponse, _Mapping]] = ..., tunnel_ack: _Optional[_Union[TunnelAck, _Mapping]] = ..., resolve_authority_request: _Optional[_Union[ResolveAuthorityRequest, _Mapping]] = ..., connection_status_request: _Optional[_Union[ConnectionStatusRequest, _Mapping]] = ..., submit_audit_event: _Optional[_Union[SubmitAuditEventRequest, _Mapping]] = ..., authority_request_op: _Optional[_Union[AuthorityRequestOperation, _Mapping]] = ..., task_subscription_op: _Optional[_Union[TaskSubscriptionOperation, _Mapping]] = ..., access_check: _Optional[_Union[AccessCheckOperation, _Mapping]] = ..., batch_access_check: _Optional[_Union[BatchAccessCheckOperation, _Mapping]] = ..., active_extensions: _Optional[_Iterable[str]] = ...) -> None: ...
class DownstreamMessage(_message.Message):
- __slots__ = ("msg", "config", "signal", "error", "kv", "task_assignment", "connection_ack", "checkpoint", "admin", "session_response", "task_query", "task_op", "workspace", "agent", "acl", "progress_update", "workflow_response", "workflow_op", "token", "audit_response", "authority_grant", "create_task", "proxy_http_response", "proxy_http_body_chunk", "tunnel_ack", "tunnel_close", "tunnel_data", "proxy_http_request", "resolve_authority_response", "connection_status_response", "authority_grant_revocation", "submit_audit_event_response", "authority_request_response", "authority_request_event", "task_hibernated", "task_subscription_response", "task_event", "active_extensions")
+ __slots__ = ("msg", "config", "signal", "error", "kv", "task_assignment", "connection_ack", "checkpoint", "admin", "session_response", "task_query", "task_op", "workspace", "agent", "acl", "progress_update", "workflow_response", "workflow_op", "token", "audit_response", "authority_grant", "create_task", "proxy_http_response", "proxy_http_body_chunk", "tunnel_ack", "tunnel_close", "tunnel_data", "proxy_http_request", "resolve_authority_response", "connection_status_response", "authority_grant_revocation", "submit_audit_event_response", "authority_request_response", "authority_request_event", "task_hibernated", "task_subscription_response", "task_event", "access_check_response", "batch_access_check_response", "active_extensions")
MSG_FIELD_NUMBER: _ClassVar[int]
CONFIG_FIELD_NUMBER: _ClassVar[int]
SIGNAL_FIELD_NUMBER: _ClassVar[int]
@@ -299,6 +321,8 @@ class DownstreamMessage(_message.Message):
TASK_HIBERNATED_FIELD_NUMBER: _ClassVar[int]
TASK_SUBSCRIPTION_RESPONSE_FIELD_NUMBER: _ClassVar[int]
TASK_EVENT_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_CHECK_RESPONSE_FIELD_NUMBER: _ClassVar[int]
+ BATCH_ACCESS_CHECK_RESPONSE_FIELD_NUMBER: _ClassVar[int]
ACTIVE_EXTENSIONS_FIELD_NUMBER: _ClassVar[int]
msg: IncomingMessage
config: ConfigSnapshot
@@ -337,8 +361,10 @@ class DownstreamMessage(_message.Message):
task_hibernated: TaskHibernated
task_subscription_response: TaskSubscriptionOperationResponse
task_event: TaskEvent
+ access_check_response: AccessCheckResponse
+ batch_access_check_response: BatchAccessCheckResponse
active_extensions: _containers.RepeatedScalarFieldContainer[str]
- def __init__(self, msg: _Optional[_Union[IncomingMessage, _Mapping]] = ..., config: _Optional[_Union[ConfigSnapshot, _Mapping]] = ..., signal: _Optional[_Union[Signal, _Mapping]] = ..., error: _Optional[_Union[ErrorResponse, _Mapping]] = ..., kv: _Optional[_Union[KVResponse, _Mapping]] = ..., task_assignment: _Optional[_Union[TaskAssignment, _Mapping]] = ..., connection_ack: _Optional[_Union[ConnectionAck, _Mapping]] = ..., checkpoint: _Optional[_Union[CheckpointResponse, _Mapping]] = ..., admin: _Optional[_Union[AdminResponse, _Mapping]] = ..., session_response: _Optional[_Union[SessionOperationResponse, _Mapping]] = ..., task_query: _Optional[_Union[TaskQueryResponse, _Mapping]] = ..., task_op: _Optional[_Union[TaskOperationResponse, _Mapping]] = ..., workspace: _Optional[_Union[WorkspaceResponse, _Mapping]] = ..., agent: _Optional[_Union[AgentResponse, _Mapping]] = ..., acl: _Optional[_Union[ACLResponse, _Mapping]] = ..., progress_update: _Optional[_Union[ProgressUpdate, _Mapping]] = ..., workflow_response: _Optional[_Union[WorkflowResponse, _Mapping]] = ..., workflow_op: _Optional[_Union[WorkflowOperation, _Mapping]] = ..., token: _Optional[_Union[TokenResponse, _Mapping]] = ..., audit_response: _Optional[_Union[AuditQueryResponse, _Mapping]] = ..., authority_grant: _Optional[_Union[AuthorityGrantResponse, _Mapping]] = ..., create_task: _Optional[_Union[CreateTaskResponse, _Mapping]] = ..., proxy_http_response: _Optional[_Union[ProxyHttpResponse, _Mapping]] = ..., proxy_http_body_chunk: _Optional[_Union[ProxyHttpBodyChunk, _Mapping]] = ..., tunnel_ack: _Optional[_Union[TunnelAck, _Mapping]] = ..., tunnel_close: _Optional[_Union[TunnelClose, _Mapping]] = ..., tunnel_data: _Optional[_Union[TunnelData, _Mapping]] = ..., proxy_http_request: _Optional[_Union[ProxyHttpRequest, _Mapping]] = ..., resolve_authority_response: _Optional[_Union[ResolveAuthorityResponse, _Mapping]] = ..., connection_status_response: _Optional[_Union[ConnectionStatusResponse, _Mapping]] = ..., authority_grant_revocation: _Optional[_Union[AuthorityGrantRevocation, _Mapping]] = ..., submit_audit_event_response: _Optional[_Union[SubmitAuditEventResponse, _Mapping]] = ..., authority_request_response: _Optional[_Union[AuthorityRequestOperationResponse, _Mapping]] = ..., authority_request_event: _Optional[_Union[AuthorityRequestEvent, _Mapping]] = ..., task_hibernated: _Optional[_Union[TaskHibernated, _Mapping]] = ..., task_subscription_response: _Optional[_Union[TaskSubscriptionOperationResponse, _Mapping]] = ..., task_event: _Optional[_Union[TaskEvent, _Mapping]] = ..., active_extensions: _Optional[_Iterable[str]] = ...) -> None: ...
+ def __init__(self, msg: _Optional[_Union[IncomingMessage, _Mapping]] = ..., config: _Optional[_Union[ConfigSnapshot, _Mapping]] = ..., signal: _Optional[_Union[Signal, _Mapping]] = ..., error: _Optional[_Union[ErrorResponse, _Mapping]] = ..., kv: _Optional[_Union[KVResponse, _Mapping]] = ..., task_assignment: _Optional[_Union[TaskAssignment, _Mapping]] = ..., connection_ack: _Optional[_Union[ConnectionAck, _Mapping]] = ..., checkpoint: _Optional[_Union[CheckpointResponse, _Mapping]] = ..., admin: _Optional[_Union[AdminResponse, _Mapping]] = ..., session_response: _Optional[_Union[SessionOperationResponse, _Mapping]] = ..., task_query: _Optional[_Union[TaskQueryResponse, _Mapping]] = ..., task_op: _Optional[_Union[TaskOperationResponse, _Mapping]] = ..., workspace: _Optional[_Union[WorkspaceResponse, _Mapping]] = ..., agent: _Optional[_Union[AgentResponse, _Mapping]] = ..., acl: _Optional[_Union[ACLResponse, _Mapping]] = ..., progress_update: _Optional[_Union[ProgressUpdate, _Mapping]] = ..., workflow_response: _Optional[_Union[WorkflowResponse, _Mapping]] = ..., workflow_op: _Optional[_Union[WorkflowOperation, _Mapping]] = ..., token: _Optional[_Union[TokenResponse, _Mapping]] = ..., audit_response: _Optional[_Union[AuditQueryResponse, _Mapping]] = ..., authority_grant: _Optional[_Union[AuthorityGrantResponse, _Mapping]] = ..., create_task: _Optional[_Union[CreateTaskResponse, _Mapping]] = ..., proxy_http_response: _Optional[_Union[ProxyHttpResponse, _Mapping]] = ..., proxy_http_body_chunk: _Optional[_Union[ProxyHttpBodyChunk, _Mapping]] = ..., tunnel_ack: _Optional[_Union[TunnelAck, _Mapping]] = ..., tunnel_close: _Optional[_Union[TunnelClose, _Mapping]] = ..., tunnel_data: _Optional[_Union[TunnelData, _Mapping]] = ..., proxy_http_request: _Optional[_Union[ProxyHttpRequest, _Mapping]] = ..., resolve_authority_response: _Optional[_Union[ResolveAuthorityResponse, _Mapping]] = ..., connection_status_response: _Optional[_Union[ConnectionStatusResponse, _Mapping]] = ..., authority_grant_revocation: _Optional[_Union[AuthorityGrantRevocation, _Mapping]] = ..., submit_audit_event_response: _Optional[_Union[SubmitAuditEventResponse, _Mapping]] = ..., authority_request_response: _Optional[_Union[AuthorityRequestOperationResponse, _Mapping]] = ..., authority_request_event: _Optional[_Union[AuthorityRequestEvent, _Mapping]] = ..., task_hibernated: _Optional[_Union[TaskHibernated, _Mapping]] = ..., task_subscription_response: _Optional[_Union[TaskSubscriptionOperationResponse, _Mapping]] = ..., task_event: _Optional[_Union[TaskEvent, _Mapping]] = ..., access_check_response: _Optional[_Union[AccessCheckResponse, _Mapping]] = ..., batch_access_check_response: _Optional[_Union[BatchAccessCheckResponse, _Mapping]] = ..., active_extensions: _Optional[_Iterable[str]] = ...) -> None: ...
class TaskHibernated(_message.Message):
__slots__ = ("task_id", "descriptor")
@@ -550,18 +576,52 @@ class ResolvedAuthorityInfo(_message.Message):
def __init__(self, root_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., audience_type: _Optional[str] = ..., audience_id: _Optional[str] = ..., max_access_level: _Optional[int] = ..., workspace_scope: _Optional[_Iterable[str]] = ..., expires_at_ms: _Optional[int] = ...) -> None: ...
class SendMessage(_message.Message):
- __slots__ = ("target_topic", "payload", "message_type", "authorization", "app_workspace")
+ __slots__ = ("target_topic", "payload", "message_type", "authorization", "app_workspace", "checked_access", "authority_continuation")
TARGET_TOPIC_FIELD_NUMBER: _ClassVar[int]
PAYLOAD_FIELD_NUMBER: _ClassVar[int]
MESSAGE_TYPE_FIELD_NUMBER: _ClassVar[int]
AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
APP_WORKSPACE_FIELD_NUMBER: _ClassVar[int]
+ CHECKED_ACCESS_FIELD_NUMBER: _ClassVar[int]
+ AUTHORITY_CONTINUATION_FIELD_NUMBER: _ClassVar[int]
target_topic: str
payload: bytes
message_type: MessageType
authorization: AuthorizationContext
app_workspace: str
- def __init__(self, target_topic: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., app_workspace: _Optional[str] = ...) -> None: ...
+ checked_access: ResourceAccessRequest
+ authority_continuation: AuthorityContinuationRequest
+ def __init__(self, target_topic: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., app_workspace: _Optional[str] = ..., checked_access: _Optional[_Union[ResourceAccessRequest, _Mapping]] = ..., authority_continuation: _Optional[_Union[AuthorityContinuationRequest, _Mapping]] = ...) -> None: ...
+
+class AuthorityContinuationScope(_message.Message):
+ __slots__ = ("workspace_scope", "resource_scope", "operation_scope", "max_access_level")
+ WORKSPACE_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ RESOURCE_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ OPERATION_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ MAX_ACCESS_LEVEL_FIELD_NUMBER: _ClassVar[int]
+ workspace_scope: _containers.RepeatedScalarFieldContainer[str]
+ resource_scope: _containers.RepeatedCompositeFieldContainer[ACLAuthorityGrantResourceScopeEntry]
+ operation_scope: _containers.RepeatedScalarFieldContainer[str]
+ max_access_level: int
+ def __init__(self, workspace_scope: _Optional[_Iterable[str]] = ..., resource_scope: _Optional[_Iterable[_Union[ACLAuthorityGrantResourceScopeEntry, _Mapping]]] = ..., operation_scope: _Optional[_Iterable[str]] = ..., max_access_level: _Optional[int] = ...) -> None: ...
+
+class AuthorityContinuationRequest(_message.Message):
+ __slots__ = ("scope_mode", "binding_id", "scope")
+ class ScopeMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
+ __slots__ = ()
+ SCOPE_MODE_UNSPECIFIED: _ClassVar[AuthorityContinuationRequest.ScopeMode]
+ SCOPE_MODE_INHERIT_PARENT: _ClassVar[AuthorityContinuationRequest.ScopeMode]
+ SCOPE_MODE_ATTENUATE: _ClassVar[AuthorityContinuationRequest.ScopeMode]
+ SCOPE_MODE_UNSPECIFIED: AuthorityContinuationRequest.ScopeMode
+ SCOPE_MODE_INHERIT_PARENT: AuthorityContinuationRequest.ScopeMode
+ SCOPE_MODE_ATTENUATE: AuthorityContinuationRequest.ScopeMode
+ SCOPE_MODE_FIELD_NUMBER: _ClassVar[int]
+ BINDING_ID_FIELD_NUMBER: _ClassVar[int]
+ SCOPE_FIELD_NUMBER: _ClassVar[int]
+ scope_mode: AuthorityContinuationRequest.ScopeMode
+ binding_id: str
+ scope: AuthorityContinuationScope
+ def __init__(self, scope_mode: _Optional[_Union[AuthorityContinuationRequest.ScopeMode, str]] = ..., binding_id: _Optional[str] = ..., scope: _Optional[_Union[AuthorityContinuationScope, _Mapping]] = ...) -> None: ...
class Metric(_message.Message):
__slots__ = ("trace_id", "entries", "metadata", "client_timestamp_ms")
@@ -712,18 +772,38 @@ class KVResponse(_message.Message):
def __init__(self, success: _Optional[bool] = ..., value: _Optional[bytes] = ..., keys: _Optional[_Iterable[str]] = ..., kv_map: _Optional[_Mapping[str, bytes]] = ..., request_id: _Optional[str] = ..., counter_value: _Optional[int] = ..., applied: _Optional[bool] = ..., next_cursor: _Optional[str] = ..., has_more: _Optional[bool] = ...) -> None: ...
class IncomingMessage(_message.Message):
- __slots__ = ("source_topic", "payload", "message_type", "workspace", "on_behalf_subject")
+ __slots__ = ("source_topic", "payload", "message_type", "workspace", "on_behalf_subject", "access_receipt", "forwarded_authorization")
SOURCE_TOPIC_FIELD_NUMBER: _ClassVar[int]
PAYLOAD_FIELD_NUMBER: _ClassVar[int]
MESSAGE_TYPE_FIELD_NUMBER: _ClassVar[int]
WORKSPACE_FIELD_NUMBER: _ClassVar[int]
ON_BEHALF_SUBJECT_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_RECEIPT_FIELD_NUMBER: _ClassVar[int]
+ FORWARDED_AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
source_topic: str
payload: bytes
message_type: MessageType
workspace: str
on_behalf_subject: PrincipalRef
- def __init__(self, source_topic: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., workspace: _Optional[str] = ..., on_behalf_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ...) -> None: ...
+ access_receipt: AccessDecisionReceipt
+ forwarded_authorization: ForwardedAuthorization
+ def __init__(self, source_topic: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., workspace: _Optional[str] = ..., on_behalf_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., access_receipt: _Optional[_Union[AccessDecisionReceipt, _Mapping]] = ..., forwarded_authorization: _Optional[_Union[ForwardedAuthorization, _Mapping]] = ...) -> None: ...
+
+class ForwardedAuthorization(_message.Message):
+ __slots__ = ("authorization", "root_grant_id", "expires_at_ms", "delivery_target", "binding_id", "scope")
+ AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
+ ROOT_GRANT_ID_FIELD_NUMBER: _ClassVar[int]
+ EXPIRES_AT_MS_FIELD_NUMBER: _ClassVar[int]
+ DELIVERY_TARGET_FIELD_NUMBER: _ClassVar[int]
+ BINDING_ID_FIELD_NUMBER: _ClassVar[int]
+ SCOPE_FIELD_NUMBER: _ClassVar[int]
+ authorization: AuthorizationContext
+ root_grant_id: str
+ expires_at_ms: int
+ delivery_target: str
+ binding_id: str
+ scope: AuthorityContinuationScope
+ def __init__(self, authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., root_grant_id: _Optional[str] = ..., expires_at_ms: _Optional[int] = ..., delivery_target: _Optional[str] = ..., binding_id: _Optional[str] = ..., scope: _Optional[_Union[AuthorityContinuationScope, _Mapping]] = ...) -> None: ...
class ConfigSnapshot(_message.Message):
__slots__ = ("kv", "global_kv", "task_context", "workspace_exclusive_kv", "global_exclusive_kv")
@@ -833,7 +913,7 @@ class TaskCompletionEvent(_message.Message):
def __init__(self, enabled: _Optional[bool] = ..., event_name: _Optional[str] = ..., on_statuses: _Optional[_Iterable[_Union[TaskStatus, str]]] = ...) -> None: ...
class CreateTaskRequest(_message.Message):
- __slots__ = ("task_type", "workspace", "assignment_mode", "target_agent_id", "launch_param_overrides", "metadata", "payload", "target_implementation", "authorization", "request_id", "target_identity", "task_class", "context_id", "retry_policy", "priority", "idempotency_key", "correlation_id", "root_task_id", "completion_event")
+ __slots__ = ("task_type", "workspace", "assignment_mode", "target_agent_id", "launch_param_overrides", "metadata", "payload", "target_implementation", "authorization", "request_id", "target_identity", "task_class", "context_id", "retry_policy", "priority", "idempotency_key", "correlation_id", "root_task_id", "completion_event", "parent_task_id", "target_offline_policy", "required_downstream_authority_hops", "originating_schedule_id")
class LaunchParamOverridesEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
@@ -867,6 +947,10 @@ class CreateTaskRequest(_message.Message):
CORRELATION_ID_FIELD_NUMBER: _ClassVar[int]
ROOT_TASK_ID_FIELD_NUMBER: _ClassVar[int]
COMPLETION_EVENT_FIELD_NUMBER: _ClassVar[int]
+ PARENT_TASK_ID_FIELD_NUMBER: _ClassVar[int]
+ TARGET_OFFLINE_POLICY_FIELD_NUMBER: _ClassVar[int]
+ REQUIRED_DOWNSTREAM_AUTHORITY_HOPS_FIELD_NUMBER: _ClassVar[int]
+ ORIGINATING_SCHEDULE_ID_FIELD_NUMBER: _ClassVar[int]
task_type: str
workspace: str
assignment_mode: TaskAssignmentMode
@@ -886,7 +970,11 @@ class CreateTaskRequest(_message.Message):
correlation_id: str
root_task_id: str
completion_event: TaskCompletionEvent
- def __init__(self, task_type: _Optional[str] = ..., workspace: _Optional[str] = ..., assignment_mode: _Optional[_Union[TaskAssignmentMode, str]] = ..., target_agent_id: _Optional[str] = ..., launch_param_overrides: _Optional[_Mapping[str, str]] = ..., metadata: _Optional[_Mapping[str, str]] = ..., payload: _Optional[bytes] = ..., target_implementation: _Optional[str] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., request_id: _Optional[str] = ..., target_identity: _Optional[str] = ..., task_class: _Optional[_Union[TaskClass, str]] = ..., context_id: _Optional[str] = ..., retry_policy: _Optional[_Union[RetryPolicy, _Mapping]] = ..., priority: _Optional[_Union[TaskPriority, str]] = ..., idempotency_key: _Optional[str] = ..., correlation_id: _Optional[str] = ..., root_task_id: _Optional[str] = ..., completion_event: _Optional[_Union[TaskCompletionEvent, _Mapping]] = ...) -> None: ...
+ parent_task_id: str
+ target_offline_policy: TargetOfflinePolicy
+ required_downstream_authority_hops: int
+ originating_schedule_id: str
+ def __init__(self, task_type: _Optional[str] = ..., workspace: _Optional[str] = ..., assignment_mode: _Optional[_Union[TaskAssignmentMode, str]] = ..., target_agent_id: _Optional[str] = ..., launch_param_overrides: _Optional[_Mapping[str, str]] = ..., metadata: _Optional[_Mapping[str, str]] = ..., payload: _Optional[bytes] = ..., target_implementation: _Optional[str] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., request_id: _Optional[str] = ..., target_identity: _Optional[str] = ..., task_class: _Optional[_Union[TaskClass, str]] = ..., context_id: _Optional[str] = ..., retry_policy: _Optional[_Union[RetryPolicy, _Mapping]] = ..., priority: _Optional[_Union[TaskPriority, str]] = ..., idempotency_key: _Optional[str] = ..., correlation_id: _Optional[str] = ..., root_task_id: _Optional[str] = ..., completion_event: _Optional[_Union[TaskCompletionEvent, _Mapping]] = ..., parent_task_id: _Optional[str] = ..., target_offline_policy: _Optional[_Union[TargetOfflinePolicy, str]] = ..., required_downstream_authority_hops: _Optional[int] = ..., originating_schedule_id: _Optional[str] = ...) -> None: ...
class CreateTaskResponse(_message.Message):
__slots__ = ("success", "task_id", "status", "error_code", "error_message", "request_id", "assigned_to", "task_token", "authority_grant_id")
@@ -911,7 +999,7 @@ class CreateTaskResponse(_message.Message):
def __init__(self, success: _Optional[bool] = ..., task_id: _Optional[str] = ..., status: _Optional[str] = ..., error_code: _Optional[str] = ..., error_message: _Optional[str] = ..., request_id: _Optional[str] = ..., assigned_to: _Optional[str] = ..., task_token: _Optional[str] = ..., authority_grant_id: _Optional[str] = ...) -> None: ...
class TaskAssignment(_message.Message):
- __slots__ = ("task_id", "task_type", "assigned_to", "metadata", "assigned_at", "profile", "launch_params", "target_implementation", "workspace", "specifier", "payload", "task_class", "checkpoint_key", "resume_session_id")
+ __slots__ = ("task_id", "task_type", "assigned_to", "metadata", "assigned_at", "profile", "launch_params", "target_implementation", "workspace", "specifier", "payload", "task_class", "checkpoint_key", "resume_session_id", "authorization")
class MetadataEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
@@ -940,6 +1028,7 @@ class TaskAssignment(_message.Message):
TASK_CLASS_FIELD_NUMBER: _ClassVar[int]
CHECKPOINT_KEY_FIELD_NUMBER: _ClassVar[int]
RESUME_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
+ AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
task_id: str
task_type: str
assigned_to: str
@@ -954,7 +1043,8 @@ class TaskAssignment(_message.Message):
task_class: TaskClass
checkpoint_key: str
resume_session_id: str
- def __init__(self, task_id: _Optional[str] = ..., task_type: _Optional[str] = ..., assigned_to: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ..., assigned_at: _Optional[int] = ..., profile: _Optional[str] = ..., launch_params: _Optional[_Mapping[str, str]] = ..., target_implementation: _Optional[str] = ..., workspace: _Optional[str] = ..., specifier: _Optional[str] = ..., payload: _Optional[bytes] = ..., task_class: _Optional[_Union[TaskClass, str]] = ..., checkpoint_key: _Optional[str] = ..., resume_session_id: _Optional[str] = ...) -> None: ...
+ authorization: AuthorizationContext
+ def __init__(self, task_id: _Optional[str] = ..., task_type: _Optional[str] = ..., assigned_to: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ..., assigned_at: _Optional[int] = ..., profile: _Optional[str] = ..., launch_params: _Optional[_Mapping[str, str]] = ..., target_implementation: _Optional[str] = ..., workspace: _Optional[str] = ..., specifier: _Optional[str] = ..., payload: _Optional[bytes] = ..., task_class: _Optional[_Union[TaskClass, str]] = ..., checkpoint_key: _Optional[str] = ..., resume_session_id: _Optional[str] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ...) -> None: ...
class CheckpointOperation(_message.Message):
__slots__ = ("op", "key", "data", "ttl", "request_id")
@@ -2368,7 +2458,7 @@ class ACLResponse(_message.Message):
def __init__(self, success: _Optional[bool] = ..., error: _Optional[str] = ..., message: _Optional[str] = ..., rule: _Optional[_Union[ACLRuleInfo, _Mapping]] = ..., rules: _Optional[_Iterable[_Union[ACLRuleInfo, _Mapping]]] = ..., total_rules: _Optional[int] = ..., fallback_policy: _Optional[_Union[ACLFallbackPolicyInfo, _Mapping]] = ..., audit_entries: _Optional[_Iterable[_Union[ACLAuditEntryInfo, _Mapping]]] = ..., total_audit_entries: _Optional[int] = ..., cleanup_result: _Optional[_Union[ACLCleanupResult, _Mapping]] = ..., authority_grant: _Optional[_Union[ACLAuthorityGrantInfo, _Mapping]] = ..., authority_grants: _Optional[_Iterable[_Union[ACLAuthorityGrantInfo, _Mapping]]] = ..., total_authority_grants: _Optional[int] = ..., request_id: _Optional[str] = ..., group: _Optional[_Union[ACLGroupInfo, _Mapping]] = ..., groups: _Optional[_Iterable[_Union[ACLGroupInfo, _Mapping]]] = ..., role: _Optional[_Union[ACLRoleInfo, _Mapping]] = ..., roles: _Optional[_Iterable[_Union[ACLRoleInfo, _Mapping]]] = ..., group_members: _Optional[_Iterable[_Union[ACLGroupMemberInfo, _Mapping]]] = ..., role_assignments: _Optional[_Iterable[_Union[ACLRoleAssignmentInfo, _Mapping]]] = ..., explanation: _Optional[_Union[ACLAccessExplanationInfo, _Mapping]] = ...) -> None: ...
class AuthorityGrantOperation(_message.Message):
- __slots__ = ("op", "grant_id", "exchange_request", "derive_request", "renew_request", "request_id", "list_request", "batch_exchange_request", "derive_for_target_request")
+ __slots__ = ("op", "grant_id", "exchange_request", "derive_request", "renew_request", "request_id", "list_request", "batch_exchange_request", "derive_for_target_request", "workflow_schedule_id")
class OpType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
EXCHANGE: _ClassVar[AuthorityGrantOperation.OpType]
@@ -2398,6 +2488,7 @@ class AuthorityGrantOperation(_message.Message):
LIST_REQUEST_FIELD_NUMBER: _ClassVar[int]
BATCH_EXCHANGE_REQUEST_FIELD_NUMBER: _ClassVar[int]
DERIVE_FOR_TARGET_REQUEST_FIELD_NUMBER: _ClassVar[int]
+ WORKFLOW_SCHEDULE_ID_FIELD_NUMBER: _ClassVar[int]
op: AuthorityGrantOperation.OpType
grant_id: str
exchange_request: AuthorityGrantExchangeRequest
@@ -2407,7 +2498,8 @@ class AuthorityGrantOperation(_message.Message):
list_request: AuthorityGrantListRequest
batch_exchange_request: AuthorityGrantBatchExchangeRequest
derive_for_target_request: AuthorityGrantDeriveForTargetRequest
- def __init__(self, op: _Optional[_Union[AuthorityGrantOperation.OpType, str]] = ..., grant_id: _Optional[str] = ..., exchange_request: _Optional[_Union[AuthorityGrantExchangeRequest, _Mapping]] = ..., derive_request: _Optional[_Union[AuthorityGrantDeriveRequest, _Mapping]] = ..., renew_request: _Optional[_Union[ACLRenewAuthorityGrantRequest, _Mapping]] = ..., request_id: _Optional[str] = ..., list_request: _Optional[_Union[AuthorityGrantListRequest, _Mapping]] = ..., batch_exchange_request: _Optional[_Union[AuthorityGrantBatchExchangeRequest, _Mapping]] = ..., derive_for_target_request: _Optional[_Union[AuthorityGrantDeriveForTargetRequest, _Mapping]] = ...) -> None: ...
+ workflow_schedule_id: str
+ def __init__(self, op: _Optional[_Union[AuthorityGrantOperation.OpType, str]] = ..., grant_id: _Optional[str] = ..., exchange_request: _Optional[_Union[AuthorityGrantExchangeRequest, _Mapping]] = ..., derive_request: _Optional[_Union[AuthorityGrantDeriveRequest, _Mapping]] = ..., renew_request: _Optional[_Union[ACLRenewAuthorityGrantRequest, _Mapping]] = ..., request_id: _Optional[str] = ..., list_request: _Optional[_Union[AuthorityGrantListRequest, _Mapping]] = ..., batch_exchange_request: _Optional[_Union[AuthorityGrantBatchExchangeRequest, _Mapping]] = ..., derive_for_target_request: _Optional[_Union[AuthorityGrantDeriveForTargetRequest, _Mapping]] = ..., workflow_schedule_id: _Optional[str] = ...) -> None: ...
class AuthorityGrantExchangeRequest(_message.Message):
__slots__ = ("source_session_id", "workspace_scope", "resource_scope", "operation_scope", "max_access_level", "audience_type", "audience_id", "valid_while_audience_active", "expires_at", "renewable_until", "may_delegate", "remaining_hops", "reason", "metadata")
@@ -3005,8 +3097,54 @@ class ProgressUpdate(_message.Message):
kind: ProgressKind
def __init__(self, source: _Optional[str] = ..., task_id: _Optional[str] = ..., state: _Optional[str] = ..., completion: _Optional[float] = ..., summary: _Optional[str] = ..., step: _Optional[_Union[ProgressStep, _Mapping]] = ..., timestamp_ms: _Optional[int] = ..., workspace: _Optional[str] = ..., request_id: _Optional[str] = ..., metadata: _Optional[_Mapping[str, str]] = ..., recipient: _Optional[str] = ..., kind: _Optional[_Union[ProgressKind, str]] = ...) -> None: ...
+class WorkflowScheduleAuthorityScope(_message.Message):
+ __slots__ = ("workspace_scope", "resource_scope", "operation_scope", "max_access_level", "expires_at", "renewable_until", "required_task_authority_hops", "lifetime_mode", "policy_version")
+ WORKSPACE_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ RESOURCE_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ OPERATION_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ MAX_ACCESS_LEVEL_FIELD_NUMBER: _ClassVar[int]
+ EXPIRES_AT_FIELD_NUMBER: _ClassVar[int]
+ RENEWABLE_UNTIL_FIELD_NUMBER: _ClassVar[int]
+ REQUIRED_TASK_AUTHORITY_HOPS_FIELD_NUMBER: _ClassVar[int]
+ LIFETIME_MODE_FIELD_NUMBER: _ClassVar[int]
+ POLICY_VERSION_FIELD_NUMBER: _ClassVar[int]
+ workspace_scope: _containers.RepeatedScalarFieldContainer[str]
+ resource_scope: _containers.RepeatedCompositeFieldContainer[ACLAuthorityGrantResourceScopeEntry]
+ operation_scope: _containers.RepeatedScalarFieldContainer[str]
+ max_access_level: int
+ expires_at: int
+ renewable_until: int
+ required_task_authority_hops: int
+ lifetime_mode: WorkflowAuthorityLifetimeMode
+ policy_version: int
+ def __init__(self, workspace_scope: _Optional[_Iterable[str]] = ..., resource_scope: _Optional[_Iterable[_Union[ACLAuthorityGrantResourceScopeEntry, _Mapping]]] = ..., operation_scope: _Optional[_Iterable[str]] = ..., max_access_level: _Optional[int] = ..., expires_at: _Optional[int] = ..., renewable_until: _Optional[int] = ..., required_task_authority_hops: _Optional[int] = ..., lifetime_mode: _Optional[_Union[WorkflowAuthorityLifetimeMode, str]] = ..., policy_version: _Optional[int] = ...) -> None: ...
+
+class WorkflowRequestContext(_message.Message):
+ __slots__ = ("actor", "subject", "actor_session_id", "schedule_authorization", "root_grant_id", "source_grant_id", "expires_at_ms", "policy_digest", "lifetime_mode", "policy_version")
+ ACTOR_FIELD_NUMBER: _ClassVar[int]
+ SUBJECT_FIELD_NUMBER: _ClassVar[int]
+ ACTOR_SESSION_ID_FIELD_NUMBER: _ClassVar[int]
+ SCHEDULE_AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
+ ROOT_GRANT_ID_FIELD_NUMBER: _ClassVar[int]
+ SOURCE_GRANT_ID_FIELD_NUMBER: _ClassVar[int]
+ EXPIRES_AT_MS_FIELD_NUMBER: _ClassVar[int]
+ POLICY_DIGEST_FIELD_NUMBER: _ClassVar[int]
+ LIFETIME_MODE_FIELD_NUMBER: _ClassVar[int]
+ POLICY_VERSION_FIELD_NUMBER: _ClassVar[int]
+ actor: PrincipalRef
+ subject: PrincipalRef
+ actor_session_id: str
+ schedule_authorization: AuthorizationContext
+ root_grant_id: str
+ source_grant_id: str
+ expires_at_ms: int
+ policy_digest: str
+ lifetime_mode: WorkflowAuthorityLifetimeMode
+ policy_version: int
+ def __init__(self, actor: _Optional[_Union[PrincipalRef, _Mapping]] = ..., subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., actor_session_id: _Optional[str] = ..., schedule_authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., root_grant_id: _Optional[str] = ..., source_grant_id: _Optional[str] = ..., expires_at_ms: _Optional[int] = ..., policy_digest: _Optional[str] = ..., lifetime_mode: _Optional[_Union[WorkflowAuthorityLifetimeMode, str]] = ..., policy_version: _Optional[int] = ...) -> None: ...
+
class WorkflowOperation(_message.Message):
- __slots__ = ("op", "id", "secondary_id", "workspace", "data", "request_id", "status_filter")
+ __slots__ = ("op", "id", "secondary_id", "workspace", "data", "request_id", "status_filter", "authorization", "schedule_authority_scope", "request_context")
class OpType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
LIST_RULES: _ClassVar[WorkflowOperation.OpType]
@@ -3070,6 +3208,9 @@ class WorkflowOperation(_message.Message):
DATA_FIELD_NUMBER: _ClassVar[int]
REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
STATUS_FILTER_FIELD_NUMBER: _ClassVar[int]
+ AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
+ SCHEDULE_AUTHORITY_SCOPE_FIELD_NUMBER: _ClassVar[int]
+ REQUEST_CONTEXT_FIELD_NUMBER: _ClassVar[int]
op: WorkflowOperation.OpType
id: str
secondary_id: str
@@ -3077,7 +3218,10 @@ class WorkflowOperation(_message.Message):
data: bytes
request_id: str
status_filter: str
- def __init__(self, op: _Optional[_Union[WorkflowOperation.OpType, str]] = ..., id: _Optional[str] = ..., secondary_id: _Optional[str] = ..., workspace: _Optional[str] = ..., data: _Optional[bytes] = ..., request_id: _Optional[str] = ..., status_filter: _Optional[str] = ...) -> None: ...
+ authorization: AuthorizationContext
+ schedule_authority_scope: WorkflowScheduleAuthorityScope
+ request_context: WorkflowRequestContext
+ def __init__(self, op: _Optional[_Union[WorkflowOperation.OpType, str]] = ..., id: _Optional[str] = ..., secondary_id: _Optional[str] = ..., workspace: _Optional[str] = ..., data: _Optional[bytes] = ..., request_id: _Optional[str] = ..., status_filter: _Optional[str] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., schedule_authority_scope: _Optional[_Union[WorkflowScheduleAuthorityScope, _Mapping]] = ..., request_context: _Optional[_Union[WorkflowRequestContext, _Mapping]] = ...) -> None: ...
class WorkflowResponse(_message.Message):
__slots__ = ("success", "error", "message", "data", "total_count", "request_id")
@@ -3096,7 +3240,7 @@ class WorkflowResponse(_message.Message):
def __init__(self, success: _Optional[bool] = ..., error: _Optional[str] = ..., message: _Optional[str] = ..., data: _Optional[bytes] = ..., total_count: _Optional[int] = ..., request_id: _Optional[str] = ...) -> None: ...
class MessageEnvelope(_message.Message):
- __slots__ = ("source", "payload", "message_type", "timestamp_ms", "metadata", "workspace", "on_behalf_subject")
+ __slots__ = ("source", "payload", "message_type", "timestamp_ms", "metadata", "workspace", "on_behalf_subject", "access_receipt", "forwarded_authorization")
class MetadataEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
@@ -3111,6 +3255,8 @@ class MessageEnvelope(_message.Message):
METADATA_FIELD_NUMBER: _ClassVar[int]
WORKSPACE_FIELD_NUMBER: _ClassVar[int]
ON_BEHALF_SUBJECT_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_RECEIPT_FIELD_NUMBER: _ClassVar[int]
+ FORWARDED_AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
source: str
payload: bytes
message_type: MessageType
@@ -3118,7 +3264,9 @@ class MessageEnvelope(_message.Message):
metadata: _containers.ScalarMap[str, str]
workspace: str
on_behalf_subject: PrincipalRef
- def __init__(self, source: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., timestamp_ms: _Optional[int] = ..., metadata: _Optional[_Mapping[str, str]] = ..., workspace: _Optional[str] = ..., on_behalf_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ...) -> None: ...
+ access_receipt: AccessDecisionReceipt
+ forwarded_authorization: ForwardedAuthorization
+ def __init__(self, source: _Optional[str] = ..., payload: _Optional[bytes] = ..., message_type: _Optional[_Union[MessageType, str]] = ..., timestamp_ms: _Optional[int] = ..., metadata: _Optional[_Mapping[str, str]] = ..., workspace: _Optional[str] = ..., on_behalf_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., access_receipt: _Optional[_Union[AccessDecisionReceipt, _Mapping]] = ..., forwarded_authorization: _Optional[_Union[ForwardedAuthorization, _Mapping]] = ...) -> None: ...
class AuditQuery(_message.Message):
__slots__ = ("request_id", "start_time", "end_time", "event_type", "actor_type", "actor_id", "resource_type", "resource_id", "operation", "workspace", "only_failures", "limit", "offset", "subject_type", "subject_id", "authority_mode", "authority_grant_id", "authorization", "exclude_actor_types", "exclude_workspaces", "exclude_service_direct")
@@ -3272,7 +3420,7 @@ class SubmitAuditEventResponse(_message.Message):
def __init__(self, client_request_id: _Optional[str] = ..., success: _Optional[bool] = ..., error_code: _Optional[str] = ..., error_message: _Optional[str] = ...) -> None: ...
class ProxyHttpRequest(_message.Message):
- __slots__ = ("request_id", "target_topic", "method", "path", "headers", "body", "body_chunked", "authorization", "app_workspace", "timeout_ms", "follow_redirects", "backend_name", "stream_response_indefinitely", "stream_idle_timeout_ms", "max_response_body_bytes", "proxy_chain_depth")
+ __slots__ = ("request_id", "target_topic", "method", "path", "headers", "body", "body_chunked", "authorization", "app_workspace", "timeout_ms", "follow_redirects", "backend_name", "stream_response_indefinitely", "stream_idle_timeout_ms", "max_response_body_bytes", "proxy_chain_depth", "checked_access", "access_receipt")
class HeadersEntry(_message.Message):
__slots__ = ("key", "value")
KEY_FIELD_NUMBER: _ClassVar[int]
@@ -3296,6 +3444,8 @@ class ProxyHttpRequest(_message.Message):
STREAM_IDLE_TIMEOUT_MS_FIELD_NUMBER: _ClassVar[int]
MAX_RESPONSE_BODY_BYTES_FIELD_NUMBER: _ClassVar[int]
PROXY_CHAIN_DEPTH_FIELD_NUMBER: _ClassVar[int]
+ CHECKED_ACCESS_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_RECEIPT_FIELD_NUMBER: _ClassVar[int]
request_id: str
target_topic: str
method: str
@@ -3312,7 +3462,9 @@ class ProxyHttpRequest(_message.Message):
stream_idle_timeout_ms: int
max_response_body_bytes: int
proxy_chain_depth: int
- def __init__(self, request_id: _Optional[str] = ..., target_topic: _Optional[str] = ..., method: _Optional[str] = ..., path: _Optional[str] = ..., headers: _Optional[_Mapping[str, str]] = ..., body: _Optional[bytes] = ..., body_chunked: _Optional[bool] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., app_workspace: _Optional[str] = ..., timeout_ms: _Optional[int] = ..., follow_redirects: _Optional[bool] = ..., backend_name: _Optional[str] = ..., stream_response_indefinitely: _Optional[bool] = ..., stream_idle_timeout_ms: _Optional[int] = ..., max_response_body_bytes: _Optional[int] = ..., proxy_chain_depth: _Optional[int] = ...) -> None: ...
+ checked_access: ResourceAccessRequest
+ access_receipt: AccessDecisionReceipt
+ def __init__(self, request_id: _Optional[str] = ..., target_topic: _Optional[str] = ..., method: _Optional[str] = ..., path: _Optional[str] = ..., headers: _Optional[_Mapping[str, str]] = ..., body: _Optional[bytes] = ..., body_chunked: _Optional[bool] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ..., app_workspace: _Optional[str] = ..., timeout_ms: _Optional[int] = ..., follow_redirects: _Optional[bool] = ..., backend_name: _Optional[str] = ..., stream_response_indefinitely: _Optional[bool] = ..., stream_idle_timeout_ms: _Optional[int] = ..., max_response_body_bytes: _Optional[int] = ..., proxy_chain_depth: _Optional[int] = ..., checked_access: _Optional[_Union[ResourceAccessRequest, _Mapping]] = ..., access_receipt: _Optional[_Union[AccessDecisionReceipt, _Mapping]] = ...) -> None: ...
class ProxyHttpResponse(_message.Message):
__slots__ = ("request_id", "status_code", "headers", "body", "body_chunked", "error")
@@ -3652,3 +3804,97 @@ class TaskAuthorityRequestEventRelay(_message.Message):
EVENT_FIELD_NUMBER: _ClassVar[int]
event: AuthorityRequestEvent
def __init__(self, event: _Optional[_Union[AuthorityRequestEvent, _Mapping]] = ...) -> None: ...
+
+class ResourceAccessRequest(_message.Message):
+ __slots__ = ("resource_type", "resource_id", "operation", "workspace", "required_access_level", "correlation_id")
+ RESOURCE_TYPE_FIELD_NUMBER: _ClassVar[int]
+ RESOURCE_ID_FIELD_NUMBER: _ClassVar[int]
+ OPERATION_FIELD_NUMBER: _ClassVar[int]
+ WORKSPACE_FIELD_NUMBER: _ClassVar[int]
+ REQUIRED_ACCESS_LEVEL_FIELD_NUMBER: _ClassVar[int]
+ CORRELATION_ID_FIELD_NUMBER: _ClassVar[int]
+ resource_type: str
+ resource_id: str
+ operation: str
+ workspace: str
+ required_access_level: int
+ correlation_id: str
+ def __init__(self, resource_type: _Optional[str] = ..., resource_id: _Optional[str] = ..., operation: _Optional[str] = ..., workspace: _Optional[str] = ..., required_access_level: _Optional[int] = ..., correlation_id: _Optional[str] = ...) -> None: ...
+
+class AccessDecisionReceipt(_message.Message):
+ __slots__ = ("decision_id", "request", "allowed", "decision", "effective_access_level", "actor", "subject", "root_subject", "authority_mode", "grant_id", "root_grant_id", "evaluated_at_ms", "expires_at_ms", "denial_code", "delivery_target")
+ DECISION_ID_FIELD_NUMBER: _ClassVar[int]
+ REQUEST_FIELD_NUMBER: _ClassVar[int]
+ ALLOWED_FIELD_NUMBER: _ClassVar[int]
+ DECISION_FIELD_NUMBER: _ClassVar[int]
+ EFFECTIVE_ACCESS_LEVEL_FIELD_NUMBER: _ClassVar[int]
+ ACTOR_FIELD_NUMBER: _ClassVar[int]
+ SUBJECT_FIELD_NUMBER: _ClassVar[int]
+ ROOT_SUBJECT_FIELD_NUMBER: _ClassVar[int]
+ AUTHORITY_MODE_FIELD_NUMBER: _ClassVar[int]
+ GRANT_ID_FIELD_NUMBER: _ClassVar[int]
+ ROOT_GRANT_ID_FIELD_NUMBER: _ClassVar[int]
+ EVALUATED_AT_MS_FIELD_NUMBER: _ClassVar[int]
+ EXPIRES_AT_MS_FIELD_NUMBER: _ClassVar[int]
+ DENIAL_CODE_FIELD_NUMBER: _ClassVar[int]
+ DELIVERY_TARGET_FIELD_NUMBER: _ClassVar[int]
+ decision_id: str
+ request: ResourceAccessRequest
+ allowed: bool
+ decision: str
+ effective_access_level: int
+ actor: PrincipalRef
+ subject: PrincipalRef
+ root_subject: PrincipalRef
+ authority_mode: str
+ grant_id: str
+ root_grant_id: str
+ evaluated_at_ms: int
+ expires_at_ms: int
+ denial_code: str
+ delivery_target: str
+ def __init__(self, decision_id: _Optional[str] = ..., request: _Optional[_Union[ResourceAccessRequest, _Mapping]] = ..., allowed: _Optional[bool] = ..., decision: _Optional[str] = ..., effective_access_level: _Optional[int] = ..., actor: _Optional[_Union[PrincipalRef, _Mapping]] = ..., subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., root_subject: _Optional[_Union[PrincipalRef, _Mapping]] = ..., authority_mode: _Optional[str] = ..., grant_id: _Optional[str] = ..., root_grant_id: _Optional[str] = ..., evaluated_at_ms: _Optional[int] = ..., expires_at_ms: _Optional[int] = ..., denial_code: _Optional[str] = ..., delivery_target: _Optional[str] = ...) -> None: ...
+
+class AccessCheckOperation(_message.Message):
+ __slots__ = ("request_id", "access", "authorization")
+ REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_FIELD_NUMBER: _ClassVar[int]
+ AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
+ request_id: str
+ access: ResourceAccessRequest
+ authorization: AuthorizationContext
+ def __init__(self, request_id: _Optional[str] = ..., access: _Optional[_Union[ResourceAccessRequest, _Mapping]] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ...) -> None: ...
+
+class AccessCheckResponse(_message.Message):
+ __slots__ = ("request_id", "success", "error", "decision")
+ REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
+ SUCCESS_FIELD_NUMBER: _ClassVar[int]
+ ERROR_FIELD_NUMBER: _ClassVar[int]
+ DECISION_FIELD_NUMBER: _ClassVar[int]
+ request_id: str
+ success: bool
+ error: str
+ decision: AccessDecisionReceipt
+ def __init__(self, request_id: _Optional[str] = ..., success: _Optional[bool] = ..., error: _Optional[str] = ..., decision: _Optional[_Union[AccessDecisionReceipt, _Mapping]] = ...) -> None: ...
+
+class BatchAccessCheckOperation(_message.Message):
+ __slots__ = ("request_id", "access", "authorization")
+ REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
+ ACCESS_FIELD_NUMBER: _ClassVar[int]
+ AUTHORIZATION_FIELD_NUMBER: _ClassVar[int]
+ request_id: str
+ access: _containers.RepeatedCompositeFieldContainer[ResourceAccessRequest]
+ authorization: AuthorizationContext
+ def __init__(self, request_id: _Optional[str] = ..., access: _Optional[_Iterable[_Union[ResourceAccessRequest, _Mapping]]] = ..., authorization: _Optional[_Union[AuthorizationContext, _Mapping]] = ...) -> None: ...
+
+class BatchAccessCheckResponse(_message.Message):
+ __slots__ = ("request_id", "success", "error", "decisions")
+ REQUEST_ID_FIELD_NUMBER: _ClassVar[int]
+ SUCCESS_FIELD_NUMBER: _ClassVar[int]
+ ERROR_FIELD_NUMBER: _ClassVar[int]
+ DECISIONS_FIELD_NUMBER: _ClassVar[int]
+ request_id: str
+ success: bool
+ error: str
+ decisions: _containers.RepeatedCompositeFieldContainer[AccessDecisionReceipt]
+ def __init__(self, request_id: _Optional[str] = ..., success: _Optional[bool] = ..., error: _Optional[str] = ..., decisions: _Optional[_Iterable[_Union[AccessDecisionReceipt, _Mapping]]] = ...) -> None: ...
diff --git a/sdk/python-client/scitrera_aether_client/proxy.py b/sdk/python-client/scitrera_aether_client/proxy.py
index d0f5d22..39384a1 100644
--- a/sdk/python-client/scitrera_aether_client/proxy.py
+++ b/sdk/python-client/scitrera_aether_client/proxy.py
@@ -333,6 +333,7 @@ def _build_request(
body: bytes,
body_chunked: bool,
authorization: Optional[aether_pb2.AuthorizationContext],
+ checked_access: Optional[aether_pb2.ResourceAccessRequest],
app_workspace: Optional[str],
timeout_ms: int,
follow_redirects: bool,
@@ -361,6 +362,10 @@ def _build_request(
req.headers[k] = v
if authorization is not None:
req.authorization.CopyFrom(authorization)
+ if checked_access is not None:
+ req.checked_access.CopyFrom(checked_access)
+ if not req.checked_access.correlation_id:
+ req.checked_access.correlation_id = request_id
return req
@@ -602,6 +607,7 @@ def proxy_http(
app_workspace: Optional[str] = None,
request_id: Optional[str] = None,
authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ checked_access: Optional[aether_pb2.ResourceAccessRequest] = None,
authority_mode: Optional[str] = None,
subject_type: Optional[str] = None,
subject_id: Optional[str] = None,
@@ -654,6 +660,7 @@ def proxy_http(
body=body,
body_chunked=body_chunked,
authorization=auth,
+ checked_access=checked_access,
app_workspace=app_workspace,
timeout_ms=int(timeout * 1000) if timeout else 0,
follow_redirects=follow_redirects,
@@ -742,6 +749,7 @@ async def proxy_http_async(
app_workspace: Optional[str] = None,
request_id: Optional[str] = None,
authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ checked_access: Optional[aether_pb2.ResourceAccessRequest] = None,
authority_mode: Optional[str] = None,
subject_type: Optional[str] = None,
subject_id: Optional[str] = None,
@@ -777,6 +785,7 @@ async def proxy_http_async(
body=body,
body_chunked=body_chunked,
authorization=auth,
+ checked_access=checked_access,
app_workspace=app_workspace,
timeout_ms=int(timeout * 1000) if timeout else 0,
follow_redirects=follow_redirects,
diff --git a/sdk/python-client/scitrera_aether_client/proxy_terminator.py b/sdk/python-client/scitrera_aether_client/proxy_terminator.py
index 31d862d..c059a57 100644
--- a/sdk/python-client/scitrera_aether_client/proxy_terminator.py
+++ b/sdk/python-client/scitrera_aether_client/proxy_terminator.py
@@ -100,6 +100,10 @@ class MintedRequest:
request_id: str
authorization: Optional[aether_pb2.AuthorizationContext] = None
app_workspace: str = ""
+ # Gateway-authored exact-resource decision carried on the Aether
+ # transport envelope. Applications must not reconstruct this from HTTP
+ # headers or request bodies.
+ access_receipt: Optional[aether_pb2.AccessDecisionReceipt] = None
@dataclass
@@ -836,6 +840,9 @@ async def _dispatch(
req.authorization if req.HasField("authorization") else None
),
app_workspace=req.app_workspace,
+ access_receipt=(
+ req.access_receipt if req.HasField("access_receipt") else None
+ ),
)
# Streaming-response path is not yet implemented (out of scope per
@@ -1105,4 +1112,4 @@ def cancel(self): # pass-through used by client.disconnect()
"OBOPolicy",
"Handler",
"HandlerResult",
-]
\ No newline at end of file
+]
diff --git a/sdk/python-client/scitrera_aether_client/types.py b/sdk/python-client/scitrera_aether_client/types.py
index 90a28ad..3edb442 100644
--- a/sdk/python-client/scitrera_aether_client/types.py
+++ b/sdk/python-client/scitrera_aether_client/types.py
@@ -425,6 +425,9 @@ class IncomingMessageLike(Protocol):
Consumers see .source_topic and .payload without knowing it's protobuf."""
source_topic: str
payload: bytes
+ workspace: str
+ access_receipt: aether_pb2.AccessDecisionReceipt
+ forwarded_authorization: aether_pb2.ForwardedAuthorization
@runtime_checkable
diff --git a/sdk/python-client/tests/test_access_check.py b/sdk/python-client/tests/test_access_check.py
new file mode 100644
index 0000000..cfafdeb
--- /dev/null
+++ b/sdk/python-client/tests/test_access_check.py
@@ -0,0 +1,81 @@
+from unittest.mock import AsyncMock, MagicMock
+
+import pytest
+
+from scitrera_aether_client.client import BaseAetherClient
+from scitrera_aether_client.client_async import BaseAsyncAetherClient
+from scitrera_aether_client.proto import aether_pb2
+
+
+def _request(correlation_id: str = "call-1") -> aether_pb2.ResourceAccessRequest:
+ return aether_pb2.ResourceAccessRequest(
+ resource_type="tool-catalog/entry",
+ resource_id="provider-1/tool-1",
+ operation="invoke",
+ workspace="workspace-1",
+ required_access_level=20,
+ correlation_id=correlation_id,
+ )
+
+
+def test_sync_check_access_correlates_and_returns_denial_receipt():
+ client = BaseAetherClient(auto_reconnect=False)
+ response = aether_pb2.AccessCheckResponse(
+ success=True,
+ decision=aether_pb2.AccessDecisionReceipt(
+ allowed=False, denial_code="access_denied", request=_request()
+ ),
+ )
+ client._send_sync_op = MagicMock(return_value=response)
+
+ receipt = client.check_access(_request())
+
+ assert receipt.allowed is False
+ assert receipt.denial_code == "access_denied"
+ upstream, request_id, timeout = client._send_sync_op.call_args.args
+ assert upstream.access_check.request_id == request_id
+ assert upstream.access_check.access.correlation_id == "call-1"
+ assert timeout == 10.0
+
+
+def test_sync_checked_send_wires_authority_and_access_request():
+ client = BaseAetherClient(auto_reconnect=False)
+ authorization = aether_pb2.AuthorizationContext(
+ authority_mode="on_behalf_of",
+ subject=aether_pb2.PrincipalRef(principal_type="user", principal_id="user-1"),
+ grant_id="grant-1",
+ )
+ continuation = aether_pb2.AuthorityContinuationRequest(
+ scope_mode=aether_pb2.AuthorityContinuationRequest.SCOPE_MODE_INHERIT_PARENT,
+ )
+
+ client.send_checked_message(
+ "sv::tools", b"payload", _request(),
+ authorization=authorization, authority_continuation=continuation,
+ )
+
+ upstream = client.request_queue.get_nowait()
+ assert upstream.send.checked_access.resource_id == "provider-1/tool-1"
+ assert upstream.send.authorization.grant_id == "grant-1"
+ assert upstream.send.authority_continuation.scope_mode == continuation.scope_mode
+
+
+@pytest.mark.asyncio
+async def test_async_batch_check_access_returns_ordered_receipts():
+ client = BaseAsyncAetherClient(auto_reconnect=False)
+ response = aether_pb2.BatchAccessCheckResponse(
+ success=True,
+ decisions=[
+ aether_pb2.AccessDecisionReceipt(allowed=True, request=_request("one")),
+ aether_pb2.AccessDecisionReceipt(allowed=False, request=_request("two")),
+ ],
+ )
+ client._send_sync_op = AsyncMock(return_value=response)
+
+ receipts = await client.batch_check_access([_request("one"), _request("two")])
+
+ assert [item.request.correlation_id for item in receipts] == ["one", "two"]
+ upstream, request_id, timeout = client._send_sync_op.call_args.args
+ assert upstream.batch_access_check.request_id == request_id
+ assert len(upstream.batch_access_check.access) == 2
+ assert timeout == 10.0
diff --git a/sdk/python-client/tests/test_client.py b/sdk/python-client/tests/test_client.py
index 34f302c..44d05df 100644
--- a/sdk/python-client/tests/test_client.py
+++ b/sdk/python-client/tests/test_client.py
@@ -38,6 +38,7 @@
OPAQUE,
SELF_ASSIGN,
TARGETED,
+ TARGET_OFFLINE_QUEUE,
create_topic_agent,
create_topic_service,
create_topic_task,
@@ -513,6 +514,7 @@ def test_create_task_self_assign(self):
task_type="echo",
workspace="test-workspace",
metadata={"key": "value"},
+ parent_task_id="parent-123",
)
msg = client.request_queue.get_nowait()
@@ -521,6 +523,7 @@ def test_create_task_self_assign(self):
assert msg.create_task.workspace == "test-workspace"
assert msg.create_task.assignment_mode == SELF_ASSIGN
assert msg.create_task.metadata["key"] == "value"
+ assert msg.create_task.parent_task_id == "parent-123"
def test_create_task_targeted(self):
"""Test task creation with targeted mode."""
@@ -530,12 +533,14 @@ def test_create_task_targeted(self):
task_type="process",
workspace="test-workspace",
target_agent_id="agent-123",
+ target_offline_policy=TARGET_OFFLINE_QUEUE,
)
msg = client.request_queue.get_nowait()
assert msg.HasField("create_task")
assert msg.create_task.assignment_mode == TARGETED
assert msg.create_task.target_agent_id == "agent-123"
+ assert msg.create_task.target_offline_policy == TARGET_OFFLINE_QUEUE
def test_create_task_with_launch_params(self):
"""Test task creation with launch parameter overrides."""
@@ -1617,9 +1622,12 @@ def test_create_task_sync_timeout(self):
task_type="sandbox_lease",
workspace="_apps",
timeout=0.1,
+ parent_task_id="parent-sync",
)
assert result is None
+ msg = client.request_queue.get_nowait()
+ assert msg.create_task.parent_task_id == "parent-sync"
def test_create_task_sync_stamps_request_id_on_request(self):
"""Request enqueued for create_task_sync must carry a non-empty request_id.
@@ -1677,6 +1685,7 @@ def call():
task_type="sandbox_lease",
workspace="default",
authorization=auth,
+ required_downstream_authority_hops=1,
timeout=0.05,
)
@@ -1691,6 +1700,7 @@ def call():
assert req_auth.grant_id == "grant-abc"
assert req_auth.subject.principal_type == "user"
assert req_auth.subject.principal_id == "alice@example.com"
+ assert msg.create_task.required_downstream_authority_hops == 1
thread.join()
@@ -2331,6 +2341,8 @@ def test_create_schedule_sync_queues_workflow_op(self, agent_client: AgentClient
msg, req_id, timeout = agent_client._send_sync_op.call_args[0]
assert msg.HasField("workflow_op")
assert msg.workflow_op.op == aether_pb2.WorkflowOperation.CREATE_SCHEDULE
+ assert msg.workflow_op.id == "sched-1"
+ assert msg.workflow_op.workspace == "ws1"
data = _json.loads(msg.workflow_op.data.decode())
assert data["id"] == "sched-1"
assert data["name"] == "My Schedule"
@@ -2341,6 +2353,33 @@ def test_create_schedule_sync_queues_workflow_op(self, agent_client: AgentClient
assert data["max_concurrent"] == 1
assert timeout == 5.0
+ def test_create_schedule_sync_carries_authority_outside_json(self, agent_client: AgentClient):
+ """Schedule authority is transport-owned and never enters action JSON."""
+ import json as _json
+ agent_client._send_sync_op = MagicMock(return_value=None)
+ authorization = aether_pb2.AuthorizationContext(
+ authority_mode="on_behalf_of",
+ subject=aether_pb2.PrincipalRef(principal_type="user", principal_id="user-a"),
+ grant_id="source-grant",
+ )
+ scope = aether_pb2.WorkflowScheduleAuthorityScope(
+ workspace_scope=["ws1"],
+ operation_scope=["task_create"],
+ max_access_level=20,
+ policy_version=1,
+ )
+ agent_client.create_schedule_sync(
+ schedule_id="sched-auth", name="Authorized", schedule_type="cron",
+ schedule_expr="0 * * * *", workspace="ws1",
+ action={"type": "create_task", "require_task_authority": True},
+ authorization=authorization, authority_scope=scope,
+ )
+ msg, _, _ = agent_client._send_sync_op.call_args[0]
+ assert msg.workflow_op.authorization.grant_id == "source-grant"
+ assert msg.workflow_op.schedule_authority_scope.policy_version == 1
+ data = _json.loads(msg.workflow_op.data.decode())
+ assert "grant_id" not in _json.dumps(data)
+
def test_upsert_schedule_sync_uses_upsert_op(self, agent_client: AgentClient):
"""upsert_schedule_sync puts an UPSERT_SCHEDULE WorkflowOperation."""
agent_client._send_sync_op = MagicMock(return_value=None)
@@ -2350,6 +2389,7 @@ def test_upsert_schedule_sync_uses_upsert_op(self, agent_client: AgentClient):
name="Updated",
schedule_type="interval",
schedule_expr="3600s",
+ workspace="ws1",
)
msg, _, _ = agent_client._send_sync_op.call_args[0]
@@ -2358,10 +2398,11 @@ def test_upsert_schedule_sync_uses_upsert_op(self, agent_client: AgentClient):
def test_delete_schedule_sync_uses_delete_op(self, agent_client: AgentClient):
"""delete_schedule_sync puts a DELETE_SCHEDULE WorkflowOperation."""
agent_client._send_sync_op = MagicMock(return_value=None)
- agent_client.delete_schedule_sync("sched-3", timeout=3.0)
+ agent_client.delete_schedule_sync("sched-3", workspace="ws1", timeout=3.0)
msg, _, timeout = agent_client._send_sync_op.call_args[0]
assert msg.workflow_op.op == aether_pb2.WorkflowOperation.DELETE_SCHEDULE
assert msg.workflow_op.id == "sched-3"
+ assert msg.workflow_op.workspace == "ws1"
assert timeout == 3.0
def test_list_schedules_sync_uses_list_op(self, agent_client: AgentClient):
@@ -2380,7 +2421,7 @@ def test_create_schedule_sync_includes_action_when_provided(self, agent_client:
action = {"type": "launch_agent", "implementation": "myorg/bot"}
agent_client.create_schedule_sync(
schedule_id="s1", name="n", schedule_type="cron",
- schedule_expr="* * * * *", action=action,
+ schedule_expr="* * * * *", workspace="ws1", action=action,
)
msg, _, _ = agent_client._send_sync_op.call_args[0]
data = _json.loads(msg.workflow_op.data.decode())
@@ -2392,7 +2433,7 @@ def test_create_schedule_sync_includes_workflow_id_when_provided(self, agent_cli
agent_client._send_sync_op = MagicMock(return_value=None)
agent_client.create_schedule_sync(
schedule_id="s1", name="n", schedule_type="cron",
- schedule_expr="* * * * *", workflow_id="wf-42",
+ schedule_expr="* * * * *", workspace="ws1", workflow_id="wf-42",
)
msg, _, _ = agent_client._send_sync_op.call_args[0]
data = _json.loads(msg.workflow_op.data.decode())
@@ -2402,10 +2443,15 @@ def test_create_schedule_sync_returns_none_on_timeout(self, agent_client: AgentC
"""create_schedule_sync returns None when the RPC times out."""
result = agent_client.create_schedule_sync(
schedule_id="s1", name="n", schedule_type="cron",
- schedule_expr="* * * * *", timeout=0.01,
+ schedule_expr="* * * * *", workspace="ws1", timeout=0.01,
)
assert result is None
+ def test_schedule_helpers_reject_wildcard_workspace(self, agent_client: AgentClient):
+ """Schedule ACL resources are always scoped to one exact workspace."""
+ with pytest.raises(ValueError, match="exact workflow schedule workspace"):
+ agent_client.list_schedules_sync("*")
+
# =============================================================================
# Audit Query Tests
diff --git a/sdk/python-client/tests/test_client_async.py b/sdk/python-client/tests/test_client_async.py
index 502887f..891dd7e 100644
--- a/sdk/python-client/tests/test_client_async.py
+++ b/sdk/python-client/tests/test_client_async.py
@@ -38,6 +38,7 @@
OPAQUE,
SELF_ASSIGN,
TARGETED,
+ TARGET_OFFLINE_QUEUE,
)
from scitrera_aether_client.exceptions import (
AuthenticationError,
@@ -457,6 +458,7 @@ async def test_create_task_self_assign(self):
task_type="echo",
workspace="test-workspace",
metadata={"key": "value"},
+ parent_task_id="parent-123",
)
msg = client._request_queue.get_nowait()
@@ -465,6 +467,7 @@ async def test_create_task_self_assign(self):
assert msg.create_task.workspace == "test-workspace"
assert msg.create_task.assignment_mode == SELF_ASSIGN
assert msg.create_task.metadata["key"] == "value"
+ assert msg.create_task.parent_task_id == "parent-123"
@pytest.mark.asyncio
async def test_create_task_targeted(self):
@@ -475,12 +478,14 @@ async def test_create_task_targeted(self):
task_type="process",
workspace="test-workspace",
target_agent_id="agent-123",
+ target_offline_policy=TARGET_OFFLINE_QUEUE,
)
msg = client._request_queue.get_nowait()
assert msg.HasField("create_task")
assert msg.create_task.assignment_mode == TARGETED
assert msg.create_task.target_agent_id == "agent-123"
+ assert msg.create_task.target_offline_policy == TARGET_OFFLINE_QUEUE
@pytest.mark.asyncio
async def test_create_task_with_launch_params(self):
@@ -1585,9 +1590,12 @@ async def test_create_task_sync_timeout(self):
task_type="sandbox_lease",
workspace="_apps",
timeout=0.1,
+ parent_task_id="parent-sync",
)
assert result is None
+ msg = client._request_queue.get_nowait()
+ assert msg.create_task.parent_task_id == "parent-sync"
# =============================================================================
@@ -2107,4 +2115,3 @@ async def _mock_stream():
assert errors_received[0].code == "CONNECTION_ERROR"
# Cleanup.
fut.cancel()
-
diff --git a/sdk/python-client/tests/test_proxy.py b/sdk/python-client/tests/test_proxy.py
index f23b25a..cf94c0f 100644
--- a/sdk/python-client/tests/test_proxy.py
+++ b/sdk/python-client/tests/test_proxy.py
@@ -356,6 +356,35 @@ def test_proxy_http_explicit_authorization_passthrough():
assert req.authorization.subject.principal_id == "u1"
+def test_proxy_http_checked_access_is_cloned_and_correlation_is_bound():
+ client = _SyncClientStub()
+ client._proxy_dispatcher = proxy_mod._ProxyDispatcher()
+ request_id = "req-vfs"
+ access = aether_pb2.ResourceAccessRequest(
+ resource_type="vfs",
+ resource_id="workspaces/ws-1/entries/ref-1",
+ operation="read",
+ workspace="ws-1",
+ required_access_level=10,
+ )
+
+ _stream_response_into(client, request_id, b"ok", chunked=False, delay=0.05)
+ proxy_http(
+ client,
+ target_topic="sv::data-connectors",
+ method="GET",
+ path="/v1/vfs/ref-1",
+ timeout=5.0,
+ request_id=request_id,
+ checked_access=access,
+ )
+
+ req = client.drain_upstream()[0].proxy_http_request
+ assert req.checked_access.resource_id == "workspaces/ws-1/entries/ref-1"
+ assert req.checked_access.correlation_id == request_id
+ assert access.correlation_id == ""
+
+
def test_proxy_http_no_authorization_when_unspecified():
client = _SyncClientStub()
client._proxy_dispatcher = proxy_mod._ProxyDispatcher()
diff --git a/sdk/python-client/tests/test_proxy_terminator.py b/sdk/python-client/tests/test_proxy_terminator.py
index 34b4dba..d9e85af 100644
--- a/sdk/python-client/tests/test_proxy_terminator.py
+++ b/sdk/python-client/tests/test_proxy_terminator.py
@@ -72,6 +72,7 @@ def _build_request(
body: bytes = b"",
body_chunked: bool = False,
authorization: Optional[aether_pb2.AuthorizationContext] = None,
+ access_receipt: Optional[aether_pb2.AccessDecisionReceipt] = None,
app_workspace: str = "",
stream_response_indefinitely: bool = False,
) -> aether_pb2.ProxyHttpRequest:
@@ -90,6 +91,8 @@ def _build_request(
req.headers[k] = v
if authorization is not None:
req.authorization.CopyFrom(authorization)
+ if access_receipt is not None:
+ req.access_receipt.CopyFrom(access_receipt)
return req
@@ -743,6 +746,43 @@ async def handler(req: MintedRequest) -> aether_pb2.ProxyHttpResponse:
assert captured[0].query == "q=hello&limit=10"
+@pytest.mark.asyncio
+async def test_minted_request_preserves_gateway_access_receipt():
+ client = _AsyncClientStub()
+ captured: List[MintedRequest] = []
+
+ async def handler(req: MintedRequest) -> aether_pb2.ProxyHttpResponse:
+ captured.append(req)
+ return aether_pb2.ProxyHttpResponse(request_id=req.request_id, status_code=200)
+
+ receipt = aether_pb2.AccessDecisionReceipt(
+ decision_id="decision-1",
+ allowed=True,
+ decision="ALLOW",
+ delivery_target="sv::data-connectors::one",
+ request=aether_pb2.ResourceAccessRequest(
+ resource_type="vfs",
+ resource_id="workspaces/ws-1/entries/ref-1",
+ operation="read",
+ workspace="ws-1",
+ required_access_level=10,
+ correlation_id="req-receipt",
+ ),
+ )
+ term = ProxyHttpTerminator(client=client, handler=handler, allow_paths=["/*"])
+ await term.start()
+
+ dispatcher = _get_terminator_dispatcher(client)
+ await dispatcher.handle_request(
+ _build_request("req-receipt", access_receipt=receipt)
+ )
+ await dispatcher.wait_idle()
+
+ assert captured[0].access_receipt is not None
+ assert captured[0].access_receipt.decision_id == "decision-1"
+ assert captured[0].access_receipt.request.resource_type == "vfs"
+
+
@pytest.mark.asyncio
async def test_handler_returning_triple_is_coerced():
client = _AsyncClientStub()
diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json
index d1a89e3..7806493 100644
--- a/sdk/typescript/package.json
+++ b/sdk/typescript/package.json
@@ -1,6 +1,6 @@
{
"name": "@scitrera/aether-client",
- "version": "0.2.2",
+ "version": "0.2.3",
"description": "TypeScript/JavaScript SDK for the Aether distributed control plane",
"license": "Apache-2.0",
"author": "scitrera.ai",
diff --git a/sdk/typescript/src/__tests__/client.test.ts b/sdk/typescript/src/__tests__/client.test.ts
index 2e854ee..e96ba69 100644
--- a/sdk/typescript/src/__tests__/client.test.ts
+++ b/sdk/typescript/src/__tests__/client.test.ts
@@ -5,6 +5,7 @@ import {
MessageType,
KVScope,
TaskAssignmentMode,
+ TargetOfflinePolicy,
SignalType,
// Topic helpers
agentTopic,
@@ -98,6 +99,15 @@ describe("PrincipalType", () => {
});
});
+describe("TargetOfflinePolicy", () => {
+ it("matches the protobuf wire values", () => {
+ expect(TargetOfflinePolicy.Unspecified).toBe(0);
+ expect(TargetOfflinePolicy.Orchestrate).toBe(1);
+ expect(TargetOfflinePolicy.Queue).toBe(2);
+ expect(TargetOfflinePolicy.Reject).toBe(3);
+ });
+});
+
describe("MessageType", () => {
it("has all expected values", () => {
expect(MessageType.Unspecified).toBe(0);
@@ -127,6 +137,170 @@ describe("TaskAssignmentMode", () => {
});
});
+describe("TaskAssignment delivery", () => {
+ it("maps payload, resume fields, and typed authorization", () => {
+ const client = new AetherClient({ address: "localhost:50051" });
+ let received: Parameters[0]>[0] | undefined;
+ client.onTaskAssignment((assignment) => {
+ received = assignment;
+ });
+
+ (client as any)._handleDownstreamMessage({
+ taskAssignment: {
+ taskId: "task-1",
+ taskType: "worker",
+ assignedTo: "ag::prod::worker::one",
+ payload: new Uint8Array([1, 2, 3]),
+ taskClass: 2,
+ checkpointKey: "checkpoint-1",
+ resumeSessionId: "session-1",
+ authorization: {
+ authorityMode: "on_behalf_of",
+ subject: { principalType: "user", principalId: "alice" },
+ grantId: "grant-1",
+ resolved: {
+ rootSubject: { principalType: "user", principalId: "alice" },
+ audienceType: "task",
+ audienceId: "task-1",
+ maxAccessLevel: 20,
+ workspaceScope: ["prod"],
+ expiresAtMs: 1234,
+ },
+ },
+ },
+ });
+
+ expect(received?.payload).toEqual(new Uint8Array([1, 2, 3]));
+ expect(received?.taskClass).toBe(2);
+ expect(received?.checkpointKey).toBe("checkpoint-1");
+ expect(received?.resumeSessionId).toBe("session-1");
+ expect(received?.authorization).toEqual({
+ authorityMode: "on_behalf_of",
+ subject: { principalType: "user", principalId: "alice" },
+ grantId: "grant-1",
+ resolved: {
+ rootSubject: { principalType: "user", principalId: "alice" },
+ audienceType: "task",
+ audienceId: "task-1",
+ maxAccessLevel: 20,
+ workspaceScope: ["prod"],
+ expiresAtMs: 1234,
+ },
+ });
+ });
+});
+
+describe("runtime access checks", () => {
+ const access = {
+ resourceType: "tool-catalog/entry",
+ resourceId: "provider-1/tool-1",
+ operation: "invoke",
+ workspace: "workspace-1",
+ requiredAccessLevel: 20,
+ correlationId: "call-1",
+ };
+
+ it("correlates a single denial as a normal decision", async () => {
+ const client = new AetherClient({ address: "localhost:50051" });
+ let upstream: any;
+ (client as any)._stream = { write: (message: any) => { upstream = message; } };
+
+ const result = client.checkAccess(access);
+ const requestId = upstream.accessCheck.requestId;
+ expect(upstream.accessCheck.access).toEqual(access);
+
+ (client as any)._handleDownstreamMessage({
+ accessCheckResponse: {
+ requestId,
+ success: true,
+ decision: {
+ decisionId: "decision-1",
+ request: access,
+ allowed: false,
+ decision: "DENY",
+ denialCode: "access_denied",
+ expiresAtMs: "1786478400000",
+ },
+ },
+ });
+
+ await expect(result).resolves.toMatchObject({
+ decisionId: "decision-1",
+ allowed: false,
+ denialCode: "access_denied",
+ request: access,
+ });
+ });
+
+ it("surfaces workspace, OBO subject, and checked-send receipt", () => {
+ const client = new AetherClient({ address: "localhost:50051" });
+ let received: any;
+ client.onMessage((message) => { received = message; });
+
+ (client as any)._handleDownstreamMessage({
+ msg: {
+ sourceTopic: "sv::tools::one",
+ payload: new Uint8Array([1]),
+ workspace: "workspace-1",
+ onBehalfSubject: { principalType: "user", principalId: "user-1" },
+ accessReceipt: {
+ decisionId: "decision-1",
+ request: access,
+ allowed: true,
+ decision: "ALLOW",
+ deliveryTarget: "sv::tools::one",
+ },
+ forwardedAuthorization: {
+ authorization: {
+ authorityMode: "on_behalf_of",
+ subject: { principalType: "user", principalId: "user-1" },
+ grantId: "child-grant-1",
+ },
+ rootGrantId: "root-grant-1",
+ expiresAtMs: "1786478400000",
+ deliveryTarget: "sv::tools::one",
+ bindingId: "call-1",
+ scope: {
+ workspaceScope: ["workspace-1"],
+ resourceScope: [{ resourceType: "vfs", patterns: ["workspace-1/*"] }],
+ operationScope: ["read"],
+ maxAccessLevel: 10,
+ },
+ },
+ },
+ });
+
+ expect(received.workspace).toBe("workspace-1");
+ expect(received.onBehalfSubject).toEqual({ principalType: "user", principalId: "user-1" });
+ expect(received.accessReceipt).toMatchObject({
+ decisionId: "decision-1",
+ allowed: true,
+ deliveryTarget: "sv::tools::one",
+ });
+ expect(received.forwardedAuthorization).toMatchObject({
+ authorization: { grantId: "child-grant-1" },
+ rootGrantId: "root-grant-1",
+ expiresAtMs: 1786478400000,
+ deliveryTarget: "sv::tools::one",
+ bindingId: "call-1",
+ scope: { workspaceScope: ["workspace-1"], operationScope: ["read"], maxAccessLevel: 10 },
+ });
+ });
+
+ it("threads an explicit authority-continuation request onto sends", async () => {
+ const client = new AetherClient({ address: "localhost:50051" });
+ let upstream: any;
+ (client as any)._connected = true;
+ (client as any)._stream = { write: (message: any) => { upstream = message; } };
+ await client.send({
+ targetTopic: "sv::tool-catalog",
+ payload: new Uint8Array([1]),
+ authorityContinuation: { scopeMode: 1 },
+ });
+ expect(upstream.send.authorityContinuation.scopeMode).toBe(1);
+ });
+});
+
describe("SignalType", () => {
it("has expected values", () => {
expect(SignalType.ForceDisconnect).toBe(0);
diff --git a/sdk/typescript/src/__tests__/proxy.test.ts b/sdk/typescript/src/__tests__/proxy.test.ts
index 329cede..bbaa29a 100644
--- a/sdk/typescript/src/__tests__/proxy.test.ts
+++ b/sdk/typescript/src/__tests__/proxy.test.ts
@@ -405,3 +405,29 @@ describe("proxyHttp backend option", () => {
expect(msg.proxyHttpRequest["backendName"]).toBe("primary");
});
});
+
+describe("proxyHttp checked access", () => {
+ it("clones the descriptor and binds an empty correlation ID", () => {
+ const client = makeClient();
+ const checkedAccess = {
+ resourceType: "vfs",
+ resourceId: "workspaces/ws-1/entries/ref-1",
+ operation: "read",
+ workspace: "ws-1",
+ requiredAccessLevel: 10,
+ correlationId: "",
+ };
+
+ void proxyHttp(client, "sv::data-connectors", "GET", "/v1/vfs/ref-1", {
+ appWorkspace: "ws-1",
+ checkedAccess,
+ });
+
+ const msg = client._sentMessages[0] as { proxyHttpRequest: Record };
+ const emitted = msg.proxyHttpRequest["checkedAccess"] as Record;
+ expect(emitted["correlationId"]).toBe(msg.proxyHttpRequest["requestId"]);
+ expect(msg.proxyHttpRequest["appWorkspace"]).toBe("ws-1");
+ expect(checkedAccess.correlationId).toBe("");
+ expect(emitted).not.toBe(checkedAccess);
+ });
+});
diff --git a/sdk/typescript/src/agents.ts b/sdk/typescript/src/agents.ts
index add6381..44083aa 100644
--- a/sdk/typescript/src/agents.ts
+++ b/sdk/typescript/src/agents.ts
@@ -11,7 +11,7 @@
import { AetherClient } from "./client.js";
import type { AetherClientOptions } from "./client.js";
-import { MessageType, TaskAssignmentMode, TaskPriority } from "./types.js";
+import { MessageType, TargetOfflinePolicy, TaskAssignmentMode, TaskPriority } from "./types.js";
import type { MessageHandler } from "./types.js";
import { InvalidArgumentError } from "./errors.js";
import {
@@ -57,6 +57,8 @@ export interface CreateTaskOptions {
workspace?: string;
/** For TARGETED mode: the agent to assign to. */
targetAgentId?: string;
+ /** TARGETED behavior while the exact target is disconnected. */
+ targetOfflinePolicy?: TargetOfflinePolicy;
/** For POOL mode: the agent implementation type to match. */
targetImplementation?: string;
/** Optional parameter overrides for orchestration. */
@@ -65,12 +67,19 @@ export interface CreateTaskOptions {
metadata?: Record;
/** Assignment mode. Default: SelfAssign. */
assignmentMode?: TaskAssignmentMode;
+ /**
+ * Optional active parent assigned to this calling identity. The gateway
+ * validates and applies the binding only to this creation request.
+ */
+ parentTaskId?: string;
/**
* Optional dispatch priority. Higher priority pending tasks are delivered
* before lower ones. Defaults to Unspecified, which the server normalizes
* to Normal.
*/
priority?: TaskPriority;
+ /** Delegation capacity the final worker must retain. Currently 0 or 1. */
+ requiredDownstreamAuthorityHops?: number;
}
// =============================================================================
@@ -390,10 +399,13 @@ export class AgentClient extends AetherClient {
workspace,
assignmentMode,
targetAgentId: opts.targetAgentId ?? "",
+ targetOfflinePolicy: opts.targetOfflinePolicy ?? TargetOfflinePolicy.Unspecified,
targetImplementation: opts.targetImplementation ?? "",
launchParamOverrides: opts.launchParamOverrides ?? {},
metadata: opts.metadata ?? {},
+ parentTaskId: opts.parentTaskId ?? "",
priority: opts.priority ?? TaskPriority.Unspecified,
+ requiredDownstreamAuthorityHops: opts.requiredDownstreamAuthorityHops ?? 0,
},
});
}
diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts
index 1e60119..101743e 100644
--- a/sdk/typescript/src/client.ts
+++ b/sdk/typescript/src/client.ts
@@ -65,6 +65,10 @@ import type {
TokenInfo,
AuditSubmitResponse,
AuditSubmitResponseHandler,
+ AuthorizationContext,
+ ResourceAccessRequest,
+ AccessDecisionReceipt,
+ PrincipalRef,
} from "./types.js";
import { MessageType, KVScope, SignalType } from "./types.js";
import {
@@ -262,6 +266,8 @@ export class AetherClient {
private _pendingWorkflowRequests = new Map void>();
private _pendingAuditSubmitRequests = new Map void>();
private _pendingTokenRequests = new Map void>();
+ private _pendingAccessCheckRequests = new Map void>();
+ private _pendingBatchAccessCheckRequests = new Map void>();
// Proxy HTTP pending requests: request_id → resolver
// @internal
@@ -487,10 +493,54 @@ export class AetherClient {
targetTopic: message.targetTopic,
payload: message.payload,
messageType: message.messageType ?? MessageType.Opaque,
+ appWorkspace: message.appWorkspace ?? "",
+ authorization: message.authorization,
+ checkedAccess: message.checkedAccess,
+ authorityContinuation: message.authorityContinuation,
},
});
}
+ /** Evaluate one exact logical resource. Denial resolves normally with allowed=false. */
+ checkAccess(access: ResourceAccessRequest, authorization?: AuthorizationContext, timeout = 10000): Promise {
+ const requestId = this.nextRequestId();
+ return new Promise((resolve, reject) => {
+ const timer = setTimeout(() => {
+ this._pendingAccessCheckRequests.delete(requestId);
+ reject(new Error("access check timed out"));
+ }, timeout);
+ this._pendingAccessCheckRequests.set(requestId, (response) => {
+ clearTimeout(timer);
+ if (!response.success || !response.decision) {
+ reject(new InvalidArgumentError(response.error || "access check failed", "access"));
+ return;
+ }
+ resolve(response.decision);
+ });
+ this._sendUpstream({ accessCheck: { requestId, access, authorization } });
+ });
+ }
+
+ /** Evaluate 1-100 exact logical resources, preserving input order. */
+ batchCheckAccess(access: ResourceAccessRequest[], authorization?: AuthorizationContext, timeout = 10000): Promise {
+ const requestId = this.nextRequestId();
+ return new Promise((resolve, reject) => {
+ const timer = setTimeout(() => {
+ this._pendingBatchAccessCheckRequests.delete(requestId);
+ reject(new Error("batch access check timed out"));
+ }, timeout);
+ this._pendingBatchAccessCheckRequests.set(requestId, (response) => {
+ clearTimeout(timer);
+ if (!response.success) {
+ reject(new InvalidArgumentError(response.error || "batch access check failed", "access"));
+ return;
+ }
+ resolve(response.decisions);
+ });
+ this._sendUpstream({ batchAccessCheck: { requestId, access, authorization } });
+ });
+ }
+
/**
* Sends a KV operation through the gateway.
* @internal Used by KVClient.
@@ -989,6 +1039,10 @@ export class AetherClient {
sourceTopic: String(msg["sourceTopic"] ?? msg["source_topic"] ?? ""),
payload: msg["payload"] instanceof Uint8Array ? msg["payload"] : new Uint8Array(),
messageType: Number(msg["messageType"] ?? msg["message_type"] ?? 0),
+ workspace: String(msg["workspace"] ?? ""),
+ onBehalfSubject: this._parsePrincipalRef(msg["onBehalfSubject"] ?? msg["on_behalf_subject"]),
+ accessReceipt: this._parseAccessReceipt(msg["accessReceipt"] ?? msg["access_receipt"]),
+ forwardedAuthorization: this._parseForwardedAuthorization(msg["forwardedAuthorization"] ?? msg["forwarded_authorization"]),
receivedAt: new Date(),
};
this._onMessage(incoming);
@@ -1092,6 +1146,22 @@ export class AetherClient {
if (data["taskAssignment"] || data["task_assignment"]) {
const ta = (data["taskAssignment"] ?? data["task_assignment"]) as Record;
+ const rawAuthorization = ta["authorization"];
+ const authorization = rawAuthorization && typeof rawAuthorization === "object"
+ ? rawAuthorization as Record
+ : undefined;
+ const rawSubject = authorization?.["subject"];
+ const subject = rawSubject && typeof rawSubject === "object"
+ ? rawSubject as Record
+ : undefined;
+ const rawResolved = authorization?.["resolved"];
+ const resolved = rawResolved && typeof rawResolved === "object"
+ ? rawResolved as Record
+ : undefined;
+ const rawRootSubject = resolved?.["rootSubject"] ?? resolved?.["root_subject"];
+ const rootSubject = rawRootSubject && typeof rawRootSubject === "object"
+ ? rawRootSubject as Record
+ : undefined;
const assignment: TaskAssignment = {
taskId: String(ta["taskId"] ?? ta["task_id"] ?? ""),
taskType: String(ta["taskType"] ?? ta["task_type"] ?? ""),
@@ -1103,6 +1173,31 @@ export class AetherClient {
targetImplementation: String(ta["targetImplementation"] ?? ta["target_implementation"] ?? ""),
workspace: String(ta["workspace"] ?? ""),
specifier: String(ta["specifier"] ?? ""),
+ payload: ta["payload"] instanceof Uint8Array ? new Uint8Array(ta["payload"]) : new Uint8Array(),
+ taskClass: Number(ta["taskClass"] ?? ta["task_class"] ?? 0),
+ checkpointKey: String(ta["checkpointKey"] ?? ta["checkpoint_key"] ?? ""),
+ resumeSessionId: String(ta["resumeSessionId"] ?? ta["resume_session_id"] ?? ""),
+ authorization: authorization ? {
+ authorityMode: String(authorization["authorityMode"] ?? authorization["authority_mode"] ?? ""),
+ subject: subject ? {
+ principalType: String(subject["principalType"] ?? subject["principal_type"] ?? ""),
+ principalId: String(subject["principalId"] ?? subject["principal_id"] ?? ""),
+ } : undefined,
+ grantId: String(authorization["grantId"] ?? authorization["grant_id"] ?? ""),
+ resolved: resolved ? {
+ rootSubject: rootSubject ? {
+ principalType: String(rootSubject["principalType"] ?? rootSubject["principal_type"] ?? ""),
+ principalId: String(rootSubject["principalId"] ?? rootSubject["principal_id"] ?? ""),
+ } : undefined,
+ audienceType: String(resolved["audienceType"] ?? resolved["audience_type"] ?? ""),
+ audienceId: String(resolved["audienceId"] ?? resolved["audience_id"] ?? ""),
+ maxAccessLevel: Number(resolved["maxAccessLevel"] ?? resolved["max_access_level"] ?? 0),
+ workspaceScope: Array.isArray(resolved["workspaceScope"] ?? resolved["workspace_scope"])
+ ? ((resolved["workspaceScope"] ?? resolved["workspace_scope"]) as unknown[]).map(String)
+ : [],
+ expiresAtMs: Number(resolved["expiresAtMs"] ?? resolved["expires_at_ms"] ?? 0),
+ } : undefined,
+ } : undefined,
};
this._onTaskAssignment(assignment);
return;
@@ -1293,6 +1388,31 @@ export class AetherClient {
return;
}
+ if (data["accessCheckResponse"] || data["access_check_response"]) {
+ const raw = (data["accessCheckResponse"] ?? data["access_check_response"]) as Record;
+ const requestId = String(raw["requestId"] ?? raw["request_id"] ?? "");
+ const receipt = this._parseAccessReceipt(raw["decision"]);
+ const pending = this._pendingAccessCheckRequests.get(requestId);
+ if (pending) {
+ this._pendingAccessCheckRequests.delete(requestId);
+ pending({ success: Boolean(raw["success"]), error: String(raw["error"] ?? ""), decision: receipt });
+ }
+ return;
+ }
+
+ if (data["batchAccessCheckResponse"] || data["batch_access_check_response"]) {
+ const raw = (data["batchAccessCheckResponse"] ?? data["batch_access_check_response"]) as Record;
+ const requestId = String(raw["requestId"] ?? raw["request_id"] ?? "");
+ const rawDecisions = Array.isArray(raw["decisions"]) ? raw["decisions"] as unknown[] : [];
+ const decisions = rawDecisions.map((item) => this._parseAccessReceipt(item)).filter((item): item is AccessDecisionReceipt => item !== undefined);
+ const pending = this._pendingBatchAccessCheckRequests.get(requestId);
+ if (pending) {
+ this._pendingBatchAccessCheckRequests.delete(requestId);
+ pending({ success: Boolean(raw["success"]), error: String(raw["error"] ?? ""), decisions });
+ }
+ return;
+ }
+
if (data["authorityGrantRevocation"] || data["authority_grant_revocation"]) {
const raw = (data["authorityGrantRevocation"] ?? data["authority_grant_revocation"]) as Record;
const evt: AuthorityGrantRevocation = {
@@ -1754,6 +1874,91 @@ export class AetherClient {
});
}
+ private _parsePrincipalRef(value: unknown): PrincipalRef | undefined {
+ if (!value || typeof value !== "object") return undefined;
+ const raw = value as Record;
+ const principalType = String(raw["principalType"] ?? raw["principal_type"] ?? "");
+ const principalId = String(raw["principalId"] ?? raw["principal_id"] ?? "");
+ return principalType && principalId ? { principalType, principalId } : undefined;
+ }
+
+ private _parseForwardedAuthorization(value: unknown): import("./types.js").ForwardedAuthorization | undefined {
+ if (!value || typeof value !== "object") return undefined;
+ const raw = value as Record;
+ const authRaw = raw["authorization"];
+ if (!authRaw || typeof authRaw !== "object") return undefined;
+ const auth = authRaw as Record;
+ const authorization: AuthorizationContext = {
+ authorityMode: String(auth["authorityMode"] ?? auth["authority_mode"] ?? ""),
+ subject: this._parsePrincipalRef(auth["subject"]),
+ grantId: String(auth["grantId"] ?? auth["grant_id"] ?? ""),
+ };
+ if (!authorization.authorityMode || !authorization.grantId) return undefined;
+ return {
+ authorization,
+ rootGrantId: String(raw["rootGrantId"] ?? raw["root_grant_id"] ?? ""),
+ expiresAtMs: Number(raw["expiresAtMs"] ?? raw["expires_at_ms"] ?? 0),
+ deliveryTarget: String(raw["deliveryTarget"] ?? raw["delivery_target"] ?? ""),
+ bindingId: String(raw["bindingId"] ?? raw["binding_id"] ?? ""),
+ scope: this._parseAuthorityContinuationScope(raw["scope"]),
+ };
+ }
+
+ private _parseAuthorityContinuationScope(value: unknown): import("./types.js").AuthorityContinuationScope {
+ const raw = value && typeof value === "object" ? value as Record : {};
+ const resourcesRaw = raw["resourceScope"] ?? raw["resource_scope"];
+ const resourceScope = Array.isArray(resourcesRaw) ? resourcesRaw.map((item) => {
+ const resource = item && typeof item === "object" ? item as Record : {};
+ const patterns = resource["patterns"];
+ return {
+ resourceType: String(resource["resourceType"] ?? resource["resource_type"] ?? ""),
+ patterns: Array.isArray(patterns) ? patterns.map(String) : [],
+ };
+ }) : [];
+ const workspaces = raw["workspaceScope"] ?? raw["workspace_scope"];
+ const operations = raw["operationScope"] ?? raw["operation_scope"];
+ return {
+ workspaceScope: Array.isArray(workspaces) ? workspaces.map(String) : [],
+ resourceScope,
+ operationScope: Array.isArray(operations) ? operations.map(String) : [],
+ maxAccessLevel: Number(raw["maxAccessLevel"] ?? raw["max_access_level"] ?? 0),
+ };
+ }
+
+ private _parseAccessRequest(value: unknown): ResourceAccessRequest {
+ const raw = value && typeof value === "object" ? value as Record : {};
+ return {
+ resourceType: String(raw["resourceType"] ?? raw["resource_type"] ?? ""),
+ resourceId: String(raw["resourceId"] ?? raw["resource_id"] ?? ""),
+ operation: String(raw["operation"] ?? ""),
+ workspace: String(raw["workspace"] ?? ""),
+ requiredAccessLevel: Number(raw["requiredAccessLevel"] ?? raw["required_access_level"] ?? 0),
+ correlationId: String(raw["correlationId"] ?? raw["correlation_id"] ?? ""),
+ };
+ }
+
+ private _parseAccessReceipt(value: unknown): AccessDecisionReceipt | undefined {
+ if (!value || typeof value !== "object") return undefined;
+ const raw = value as Record;
+ return {
+ decisionId: String(raw["decisionId"] ?? raw["decision_id"] ?? ""),
+ request: this._parseAccessRequest(raw["request"]),
+ allowed: Boolean(raw["allowed"]),
+ decision: String(raw["decision"] ?? ""),
+ effectiveAccessLevel: Number(raw["effectiveAccessLevel"] ?? raw["effective_access_level"] ?? 0),
+ actor: this._parsePrincipalRef(raw["actor"]),
+ subject: this._parsePrincipalRef(raw["subject"]),
+ rootSubject: this._parsePrincipalRef(raw["rootSubject"] ?? raw["root_subject"]),
+ authorityMode: String(raw["authorityMode"] ?? raw["authority_mode"] ?? ""),
+ grantId: String(raw["grantId"] ?? raw["grant_id"] ?? ""),
+ rootGrantId: String(raw["rootGrantId"] ?? raw["root_grant_id"] ?? ""),
+ evaluatedAtMs: Number(raw["evaluatedAtMs"] ?? raw["evaluated_at_ms"] ?? 0),
+ expiresAtMs: Number(raw["expiresAtMs"] ?? raw["expires_at_ms"] ?? 0),
+ denialCode: String(raw["denialCode"] ?? raw["denial_code"] ?? ""),
+ deliveryTarget: String(raw["deliveryTarget"] ?? raw["delivery_target"] ?? ""),
+ };
+ }
+
// ===========================================================================
// Task Management
// ===========================================================================
diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts
index 285d30b..8d31b39 100644
--- a/sdk/typescript/src/index.ts
+++ b/sdk/typescript/src/index.ts
@@ -80,7 +80,7 @@ export type { UserClientOptions } from "./users.js";
export { OrchestratorClient, BaseOrchestrator } from "./orchestrator.js";
export type { OrchestratorClientOptions, BaseOrchestratorOptions } from "./orchestrator.js";
-export { WorkflowEngineClient } from "./workflow.js";
+export { WorkflowEngineClient, WORKFLOW_SCHEDULE_AUTHORITY_POLICY_VERSION } from "./workflow.js";
export type { WorkflowEngineClientOptions } from "./workflow.js";
export { MetricsBridgeClient } from "./metrics.js";
@@ -108,6 +108,8 @@ export {
MessageType,
KVScope,
TaskAssignmentMode,
+ TargetOfflinePolicy,
+ AuthorityContinuationScopeMode,
SignalType,
} from "./types.js";
@@ -115,11 +117,21 @@ export type {
// Message structures
IncomingMessage,
OutgoingMessage,
+ PrincipalRef,
+ AuthorizationContext,
+ ForwardedAuthorization,
+ AuthorityContinuationScope,
+ AuthorityContinuationRequest,
+ AuthorityGrantResourceScopeEntry,
+ ResourceAccessRequest,
+ AccessDecisionReceipt,
ConfigSnapshot,
Signal,
ErrorResponse,
ConnectionAck,
TaskAssignment,
+ TaskAssignmentAuthorization,
+ TaskAssignmentResolvedAuthority,
// KV types
KVResponse,
KVGetOptions,
diff --git a/sdk/typescript/src/proto/aether.ts b/sdk/typescript/src/proto/aether.ts
index f32c803..e401622 100644
--- a/sdk/typescript/src/proto/aether.ts
+++ b/sdk/typescript/src/proto/aether.ts
@@ -26,6 +26,9 @@ import type { ACLRoleRequest as _aether_v1_ACLRoleRequest, ACLRoleRequest__Outpu
import type { ACLRuleFilter as _aether_v1_ACLRuleFilter, ACLRuleFilter__Output as _aether_v1_ACLRuleFilter__Output } from './aether/v1/ACLRuleFilter';
import type { ACLRuleInfo as _aether_v1_ACLRuleInfo, ACLRuleInfo__Output as _aether_v1_ACLRuleInfo__Output } from './aether/v1/ACLRuleInfo';
import type { ACLSetFallbackRequest as _aether_v1_ACLSetFallbackRequest, ACLSetFallbackRequest__Output as _aether_v1_ACLSetFallbackRequest__Output } from './aether/v1/ACLSetFallbackRequest';
+import type { AccessCheckOperation as _aether_v1_AccessCheckOperation, AccessCheckOperation__Output as _aether_v1_AccessCheckOperation__Output } from './aether/v1/AccessCheckOperation';
+import type { AccessCheckResponse as _aether_v1_AccessCheckResponse, AccessCheckResponse__Output as _aether_v1_AccessCheckResponse__Output } from './aether/v1/AccessCheckResponse';
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from './aether/v1/AccessDecisionReceipt';
import type { AdminQuery as _aether_v1_AdminQuery, AdminQuery__Output as _aether_v1_AdminQuery__Output } from './aether/v1/AdminQuery';
import type { AdminResponse as _aether_v1_AdminResponse, AdminResponse__Output as _aether_v1_AdminResponse__Output } from './aether/v1/AdminResponse';
import type { AetherGatewayClient as _aether_v1_AetherGatewayClient, AetherGatewayDefinition as _aether_v1_AetherGatewayDefinition } from './aether/v1/AetherGateway';
@@ -40,6 +43,8 @@ import type { AgentResponse as _aether_v1_AgentResponse, AgentResponse__Output a
import type { AuditEntry as _aether_v1_AuditEntry, AuditEntry__Output as _aether_v1_AuditEntry__Output } from './aether/v1/AuditEntry';
import type { AuditQuery as _aether_v1_AuditQuery, AuditQuery__Output as _aether_v1_AuditQuery__Output } from './aether/v1/AuditQuery';
import type { AuditQueryResponse as _aether_v1_AuditQueryResponse, AuditQueryResponse__Output as _aether_v1_AuditQueryResponse__Output } from './aether/v1/AuditQueryResponse';
+import type { AuthorityContinuationRequest as _aether_v1_AuthorityContinuationRequest, AuthorityContinuationRequest__Output as _aether_v1_AuthorityContinuationRequest__Output } from './aether/v1/AuthorityContinuationRequest';
+import type { AuthorityContinuationScope as _aether_v1_AuthorityContinuationScope, AuthorityContinuationScope__Output as _aether_v1_AuthorityContinuationScope__Output } from './aether/v1/AuthorityContinuationScope';
import type { AuthorityGrantBatchExchangeRequest as _aether_v1_AuthorityGrantBatchExchangeRequest, AuthorityGrantBatchExchangeRequest__Output as _aether_v1_AuthorityGrantBatchExchangeRequest__Output } from './aether/v1/AuthorityGrantBatchExchangeRequest';
import type { AuthorityGrantDeriveForTargetRequest as _aether_v1_AuthorityGrantDeriveForTargetRequest, AuthorityGrantDeriveForTargetRequest__Output as _aether_v1_AuthorityGrantDeriveForTargetRequest__Output } from './aether/v1/AuthorityGrantDeriveForTargetRequest';
import type { AuthorityGrantDeriveRequest as _aether_v1_AuthorityGrantDeriveRequest, AuthorityGrantDeriveRequest__Output as _aether_v1_AuthorityGrantDeriveRequest__Output } from './aether/v1/AuthorityGrantDeriveRequest';
@@ -59,6 +64,8 @@ import type { AuthorityRequestResourceScopeEntry as _aether_v1_AuthorityRequestR
import type { AuthorityRequestRoutingTarget as _aether_v1_AuthorityRequestRoutingTarget, AuthorityRequestRoutingTarget__Output as _aether_v1_AuthorityRequestRoutingTarget__Output } from './aether/v1/AuthorityRequestRoutingTarget';
import type { AuthoritySpan as _aether_v1_AuthoritySpan, AuthoritySpan__Output as _aether_v1_AuthoritySpan__Output } from './aether/v1/AuthoritySpan';
import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from './aether/v1/AuthorizationContext';
+import type { BatchAccessCheckOperation as _aether_v1_BatchAccessCheckOperation, BatchAccessCheckOperation__Output as _aether_v1_BatchAccessCheckOperation__Output } from './aether/v1/BatchAccessCheckOperation';
+import type { BatchAccessCheckResponse as _aether_v1_BatchAccessCheckResponse, BatchAccessCheckResponse__Output as _aether_v1_BatchAccessCheckResponse__Output } from './aether/v1/BatchAccessCheckResponse';
import type { BridgeIdentity as _aether_v1_BridgeIdentity, BridgeIdentity__Output as _aether_v1_BridgeIdentity__Output } from './aether/v1/BridgeIdentity';
import type { BuildInfo as _aether_v1_BuildInfo, BuildInfo__Output as _aether_v1_BuildInfo__Output } from './aether/v1/BuildInfo';
import type { CheckpointOperation as _aether_v1_CheckpointOperation, CheckpointOperation__Output as _aether_v1_CheckpointOperation__Output } from './aether/v1/CheckpointOperation';
@@ -77,6 +84,7 @@ import type { ErrorResponse as _aether_v1_ErrorResponse, ErrorResponse__Output a
import type { ExtensionDeclaration as _aether_v1_ExtensionDeclaration, ExtensionDeclaration__Output as _aether_v1_ExtensionDeclaration__Output } from './aether/v1/ExtensionDeclaration';
import type { FlowEdge as _aether_v1_FlowEdge, FlowEdge__Output as _aether_v1_FlowEdge__Output } from './aether/v1/FlowEdge';
import type { FlowNode as _aether_v1_FlowNode, FlowNode__Output as _aether_v1_FlowNode__Output } from './aether/v1/FlowNode';
+import type { ForwardedAuthorization as _aether_v1_ForwardedAuthorization, ForwardedAuthorization__Output as _aether_v1_ForwardedAuthorization__Output } from './aether/v1/ForwardedAuthorization';
import type { GatewayInfo as _aether_v1_GatewayInfo, GatewayInfo__Output as _aether_v1_GatewayInfo__Output } from './aether/v1/GatewayInfo';
import type { GatewayStats as _aether_v1_GatewayStats, GatewayStats__Output as _aether_v1_GatewayStats__Output } from './aether/v1/GatewayStats';
import type { HealthCheck as _aether_v1_HealthCheck, HealthCheck__Output as _aether_v1_HealthCheck__Output } from './aether/v1/HealthCheck';
@@ -107,6 +115,7 @@ import type { ResolveAuthorityRequestPayload as _aether_v1_ResolveAuthorityReque
import type { ResolveAuthorityResponse as _aether_v1_ResolveAuthorityResponse, ResolveAuthorityResponse__Output as _aether_v1_ResolveAuthorityResponse__Output } from './aether/v1/ResolveAuthorityResponse';
import type { ResolvedAuthority as _aether_v1_ResolvedAuthority, ResolvedAuthority__Output as _aether_v1_ResolvedAuthority__Output } from './aether/v1/ResolvedAuthority';
import type { ResolvedAuthorityInfo as _aether_v1_ResolvedAuthorityInfo, ResolvedAuthorityInfo__Output as _aether_v1_ResolvedAuthorityInfo__Output } from './aether/v1/ResolvedAuthorityInfo';
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from './aether/v1/ResourceAccessRequest';
import type { RetryPolicy as _aether_v1_RetryPolicy, RetryPolicy__Output as _aether_v1_RetryPolicy__Output } from './aether/v1/RetryPolicy';
import type { SendMessage as _aether_v1_SendMessage, SendMessage__Output as _aether_v1_SendMessage__Output } from './aether/v1/SendMessage';
import type { ServiceIdentity as _aether_v1_ServiceIdentity, ServiceIdentity__Output as _aether_v1_ServiceIdentity__Output } from './aether/v1/ServiceIdentity';
@@ -147,7 +156,9 @@ import type { UserIdentity as _aether_v1_UserIdentity, UserIdentity__Output as _
import type { WaitSpec as _aether_v1_WaitSpec, WaitSpec__Output as _aether_v1_WaitSpec__Output } from './aether/v1/WaitSpec';
import type { WorkflowEngineIdentity as _aether_v1_WorkflowEngineIdentity, WorkflowEngineIdentity__Output as _aether_v1_WorkflowEngineIdentity__Output } from './aether/v1/WorkflowEngineIdentity';
import type { WorkflowOperation as _aether_v1_WorkflowOperation, WorkflowOperation__Output as _aether_v1_WorkflowOperation__Output } from './aether/v1/WorkflowOperation';
+import type { WorkflowRequestContext as _aether_v1_WorkflowRequestContext, WorkflowRequestContext__Output as _aether_v1_WorkflowRequestContext__Output } from './aether/v1/WorkflowRequestContext';
import type { WorkflowResponse as _aether_v1_WorkflowResponse, WorkflowResponse__Output as _aether_v1_WorkflowResponse__Output } from './aether/v1/WorkflowResponse';
+import type { WorkflowScheduleAuthorityScope as _aether_v1_WorkflowScheduleAuthorityScope, WorkflowScheduleAuthorityScope__Output as _aether_v1_WorkflowScheduleAuthorityScope__Output } from './aether/v1/WorkflowScheduleAuthorityScope';
import type { WorkspaceFilter as _aether_v1_WorkspaceFilter, WorkspaceFilter__Output as _aether_v1_WorkspaceFilter__Output } from './aether/v1/WorkspaceFilter';
import type { WorkspaceInfo as _aether_v1_WorkspaceInfo, WorkspaceInfo__Output as _aether_v1_WorkspaceInfo__Output } from './aether/v1/WorkspaceInfo';
import type { WorkspaceOperation as _aether_v1_WorkspaceOperation, WorkspaceOperation__Output as _aether_v1_WorkspaceOperation__Output } from './aether/v1/WorkspaceOperation';
@@ -185,6 +196,9 @@ export interface ProtoGrpcType {
ACLRuleFilter: MessageTypeDefinition<_aether_v1_ACLRuleFilter, _aether_v1_ACLRuleFilter__Output>
ACLRuleInfo: MessageTypeDefinition<_aether_v1_ACLRuleInfo, _aether_v1_ACLRuleInfo__Output>
ACLSetFallbackRequest: MessageTypeDefinition<_aether_v1_ACLSetFallbackRequest, _aether_v1_ACLSetFallbackRequest__Output>
+ AccessCheckOperation: MessageTypeDefinition<_aether_v1_AccessCheckOperation, _aether_v1_AccessCheckOperation__Output>
+ AccessCheckResponse: MessageTypeDefinition<_aether_v1_AccessCheckResponse, _aether_v1_AccessCheckResponse__Output>
+ AccessDecisionReceipt: MessageTypeDefinition<_aether_v1_AccessDecisionReceipt, _aether_v1_AccessDecisionReceipt__Output>
AccessLevel: EnumTypeDefinition
AdminQuery: MessageTypeDefinition<_aether_v1_AdminQuery, _aether_v1_AdminQuery__Output>
AdminResponse: MessageTypeDefinition<_aether_v1_AdminResponse, _aether_v1_AdminResponse__Output>
@@ -200,6 +214,8 @@ export interface ProtoGrpcType {
AuditEntry: MessageTypeDefinition<_aether_v1_AuditEntry, _aether_v1_AuditEntry__Output>
AuditQuery: MessageTypeDefinition<_aether_v1_AuditQuery, _aether_v1_AuditQuery__Output>
AuditQueryResponse: MessageTypeDefinition<_aether_v1_AuditQueryResponse, _aether_v1_AuditQueryResponse__Output>
+ AuthorityContinuationRequest: MessageTypeDefinition<_aether_v1_AuthorityContinuationRequest, _aether_v1_AuthorityContinuationRequest__Output>
+ AuthorityContinuationScope: MessageTypeDefinition<_aether_v1_AuthorityContinuationScope, _aether_v1_AuthorityContinuationScope__Output>
AuthorityGrantBatchExchangeRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantBatchExchangeRequest, _aether_v1_AuthorityGrantBatchExchangeRequest__Output>
AuthorityGrantDeriveForTargetRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantDeriveForTargetRequest, _aether_v1_AuthorityGrantDeriveForTargetRequest__Output>
AuthorityGrantDeriveRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantDeriveRequest, _aether_v1_AuthorityGrantDeriveRequest__Output>
@@ -221,6 +237,8 @@ export interface ProtoGrpcType {
AuthoritySpan: MessageTypeDefinition<_aether_v1_AuthoritySpan, _aether_v1_AuthoritySpan__Output>
AuthorizationContext: MessageTypeDefinition<_aether_v1_AuthorizationContext, _aether_v1_AuthorizationContext__Output>
BackoffStrategy: EnumTypeDefinition
+ BatchAccessCheckOperation: MessageTypeDefinition<_aether_v1_BatchAccessCheckOperation, _aether_v1_BatchAccessCheckOperation__Output>
+ BatchAccessCheckResponse: MessageTypeDefinition<_aether_v1_BatchAccessCheckResponse, _aether_v1_BatchAccessCheckResponse__Output>
BridgeIdentity: MessageTypeDefinition<_aether_v1_BridgeIdentity, _aether_v1_BridgeIdentity__Output>
BuildInfo: MessageTypeDefinition<_aether_v1_BuildInfo, _aether_v1_BuildInfo__Output>
CheckpointOperation: MessageTypeDefinition<_aether_v1_CheckpointOperation, _aether_v1_CheckpointOperation__Output>
@@ -239,6 +257,7 @@ export interface ProtoGrpcType {
ExtensionDeclaration: MessageTypeDefinition<_aether_v1_ExtensionDeclaration, _aether_v1_ExtensionDeclaration__Output>
FlowEdge: MessageTypeDefinition<_aether_v1_FlowEdge, _aether_v1_FlowEdge__Output>
FlowNode: MessageTypeDefinition<_aether_v1_FlowNode, _aether_v1_FlowNode__Output>
+ ForwardedAuthorization: MessageTypeDefinition<_aether_v1_ForwardedAuthorization, _aether_v1_ForwardedAuthorization__Output>
GatewayInfo: MessageTypeDefinition<_aether_v1_GatewayInfo, _aether_v1_GatewayInfo__Output>
GatewayStats: MessageTypeDefinition<_aether_v1_GatewayStats, _aether_v1_GatewayStats__Output>
HealthCheck: MessageTypeDefinition<_aether_v1_HealthCheck, _aether_v1_HealthCheck__Output>
@@ -274,6 +293,7 @@ export interface ProtoGrpcType {
ResolveAuthorityResponse: MessageTypeDefinition<_aether_v1_ResolveAuthorityResponse, _aether_v1_ResolveAuthorityResponse__Output>
ResolvedAuthority: MessageTypeDefinition<_aether_v1_ResolvedAuthority, _aether_v1_ResolvedAuthority__Output>
ResolvedAuthorityInfo: MessageTypeDefinition<_aether_v1_ResolvedAuthorityInfo, _aether_v1_ResolvedAuthorityInfo__Output>
+ ResourceAccessRequest: MessageTypeDefinition<_aether_v1_ResourceAccessRequest, _aether_v1_ResourceAccessRequest__Output>
RetryPolicy: MessageTypeDefinition<_aether_v1_RetryPolicy, _aether_v1_RetryPolicy__Output>
SendMessage: MessageTypeDefinition<_aether_v1_SendMessage, _aether_v1_SendMessage__Output>
ServiceIdentity: MessageTypeDefinition<_aether_v1_ServiceIdentity, _aether_v1_ServiceIdentity__Output>
@@ -283,6 +303,7 @@ export interface ProtoGrpcType {
SubmitAuditEventRequest: MessageTypeDefinition<_aether_v1_SubmitAuditEventRequest, _aether_v1_SubmitAuditEventRequest__Output>
SubmitAuditEventResponse: MessageTypeDefinition<_aether_v1_SubmitAuditEventResponse, _aether_v1_SubmitAuditEventResponse__Output>
SwitchWorkspace: MessageTypeDefinition<_aether_v1_SwitchWorkspace, _aether_v1_SwitchWorkspace__Output>
+ TargetOfflinePolicy: EnumTypeDefinition
TaskAssignment: MessageTypeDefinition<_aether_v1_TaskAssignment, _aether_v1_TaskAssignment__Output>
TaskAssignmentMode: EnumTypeDefinition
TaskAuthorityRequestEventRelay: MessageTypeDefinition<_aether_v1_TaskAuthorityRequestEventRelay, _aether_v1_TaskAuthorityRequestEventRelay__Output>
@@ -317,9 +338,12 @@ export interface ProtoGrpcType {
UserIdentity: MessageTypeDefinition<_aether_v1_UserIdentity, _aether_v1_UserIdentity__Output>
WaitReason: EnumTypeDefinition
WaitSpec: MessageTypeDefinition<_aether_v1_WaitSpec, _aether_v1_WaitSpec__Output>
+ WorkflowAuthorityLifetimeMode: EnumTypeDefinition
WorkflowEngineIdentity: MessageTypeDefinition<_aether_v1_WorkflowEngineIdentity, _aether_v1_WorkflowEngineIdentity__Output>
WorkflowOperation: MessageTypeDefinition<_aether_v1_WorkflowOperation, _aether_v1_WorkflowOperation__Output>
+ WorkflowRequestContext: MessageTypeDefinition<_aether_v1_WorkflowRequestContext, _aether_v1_WorkflowRequestContext__Output>
WorkflowResponse: MessageTypeDefinition<_aether_v1_WorkflowResponse, _aether_v1_WorkflowResponse__Output>
+ WorkflowScheduleAuthorityScope: MessageTypeDefinition<_aether_v1_WorkflowScheduleAuthorityScope, _aether_v1_WorkflowScheduleAuthorityScope__Output>
WorkspaceFilter: MessageTypeDefinition<_aether_v1_WorkspaceFilter, _aether_v1_WorkspaceFilter__Output>
WorkspaceInfo: MessageTypeDefinition<_aether_v1_WorkspaceInfo, _aether_v1_WorkspaceInfo__Output>
WorkspaceOperation: MessageTypeDefinition<_aether_v1_WorkspaceOperation, _aether_v1_WorkspaceOperation__Output>
diff --git a/sdk/typescript/src/proto/aether/v1/AccessCheckOperation.ts b/sdk/typescript/src/proto/aether/v1/AccessCheckOperation.ts
new file mode 100644
index 0000000..e05c3d2
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/AccessCheckOperation.ts
@@ -0,0 +1,16 @@
+// Original file: aether.proto
+
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from '../../aether/v1/ResourceAccessRequest';
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+
+export interface AccessCheckOperation {
+ 'requestId'?: (string);
+ 'access'?: (_aether_v1_ResourceAccessRequest | null);
+ 'authorization'?: (_aether_v1_AuthorizationContext | null);
+}
+
+export interface AccessCheckOperation__Output {
+ 'requestId': (string);
+ 'access': (_aether_v1_ResourceAccessRequest__Output | null);
+ 'authorization': (_aether_v1_AuthorizationContext__Output | null);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/AccessCheckResponse.ts b/sdk/typescript/src/proto/aether/v1/AccessCheckResponse.ts
new file mode 100644
index 0000000..c51b614
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/AccessCheckResponse.ts
@@ -0,0 +1,23 @@
+// Original file: aether.proto
+
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from '../../aether/v1/AccessDecisionReceipt';
+
+export interface AccessCheckResponse {
+ 'requestId'?: (string);
+ /**
+ * evaluation completed; denial is success
+ */
+ 'success'?: (boolean);
+ 'error'?: (string);
+ 'decision'?: (_aether_v1_AccessDecisionReceipt | null);
+}
+
+export interface AccessCheckResponse__Output {
+ 'requestId': (string);
+ /**
+ * evaluation completed; denial is success
+ */
+ 'success': (boolean);
+ 'error': (string);
+ 'decision': (_aether_v1_AccessDecisionReceipt__Output | null);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/AccessDecisionReceipt.ts b/sdk/typescript/src/proto/aether/v1/AccessDecisionReceipt.ts
new file mode 100644
index 0000000..c0310cf
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/AccessDecisionReceipt.ts
@@ -0,0 +1,95 @@
+// Original file: aether.proto
+
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from '../../aether/v1/ResourceAccessRequest';
+import type { PrincipalRef as _aether_v1_PrincipalRef, PrincipalRef__Output as _aether_v1_PrincipalRef__Output } from '../../aether/v1/PrincipalRef';
+import type { Long } from '@grpc/proto-loader';
+
+/**
+ * AccessDecisionReceipt is gateway-authored transport metadata. Receivers may
+ * trust it only when it arrived in the Aether envelope, never when an
+ * equivalent object appears inside an application payload.
+ */
+export interface AccessDecisionReceipt {
+ 'decisionId'?: (string);
+ 'request'?: (_aether_v1_ResourceAccessRequest | null);
+ 'allowed'?: (boolean);
+ /**
+ * "ALLOW" or "DENY"
+ */
+ 'decision'?: (string);
+ 'effectiveAccessLevel'?: (number);
+ /**
+ * authenticated connected principal
+ */
+ 'actor'?: (_aether_v1_PrincipalRef | null);
+ /**
+ * populated for on-behalf-of checks
+ */
+ 'subject'?: (_aether_v1_PrincipalRef | null);
+ /**
+ * populated when the grant records one
+ */
+ 'rootSubject'?: (_aether_v1_PrincipalRef | null);
+ /**
+ * "direct" or "on_behalf_of"
+ */
+ 'authorityMode'?: (string);
+ 'grantId'?: (string);
+ 'rootGrantId'?: (string);
+ 'evaluatedAtMs'?: (number | string | Long);
+ 'expiresAtMs'?: (number | string | Long);
+ /**
+ * stable code; empty for allowed checks
+ */
+ 'denialCode'?: (string);
+ /**
+ * Populated for checked SendMessage and ProxyHTTP delivery. This binds the
+ * receipt to the concrete post-wildcard-resolution target that received it.
+ */
+ 'deliveryTarget'?: (string);
+}
+
+/**
+ * AccessDecisionReceipt is gateway-authored transport metadata. Receivers may
+ * trust it only when it arrived in the Aether envelope, never when an
+ * equivalent object appears inside an application payload.
+ */
+export interface AccessDecisionReceipt__Output {
+ 'decisionId': (string);
+ 'request': (_aether_v1_ResourceAccessRequest__Output | null);
+ 'allowed': (boolean);
+ /**
+ * "ALLOW" or "DENY"
+ */
+ 'decision': (string);
+ 'effectiveAccessLevel': (number);
+ /**
+ * authenticated connected principal
+ */
+ 'actor': (_aether_v1_PrincipalRef__Output | null);
+ /**
+ * populated for on-behalf-of checks
+ */
+ 'subject': (_aether_v1_PrincipalRef__Output | null);
+ /**
+ * populated when the grant records one
+ */
+ 'rootSubject': (_aether_v1_PrincipalRef__Output | null);
+ /**
+ * "direct" or "on_behalf_of"
+ */
+ 'authorityMode': (string);
+ 'grantId': (string);
+ 'rootGrantId': (string);
+ 'evaluatedAtMs': (string);
+ 'expiresAtMs': (string);
+ /**
+ * stable code; empty for allowed checks
+ */
+ 'denialCode': (string);
+ /**
+ * Populated for checked SendMessage and ProxyHTTP delivery. This binds the
+ * receipt to the concrete post-wildcard-resolution target that received it.
+ */
+ 'deliveryTarget': (string);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/AuthorityContinuationRequest.ts b/sdk/typescript/src/proto/aether/v1/AuthorityContinuationRequest.ts
new file mode 100644
index 0000000..2f1e0de
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/AuthorityContinuationRequest.ts
@@ -0,0 +1,59 @@
+// Original file: aether.proto
+
+import type { AuthorityContinuationScope as _aether_v1_AuthorityContinuationScope, AuthorityContinuationScope__Output as _aether_v1_AuthorityContinuationScope__Output } from '../../aether/v1/AuthorityContinuationScope';
+
+// Original file: aether.proto
+
+export const _aether_v1_AuthorityContinuationRequest_ScopeMode = {
+ SCOPE_MODE_UNSPECIFIED: 'SCOPE_MODE_UNSPECIFIED',
+ /**
+ * Service-only mode used when a trusted service must evaluate arbitrary
+ * resources within the caller's existing authority ceiling.
+ */
+ SCOPE_MODE_INHERIT_PARENT: 'SCOPE_MODE_INHERIT_PARENT',
+ /**
+ * Required for agent recipients. The requested scope is validated as a
+ * strict subset of the parent and the child is minted per invocation.
+ */
+ SCOPE_MODE_ATTENUATE: 'SCOPE_MODE_ATTENUATE',
+} as const;
+
+export type _aether_v1_AuthorityContinuationRequest_ScopeMode =
+ | 'SCOPE_MODE_UNSPECIFIED'
+ | 0
+ /**
+ * Service-only mode used when a trusted service must evaluate arbitrary
+ * resources within the caller's existing authority ceiling.
+ */
+ | 'SCOPE_MODE_INHERIT_PARENT'
+ | 1
+ /**
+ * Required for agent recipients. The requested scope is validated as a
+ * strict subset of the parent and the child is minted per invocation.
+ */
+ | 'SCOPE_MODE_ATTENUATE'
+ | 2
+
+export type _aether_v1_AuthorityContinuationRequest_ScopeMode__Output = typeof _aether_v1_AuthorityContinuationRequest_ScopeMode[keyof typeof _aether_v1_AuthorityContinuationRequest_ScopeMode]
+
+export interface AuthorityContinuationRequest {
+ 'scopeMode'?: (_aether_v1_AuthorityContinuationRequest_ScopeMode);
+ /**
+ * Opaque invocation identifier. Required for ATTENUATE and matched to the
+ * checked-access correlation ID so the trusted receipt, child, and payload
+ * can be validated as one call by the recipient.
+ */
+ 'bindingId'?: (string);
+ 'scope'?: (_aether_v1_AuthorityContinuationScope | null);
+}
+
+export interface AuthorityContinuationRequest__Output {
+ 'scopeMode': (_aether_v1_AuthorityContinuationRequest_ScopeMode__Output);
+ /**
+ * Opaque invocation identifier. Required for ATTENUATE and matched to the
+ * checked-access correlation ID so the trusted receipt, child, and payload
+ * can be validated as one call by the recipient.
+ */
+ 'bindingId': (string);
+ 'scope': (_aether_v1_AuthorityContinuationScope__Output | null);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/AuthorityContinuationScope.ts b/sdk/typescript/src/proto/aether/v1/AuthorityContinuationScope.ts
new file mode 100644
index 0000000..78a8faf
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/AuthorityContinuationScope.ts
@@ -0,0 +1,27 @@
+// Original file: aether.proto
+
+import type { ACLAuthorityGrantResourceScopeEntry as _aether_v1_ACLAuthorityGrantResourceScopeEntry, ACLAuthorityGrantResourceScopeEntry__Output as _aether_v1_ACLAuthorityGrantResourceScopeEntry__Output } from '../../aether/v1/ACLAuthorityGrantResourceScopeEntry';
+
+/**
+ * Explicit scope ceiling for a derived message authority continuation. Empty
+ * axes retain the AuthorityGrant meaning of unrestricted, so an attenuated
+ * agent continuation requires every axis to be populated and validated.
+ */
+export interface AuthorityContinuationScope {
+ 'workspaceScope'?: (string)[];
+ 'resourceScope'?: (_aether_v1_ACLAuthorityGrantResourceScopeEntry)[];
+ 'operationScope'?: (string)[];
+ 'maxAccessLevel'?: (number);
+}
+
+/**
+ * Explicit scope ceiling for a derived message authority continuation. Empty
+ * axes retain the AuthorityGrant meaning of unrestricted, so an attenuated
+ * agent continuation requires every axis to be populated and validated.
+ */
+export interface AuthorityContinuationScope__Output {
+ 'workspaceScope': (string)[];
+ 'resourceScope': (_aether_v1_ACLAuthorityGrantResourceScopeEntry__Output)[];
+ 'operationScope': (string)[];
+ 'maxAccessLevel': (number);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/AuthorityGrantOperation.ts b/sdk/typescript/src/proto/aether/v1/AuthorityGrantOperation.ts
index e4fa246..09e826b 100644
--- a/sdk/typescript/src/proto/aether/v1/AuthorityGrantOperation.ts
+++ b/sdk/typescript/src/proto/aether/v1/AuthorityGrantOperation.ts
@@ -136,6 +136,11 @@ export interface AuthorityGrantOperation {
* For DERIVE_FOR_TARGET
*/
'deriveForTargetRequest'?: (_aether_v1_AuthorityGrantDeriveForTargetRequest | null);
+ /**
+ * WorkflowEngine-only audience context for GET/REVOKE of a
+ * workflow_schedule grant. Ignored for other actors and operations.
+ */
+ 'workflowScheduleId'?: (string);
}
/**
@@ -177,4 +182,9 @@ export interface AuthorityGrantOperation__Output {
* For DERIVE_FOR_TARGET
*/
'deriveForTargetRequest': (_aether_v1_AuthorityGrantDeriveForTargetRequest__Output | null);
+ /**
+ * WorkflowEngine-only audience context for GET/REVOKE of a
+ * workflow_schedule grant. Ignored for other actors and operations.
+ */
+ 'workflowScheduleId': (string);
}
diff --git a/sdk/typescript/src/proto/aether/v1/BatchAccessCheckOperation.ts b/sdk/typescript/src/proto/aether/v1/BatchAccessCheckOperation.ts
new file mode 100644
index 0000000..294ce7e
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/BatchAccessCheckOperation.ts
@@ -0,0 +1,16 @@
+// Original file: aether.proto
+
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from '../../aether/v1/ResourceAccessRequest';
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+
+export interface BatchAccessCheckOperation {
+ 'requestId'?: (string);
+ 'access'?: (_aether_v1_ResourceAccessRequest)[];
+ 'authorization'?: (_aether_v1_AuthorizationContext | null);
+}
+
+export interface BatchAccessCheckOperation__Output {
+ 'requestId': (string);
+ 'access': (_aether_v1_ResourceAccessRequest__Output)[];
+ 'authorization': (_aether_v1_AuthorizationContext__Output | null);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/BatchAccessCheckResponse.ts b/sdk/typescript/src/proto/aether/v1/BatchAccessCheckResponse.ts
new file mode 100644
index 0000000..0745d81
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/BatchAccessCheckResponse.ts
@@ -0,0 +1,23 @@
+// Original file: aether.proto
+
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from '../../aether/v1/AccessDecisionReceipt';
+
+export interface BatchAccessCheckResponse {
+ 'requestId'?: (string);
+ 'success'?: (boolean);
+ 'error'?: (string);
+ /**
+ * Same order and cardinality as BatchAccessCheckOperation.access.
+ */
+ 'decisions'?: (_aether_v1_AccessDecisionReceipt)[];
+}
+
+export interface BatchAccessCheckResponse__Output {
+ 'requestId': (string);
+ 'success': (boolean);
+ 'error': (string);
+ /**
+ * Same order and cardinality as BatchAccessCheckOperation.access.
+ */
+ 'decisions': (_aether_v1_AccessDecisionReceipt__Output)[];
+}
diff --git a/sdk/typescript/src/proto/aether/v1/CreateTaskRequest.ts b/sdk/typescript/src/proto/aether/v1/CreateTaskRequest.ts
index 6bc10c3..ac2e065 100644
--- a/sdk/typescript/src/proto/aether/v1/CreateTaskRequest.ts
+++ b/sdk/typescript/src/proto/aether/v1/CreateTaskRequest.ts
@@ -6,6 +6,7 @@ import type { TaskClass as _aether_v1_TaskClass, TaskClass__Output as _aether_v1
import type { RetryPolicy as _aether_v1_RetryPolicy, RetryPolicy__Output as _aether_v1_RetryPolicy__Output } from '../../aether/v1/RetryPolicy';
import type { TaskPriority as _aether_v1_TaskPriority, TaskPriority__Output as _aether_v1_TaskPriority__Output } from '../../aether/v1/TaskPriority';
import type { TaskCompletionEvent as _aether_v1_TaskCompletionEvent, TaskCompletionEvent__Output as _aether_v1_TaskCompletionEvent__Output } from '../../aether/v1/TaskCompletionEvent';
+import type { TargetOfflinePolicy as _aether_v1_TargetOfflinePolicy, TargetOfflinePolicy__Output as _aether_v1_TargetOfflinePolicy__Output } from '../../aether/v1/TargetOfflinePolicy';
export interface CreateTaskRequest {
'taskType'?: (string);
@@ -95,6 +96,35 @@ export interface CreateTaskRequest {
* reaches a (selected) terminal status. Absent/disabled = no emission.
*/
'completionEvent'?: (_aether_v1_TaskCompletionEvent | null);
+ /**
+ * Optional native parent for a nested task created by a long-lived worker.
+ * The gateway accepts an explicit value only when the caller is the active
+ * parent task's assigned execution identity. This is a request-scoped binding:
+ * it may select a different assigned task than the connection's startup/task-
+ * token association. Empty preserves connection-associated parent inference.
+ */
+ 'parentTaskId'?: (string);
+ /**
+ * TARGETED mode only. QUEUE persists the task for delivery when the exact
+ * static worker reconnects, without requiring an orchestration registry
+ * entry. REJECT fails task creation while the worker is absent.
+ */
+ 'targetOfflinePolicy'?: (_aether_v1_TargetOfflinePolicy);
+ /**
+ * Minimum delegation capacity the task's final execution identity must
+ * retain after task-authority setup. Currently 0 or 1. Set to 1 when the
+ * worker must perform one explicit downstream authorization continuation
+ * (for example, Sahara querying the tool catalog under the user's authority).
+ * In POOL mode the gateway reserves the additional anchor-to-assignee hop.
+ */
+ 'requiredDownstreamAuthorityHops'?: (number);
+ /**
+ * WorkflowEngine-only authority audience binding. The gateway accepts this
+ * field only from the authenticated WorkflowEngine principal and requires it
+ * to match a workflow_schedule audience on authorization. Ordinary task
+ * creators must leave it empty.
+ */
+ 'originatingScheduleId'?: (string);
}
export interface CreateTaskRequest__Output {
@@ -185,4 +215,33 @@ export interface CreateTaskRequest__Output {
* reaches a (selected) terminal status. Absent/disabled = no emission.
*/
'completionEvent': (_aether_v1_TaskCompletionEvent__Output | null);
+ /**
+ * Optional native parent for a nested task created by a long-lived worker.
+ * The gateway accepts an explicit value only when the caller is the active
+ * parent task's assigned execution identity. This is a request-scoped binding:
+ * it may select a different assigned task than the connection's startup/task-
+ * token association. Empty preserves connection-associated parent inference.
+ */
+ 'parentTaskId': (string);
+ /**
+ * TARGETED mode only. QUEUE persists the task for delivery when the exact
+ * static worker reconnects, without requiring an orchestration registry
+ * entry. REJECT fails task creation while the worker is absent.
+ */
+ 'targetOfflinePolicy': (_aether_v1_TargetOfflinePolicy__Output);
+ /**
+ * Minimum delegation capacity the task's final execution identity must
+ * retain after task-authority setup. Currently 0 or 1. Set to 1 when the
+ * worker must perform one explicit downstream authorization continuation
+ * (for example, Sahara querying the tool catalog under the user's authority).
+ * In POOL mode the gateway reserves the additional anchor-to-assignee hop.
+ */
+ 'requiredDownstreamAuthorityHops': (number);
+ /**
+ * WorkflowEngine-only authority audience binding. The gateway accepts this
+ * field only from the authenticated WorkflowEngine principal and requires it
+ * to match a workflow_schedule audience on authorization. Ordinary task
+ * creators must leave it empty.
+ */
+ 'originatingScheduleId': (string);
}
diff --git a/sdk/typescript/src/proto/aether/v1/DownstreamMessage.ts b/sdk/typescript/src/proto/aether/v1/DownstreamMessage.ts
index a2da4bf..78b6003 100644
--- a/sdk/typescript/src/proto/aether/v1/DownstreamMessage.ts
+++ b/sdk/typescript/src/proto/aether/v1/DownstreamMessage.ts
@@ -37,6 +37,8 @@ import type { AuthorityRequestEvent as _aether_v1_AuthorityRequestEvent, Authori
import type { TaskHibernated as _aether_v1_TaskHibernated, TaskHibernated__Output as _aether_v1_TaskHibernated__Output } from '../../aether/v1/TaskHibernated';
import type { TaskSubscriptionOperationResponse as _aether_v1_TaskSubscriptionOperationResponse, TaskSubscriptionOperationResponse__Output as _aether_v1_TaskSubscriptionOperationResponse__Output } from '../../aether/v1/TaskSubscriptionOperationResponse';
import type { TaskEvent as _aether_v1_TaskEvent, TaskEvent__Output as _aether_v1_TaskEvent__Output } from '../../aether/v1/TaskEvent';
+import type { AccessCheckResponse as _aether_v1_AccessCheckResponse, AccessCheckResponse__Output as _aether_v1_AccessCheckResponse__Output } from '../../aether/v1/AccessCheckResponse';
+import type { BatchAccessCheckResponse as _aether_v1_BatchAccessCheckResponse, BatchAccessCheckResponse__Output as _aether_v1_BatchAccessCheckResponse__Output } from '../../aether/v1/BatchAccessCheckResponse';
export interface DownstreamMessage {
'msg'?: (_aether_v1_IncomingMessage | null);
@@ -83,7 +85,9 @@ export interface DownstreamMessage {
* unknown URIs are ignored.
*/
'activeExtensions'?: (string)[];
- 'payload'?: "msg"|"config"|"signal"|"error"|"kv"|"taskAssignment"|"connectionAck"|"checkpoint"|"admin"|"sessionResponse"|"taskQuery"|"taskOp"|"workspace"|"agent"|"acl"|"progressUpdate"|"workflowResponse"|"workflowOp"|"token"|"auditResponse"|"authorityGrant"|"createTask"|"proxyHttpResponse"|"proxyHttpBodyChunk"|"tunnelAck"|"tunnelClose"|"tunnelData"|"proxyHttpRequest"|"resolveAuthorityResponse"|"connectionStatusResponse"|"authorityGrantRevocation"|"submitAuditEventResponse"|"authorityRequestResponse"|"authorityRequestEvent"|"taskHibernated"|"taskSubscriptionResponse"|"taskEvent";
+ 'accessCheckResponse'?: (_aether_v1_AccessCheckResponse | null);
+ 'batchAccessCheckResponse'?: (_aether_v1_BatchAccessCheckResponse | null);
+ 'payload'?: "msg"|"config"|"signal"|"error"|"kv"|"taskAssignment"|"connectionAck"|"checkpoint"|"admin"|"sessionResponse"|"taskQuery"|"taskOp"|"workspace"|"agent"|"acl"|"progressUpdate"|"workflowResponse"|"workflowOp"|"token"|"auditResponse"|"authorityGrant"|"createTask"|"proxyHttpResponse"|"proxyHttpBodyChunk"|"tunnelAck"|"tunnelClose"|"tunnelData"|"proxyHttpRequest"|"resolveAuthorityResponse"|"connectionStatusResponse"|"authorityGrantRevocation"|"submitAuditEventResponse"|"authorityRequestResponse"|"authorityRequestEvent"|"taskHibernated"|"taskSubscriptionResponse"|"taskEvent"|"accessCheckResponse"|"batchAccessCheckResponse";
}
export interface DownstreamMessage__Output {
@@ -131,5 +135,7 @@ export interface DownstreamMessage__Output {
* unknown URIs are ignored.
*/
'activeExtensions': (string)[];
- 'payload'?: "msg"|"config"|"signal"|"error"|"kv"|"taskAssignment"|"connectionAck"|"checkpoint"|"admin"|"sessionResponse"|"taskQuery"|"taskOp"|"workspace"|"agent"|"acl"|"progressUpdate"|"workflowResponse"|"workflowOp"|"token"|"auditResponse"|"authorityGrant"|"createTask"|"proxyHttpResponse"|"proxyHttpBodyChunk"|"tunnelAck"|"tunnelClose"|"tunnelData"|"proxyHttpRequest"|"resolveAuthorityResponse"|"connectionStatusResponse"|"authorityGrantRevocation"|"submitAuditEventResponse"|"authorityRequestResponse"|"authorityRequestEvent"|"taskHibernated"|"taskSubscriptionResponse"|"taskEvent";
+ 'accessCheckResponse'?: (_aether_v1_AccessCheckResponse__Output | null);
+ 'batchAccessCheckResponse'?: (_aether_v1_BatchAccessCheckResponse__Output | null);
+ 'payload'?: "msg"|"config"|"signal"|"error"|"kv"|"taskAssignment"|"connectionAck"|"checkpoint"|"admin"|"sessionResponse"|"taskQuery"|"taskOp"|"workspace"|"agent"|"acl"|"progressUpdate"|"workflowResponse"|"workflowOp"|"token"|"auditResponse"|"authorityGrant"|"createTask"|"proxyHttpResponse"|"proxyHttpBodyChunk"|"tunnelAck"|"tunnelClose"|"tunnelData"|"proxyHttpRequest"|"resolveAuthorityResponse"|"connectionStatusResponse"|"authorityGrantRevocation"|"submitAuditEventResponse"|"authorityRequestResponse"|"authorityRequestEvent"|"taskHibernated"|"taskSubscriptionResponse"|"taskEvent"|"accessCheckResponse"|"batchAccessCheckResponse";
}
diff --git a/sdk/typescript/src/proto/aether/v1/ForwardedAuthorization.ts b/sdk/typescript/src/proto/aether/v1/ForwardedAuthorization.ts
new file mode 100644
index 0000000..236149c
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/ForwardedAuthorization.ts
@@ -0,0 +1,47 @@
+// Original file: aether.proto
+
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+import type { AuthorityContinuationScope as _aether_v1_AuthorityContinuationScope, AuthorityContinuationScope__Output as _aether_v1_AuthorityContinuationScope__Output } from '../../aether/v1/AuthorityContinuationScope';
+import type { Long } from '@grpc/proto-loader';
+
+/**
+ * Trusted authorization continuation carried outside the application payload.
+ * The child grant is non-delegable, scope-attenuated to its parent, short-lived,
+ * and linked into the parent's revocation cascade.
+ */
+export interface ForwardedAuthorization {
+ 'authorization'?: (_aether_v1_AuthorizationContext | null);
+ 'rootGrantId'?: (string);
+ 'expiresAtMs'?: (number | string | Long);
+ 'deliveryTarget'?: (string);
+ /**
+ * Empty only for a reusable service continuation using INHERIT_PARENT.
+ */
+ 'bindingId'?: (string);
+ /**
+ * Gateway-authored projection of the effective child scope. Recipients use
+ * this to enforce their local, server-owned invocation authority profile.
+ */
+ 'scope'?: (_aether_v1_AuthorityContinuationScope | null);
+}
+
+/**
+ * Trusted authorization continuation carried outside the application payload.
+ * The child grant is non-delegable, scope-attenuated to its parent, short-lived,
+ * and linked into the parent's revocation cascade.
+ */
+export interface ForwardedAuthorization__Output {
+ 'authorization': (_aether_v1_AuthorizationContext__Output | null);
+ 'rootGrantId': (string);
+ 'expiresAtMs': (string);
+ 'deliveryTarget': (string);
+ /**
+ * Empty only for a reusable service continuation using INHERIT_PARENT.
+ */
+ 'bindingId': (string);
+ /**
+ * Gateway-authored projection of the effective child scope. Recipients use
+ * this to enforce their local, server-owned invocation authority profile.
+ */
+ 'scope': (_aether_v1_AuthorityContinuationScope__Output | null);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/IncomingMessage.ts b/sdk/typescript/src/proto/aether/v1/IncomingMessage.ts
index 82bb53f..1895559 100644
--- a/sdk/typescript/src/proto/aether/v1/IncomingMessage.ts
+++ b/sdk/typescript/src/proto/aether/v1/IncomingMessage.ts
@@ -2,6 +2,8 @@
import type { MessageType as _aether_v1_MessageType, MessageType__Output as _aether_v1_MessageType__Output } from '../../aether/v1/MessageType';
import type { PrincipalRef as _aether_v1_PrincipalRef, PrincipalRef__Output as _aether_v1_PrincipalRef__Output } from '../../aether/v1/PrincipalRef';
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from '../../aether/v1/AccessDecisionReceipt';
+import type { ForwardedAuthorization as _aether_v1_ForwardedAuthorization, ForwardedAuthorization__Output as _aether_v1_ForwardedAuthorization__Output } from '../../aether/v1/ForwardedAuthorization';
export interface IncomingMessage {
'sourceTopic'?: (string);
@@ -28,6 +30,19 @@ export interface IncomingMessage {
* (non-OBO) sends. See MessageEnvelope.on_behalf_subject.
*/
'onBehalfSubject'?: (_aether_v1_PrincipalRef | null);
+ /**
+ * Gateway-authored receipt from SendMessage.checked_access. Never populated
+ * from the application payload.
+ */
+ 'accessReceipt'?: (_aether_v1_AccessDecisionReceipt | null);
+ /**
+ * Gateway-derived authority continuation for this exact delivery target.
+ * Populated only when SendMessage.authority_continuation was explicitly set
+ * and the sender's resolved grant could delegate. Recipients can pass the
+ * authorization context to CheckAccess / BatchCheckAccess; root_grant_id,
+ * expiry, and delivery_target are trusted binding/audit metadata.
+ */
+ 'forwardedAuthorization'?: (_aether_v1_ForwardedAuthorization | null);
}
export interface IncomingMessage__Output {
@@ -55,4 +70,17 @@ export interface IncomingMessage__Output {
* (non-OBO) sends. See MessageEnvelope.on_behalf_subject.
*/
'onBehalfSubject': (_aether_v1_PrincipalRef__Output | null);
+ /**
+ * Gateway-authored receipt from SendMessage.checked_access. Never populated
+ * from the application payload.
+ */
+ 'accessReceipt': (_aether_v1_AccessDecisionReceipt__Output | null);
+ /**
+ * Gateway-derived authority continuation for this exact delivery target.
+ * Populated only when SendMessage.authority_continuation was explicitly set
+ * and the sender's resolved grant could delegate. Recipients can pass the
+ * authorization context to CheckAccess / BatchCheckAccess; root_grant_id,
+ * expiry, and delivery_target are trusted binding/audit metadata.
+ */
+ 'forwardedAuthorization': (_aether_v1_ForwardedAuthorization__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/MessageEnvelope.ts b/sdk/typescript/src/proto/aether/v1/MessageEnvelope.ts
index 47f6a0a..269fd4c 100644
--- a/sdk/typescript/src/proto/aether/v1/MessageEnvelope.ts
+++ b/sdk/typescript/src/proto/aether/v1/MessageEnvelope.ts
@@ -2,6 +2,8 @@
import type { MessageType as _aether_v1_MessageType, MessageType__Output as _aether_v1_MessageType__Output } from '../../aether/v1/MessageType';
import type { PrincipalRef as _aether_v1_PrincipalRef, PrincipalRef__Output as _aether_v1_PrincipalRef__Output } from '../../aether/v1/PrincipalRef';
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from '../../aether/v1/AccessDecisionReceipt';
+import type { ForwardedAuthorization as _aether_v1_ForwardedAuthorization, ForwardedAuthorization__Output as _aether_v1_ForwardedAuthorization__Output } from '../../aether/v1/ForwardedAuthorization';
import type { Long } from '@grpc/proto-loader';
/**
@@ -62,6 +64,15 @@ export interface MessageEnvelope {
* (CreateTaskResponse.authority_grant_id), not this field.
*/
'onBehalfSubject'?: (_aether_v1_PrincipalRef | null);
+ /**
+ * Gateway-authored exact-resource decision propagated to the recipient.
+ */
+ 'accessReceipt'?: (_aether_v1_AccessDecisionReceipt | null);
+ /**
+ * Gateway-authored authority continuation. This internal envelope field is
+ * copied to IncomingMessage and is never accepted from application payloads.
+ */
+ 'forwardedAuthorization'?: (_aether_v1_ForwardedAuthorization | null);
}
/**
@@ -122,4 +133,13 @@ export interface MessageEnvelope__Output {
* (CreateTaskResponse.authority_grant_id), not this field.
*/
'onBehalfSubject': (_aether_v1_PrincipalRef__Output | null);
+ /**
+ * Gateway-authored exact-resource decision propagated to the recipient.
+ */
+ 'accessReceipt': (_aether_v1_AccessDecisionReceipt__Output | null);
+ /**
+ * Gateway-authored authority continuation. This internal envelope field is
+ * copied to IncomingMessage and is never accepted from application payloads.
+ */
+ 'forwardedAuthorization': (_aether_v1_ForwardedAuthorization__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/ProxyHttpRequest.ts b/sdk/typescript/src/proto/aether/v1/ProxyHttpRequest.ts
index 254a735..b2b6b11 100644
--- a/sdk/typescript/src/proto/aether/v1/ProxyHttpRequest.ts
+++ b/sdk/typescript/src/proto/aether/v1/ProxyHttpRequest.ts
@@ -1,6 +1,8 @@
// Original file: aether.proto
import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from '../../aether/v1/ResourceAccessRequest';
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from '../../aether/v1/AccessDecisionReceipt';
import type { Long } from '@grpc/proto-loader';
/**
@@ -57,6 +59,18 @@ export interface ProxyHttpRequest {
* lower than the inbound chain depth they observed).
*/
'proxyChainDepth'?: (number);
+ /**
+ * Optional exact logical-resource authorization evaluated by the gateway
+ * after route authorization and wildcard target resolution. A denied or
+ * unavailable check prevents delivery to the terminator.
+ */
+ 'checkedAccess'?: (_aether_v1_ResourceAccessRequest | null);
+ /**
+ * Gateway-authored result of checked_access. The gateway always clears any
+ * caller-supplied value before evaluation; terminators must trust this only
+ * as transport metadata on the delivered envelope.
+ */
+ 'accessReceipt'?: (_aether_v1_AccessDecisionReceipt | null);
}
/**
@@ -113,4 +127,16 @@ export interface ProxyHttpRequest__Output {
* lower than the inbound chain depth they observed).
*/
'proxyChainDepth': (number);
+ /**
+ * Optional exact logical-resource authorization evaluated by the gateway
+ * after route authorization and wildcard target resolution. A denied or
+ * unavailable check prevents delivery to the terminator.
+ */
+ 'checkedAccess': (_aether_v1_ResourceAccessRequest__Output | null);
+ /**
+ * Gateway-authored result of checked_access. The gateway always clears any
+ * caller-supplied value before evaluation; terminators must trust this only
+ * as transport metadata on the delivered envelope.
+ */
+ 'accessReceipt': (_aether_v1_AccessDecisionReceipt__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/ResourceAccessRequest.ts b/sdk/typescript/src/proto/aether/v1/ResourceAccessRequest.ts
new file mode 100644
index 0000000..18dd145
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/ResourceAccessRequest.ts
@@ -0,0 +1,40 @@
+// Original file: aether.proto
+
+
+/**
+ * ResourceAccessRequest is the portable runtime authorization tuple evaluated
+ * by the gateway. It is intentionally independent of any tool protocol: the
+ * same primitive gates workspace views, catalog providers/entries, and future
+ * logical resources. All string fields are required except workspace.
+ */
+export interface ResourceAccessRequest {
+ 'resourceType'?: (string);
+ 'resourceId'?: (string);
+ 'operation'?: (string);
+ 'workspace'?: (string);
+ 'requiredAccessLevel'?: (number);
+ /**
+ * Caller-generated correlation binding for a single logical action. A
+ * recipient compares this value with its application payload/request.
+ */
+ 'correlationId'?: (string);
+}
+
+/**
+ * ResourceAccessRequest is the portable runtime authorization tuple evaluated
+ * by the gateway. It is intentionally independent of any tool protocol: the
+ * same primitive gates workspace views, catalog providers/entries, and future
+ * logical resources. All string fields are required except workspace.
+ */
+export interface ResourceAccessRequest__Output {
+ 'resourceType': (string);
+ 'resourceId': (string);
+ 'operation': (string);
+ 'workspace': (string);
+ 'requiredAccessLevel': (number);
+ /**
+ * Caller-generated correlation binding for a single logical action. A
+ * recipient compares this value with its application payload/request.
+ */
+ 'correlationId': (string);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/SendMessage.ts b/sdk/typescript/src/proto/aether/v1/SendMessage.ts
index 7def115..700c439 100644
--- a/sdk/typescript/src/proto/aether/v1/SendMessage.ts
+++ b/sdk/typescript/src/proto/aether/v1/SendMessage.ts
@@ -2,6 +2,8 @@
import type { MessageType as _aether_v1_MessageType, MessageType__Output as _aether_v1_MessageType__Output } from '../../aether/v1/MessageType';
import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from '../../aether/v1/ResourceAccessRequest';
+import type { AuthorityContinuationRequest as _aether_v1_AuthorityContinuationRequest, AuthorityContinuationRequest__Output as _aether_v1_AuthorityContinuationRequest__Output } from '../../aether/v1/AuthorityContinuationRequest';
export interface SendMessage {
'targetTopic'?: (string);
@@ -19,6 +21,24 @@ export interface SendMessage {
* scope source.
*/
'appWorkspace'?: (string);
+ /**
+ * Optional exact logical-resource check evaluated in addition to ordinary
+ * topic-route authorization. On allow, the resulting receipt is attached to
+ * the trusted MessageEnvelope/IncomingMessage metadata; on deny, nothing is
+ * published. Existing sends without this field retain their current path.
+ */
+ 'checkedAccess'?: (_aether_v1_ResourceAccessRequest | null);
+ /**
+ * Explicitly request a gateway-derived, short-lived authorization context
+ * for the resolved recipient. The gateway only honors this when the send is
+ * already operating under a validated OBO grant with delegation capacity.
+ * For sv::{implementation} targets, wildcard resolution happens first and
+ * the child grant is bound to the concrete service instance. Exact agent
+ * targets require an invocation-bound, explicitly attenuated scope. The
+ * recipient receives the result in IncomingMessage.forwarded_authorization;
+ * payload data can never populate that trusted field.
+ */
+ 'authorityContinuation'?: (_aether_v1_AuthorityContinuationRequest | null);
}
export interface SendMessage__Output {
@@ -37,4 +57,22 @@ export interface SendMessage__Output {
* scope source.
*/
'appWorkspace': (string);
+ /**
+ * Optional exact logical-resource check evaluated in addition to ordinary
+ * topic-route authorization. On allow, the resulting receipt is attached to
+ * the trusted MessageEnvelope/IncomingMessage metadata; on deny, nothing is
+ * published. Existing sends without this field retain their current path.
+ */
+ 'checkedAccess': (_aether_v1_ResourceAccessRequest__Output | null);
+ /**
+ * Explicitly request a gateway-derived, short-lived authorization context
+ * for the resolved recipient. The gateway only honors this when the send is
+ * already operating under a validated OBO grant with delegation capacity.
+ * For sv::{implementation} targets, wildcard resolution happens first and
+ * the child grant is bound to the concrete service instance. Exact agent
+ * targets require an invocation-bound, explicitly attenuated scope. The
+ * recipient receives the result in IncomingMessage.forwarded_authorization;
+ * payload data can never populate that trusted field.
+ */
+ 'authorityContinuation': (_aether_v1_AuthorityContinuationRequest__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/TargetOfflinePolicy.ts b/sdk/typescript/src/proto/aether/v1/TargetOfflinePolicy.ts
new file mode 100644
index 0000000..a3556a6
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/TargetOfflinePolicy.ts
@@ -0,0 +1,35 @@
+// Original file: aether.proto
+
+/**
+ * Controls what TARGETED task creation does when the exact target identity is
+ * not connected. UNSPECIFIED deliberately preserves the released behavior:
+ * validate the implementation and ask an orchestrator to start the worker.
+ */
+export const TargetOfflinePolicy = {
+ TARGET_OFFLINE_POLICY_UNSPECIFIED: 'TARGET_OFFLINE_POLICY_UNSPECIFIED',
+ TARGET_OFFLINE_POLICY_ORCHESTRATE: 'TARGET_OFFLINE_POLICY_ORCHESTRATE',
+ TARGET_OFFLINE_POLICY_QUEUE: 'TARGET_OFFLINE_POLICY_QUEUE',
+ TARGET_OFFLINE_POLICY_REJECT: 'TARGET_OFFLINE_POLICY_REJECT',
+} as const;
+
+/**
+ * Controls what TARGETED task creation does when the exact target identity is
+ * not connected. UNSPECIFIED deliberately preserves the released behavior:
+ * validate the implementation and ask an orchestrator to start the worker.
+ */
+export type TargetOfflinePolicy =
+ | 'TARGET_OFFLINE_POLICY_UNSPECIFIED'
+ | 0
+ | 'TARGET_OFFLINE_POLICY_ORCHESTRATE'
+ | 1
+ | 'TARGET_OFFLINE_POLICY_QUEUE'
+ | 2
+ | 'TARGET_OFFLINE_POLICY_REJECT'
+ | 3
+
+/**
+ * Controls what TARGETED task creation does when the exact target identity is
+ * not connected. UNSPECIFIED deliberately preserves the released behavior:
+ * validate the implementation and ask an orchestrator to start the worker.
+ */
+export type TargetOfflinePolicy__Output = typeof TargetOfflinePolicy[keyof typeof TargetOfflinePolicy]
diff --git a/sdk/typescript/src/proto/aether/v1/TaskAssignment.ts b/sdk/typescript/src/proto/aether/v1/TaskAssignment.ts
index c4e7c63..33dbb7c 100644
--- a/sdk/typescript/src/proto/aether/v1/TaskAssignment.ts
+++ b/sdk/typescript/src/proto/aether/v1/TaskAssignment.ts
@@ -1,6 +1,7 @@
// Original file: aether.proto
import type { TaskClass as _aether_v1_TaskClass, TaskClass__Output as _aether_v1_TaskClass__Output } from '../../aether/v1/TaskClass';
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
import type { Long } from '@grpc/proto-loader';
export interface TaskAssignment {
@@ -53,6 +54,13 @@ export interface TaskAssignment {
* Hibernation rehydration: session id to resume. Empty = fresh session.
*/
'resumeSessionId'?: (string);
+ /**
+ * Task-scoped on-behalf-of authority prepared for the assigned executor.
+ * The grant is audience-bound to this assignee/task and is revoked with the
+ * task lifecycle. It is delivered on the typed execution plane rather than
+ * requiring workers to parse server-enriched metadata.
+ */
+ 'authorization'?: (_aether_v1_AuthorizationContext | null);
}
export interface TaskAssignment__Output {
@@ -105,4 +113,11 @@ export interface TaskAssignment__Output {
* Hibernation rehydration: session id to resume. Empty = fresh session.
*/
'resumeSessionId': (string);
+ /**
+ * Task-scoped on-behalf-of authority prepared for the assigned executor.
+ * The grant is audience-bound to this assignee/task and is revoked with the
+ * task lifecycle. It is delivered on the typed execution plane rather than
+ * requiring workers to parse server-enriched metadata.
+ */
+ 'authorization': (_aether_v1_AuthorizationContext__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/UpstreamMessage.ts b/sdk/typescript/src/proto/aether/v1/UpstreamMessage.ts
index 57df043..b00af3a 100644
--- a/sdk/typescript/src/proto/aether/v1/UpstreamMessage.ts
+++ b/sdk/typescript/src/proto/aether/v1/UpstreamMessage.ts
@@ -31,6 +31,8 @@ import type { ConnectionStatusRequest as _aether_v1_ConnectionStatusRequest, Con
import type { SubmitAuditEventRequest as _aether_v1_SubmitAuditEventRequest, SubmitAuditEventRequest__Output as _aether_v1_SubmitAuditEventRequest__Output } from '../../aether/v1/SubmitAuditEventRequest';
import type { AuthorityRequestOperation as _aether_v1_AuthorityRequestOperation, AuthorityRequestOperation__Output as _aether_v1_AuthorityRequestOperation__Output } from '../../aether/v1/AuthorityRequestOperation';
import type { TaskSubscriptionOperation as _aether_v1_TaskSubscriptionOperation, TaskSubscriptionOperation__Output as _aether_v1_TaskSubscriptionOperation__Output } from '../../aether/v1/TaskSubscriptionOperation';
+import type { AccessCheckOperation as _aether_v1_AccessCheckOperation, AccessCheckOperation__Output as _aether_v1_AccessCheckOperation__Output } from '../../aether/v1/AccessCheckOperation';
+import type { BatchAccessCheckOperation as _aether_v1_BatchAccessCheckOperation, BatchAccessCheckOperation__Output as _aether_v1_BatchAccessCheckOperation__Output } from '../../aether/v1/BatchAccessCheckOperation';
export interface UpstreamMessage {
'init'?: (_aether_v1_InitConnection | null);
@@ -74,7 +76,9 @@ export interface UpstreamMessage {
* proto-side rewrites of every payload type.
*/
'activeExtensions'?: (string)[];
- 'payload'?: "init"|"send"|"switchWorkspace"|"kvOp"|"createTask"|"checkpointOp"|"adminQuery"|"sessionOp"|"taskQuery"|"taskOp"|"workspaceOp"|"agentOp"|"aclOp"|"progress"|"workflowOp"|"workflowResponse"|"tokenOp"|"auditQuery"|"authorityGrantOp"|"proxyHttpRequest"|"proxyHttpBodyChunk"|"tunnelOpen"|"tunnelData"|"tunnelClose"|"proxyHttpResponse"|"tunnelAck"|"resolveAuthorityRequest"|"connectionStatusRequest"|"submitAuditEvent"|"authorityRequestOp"|"taskSubscriptionOp";
+ 'accessCheck'?: (_aether_v1_AccessCheckOperation | null);
+ 'batchAccessCheck'?: (_aether_v1_BatchAccessCheckOperation | null);
+ 'payload'?: "init"|"send"|"switchWorkspace"|"kvOp"|"createTask"|"checkpointOp"|"adminQuery"|"sessionOp"|"taskQuery"|"taskOp"|"workspaceOp"|"agentOp"|"aclOp"|"progress"|"workflowOp"|"workflowResponse"|"tokenOp"|"auditQuery"|"authorityGrantOp"|"proxyHttpRequest"|"proxyHttpBodyChunk"|"tunnelOpen"|"tunnelData"|"tunnelClose"|"proxyHttpResponse"|"tunnelAck"|"resolveAuthorityRequest"|"connectionStatusRequest"|"submitAuditEvent"|"authorityRequestOp"|"taskSubscriptionOp"|"accessCheck"|"batchAccessCheck";
}
export interface UpstreamMessage__Output {
@@ -119,5 +123,7 @@ export interface UpstreamMessage__Output {
* proto-side rewrites of every payload type.
*/
'activeExtensions': (string)[];
- 'payload'?: "init"|"send"|"switchWorkspace"|"kvOp"|"createTask"|"checkpointOp"|"adminQuery"|"sessionOp"|"taskQuery"|"taskOp"|"workspaceOp"|"agentOp"|"aclOp"|"progress"|"workflowOp"|"workflowResponse"|"tokenOp"|"auditQuery"|"authorityGrantOp"|"proxyHttpRequest"|"proxyHttpBodyChunk"|"tunnelOpen"|"tunnelData"|"tunnelClose"|"proxyHttpResponse"|"tunnelAck"|"resolveAuthorityRequest"|"connectionStatusRequest"|"submitAuditEvent"|"authorityRequestOp"|"taskSubscriptionOp";
+ 'accessCheck'?: (_aether_v1_AccessCheckOperation__Output | null);
+ 'batchAccessCheck'?: (_aether_v1_BatchAccessCheckOperation__Output | null);
+ 'payload'?: "init"|"send"|"switchWorkspace"|"kvOp"|"createTask"|"checkpointOp"|"adminQuery"|"sessionOp"|"taskQuery"|"taskOp"|"workspaceOp"|"agentOp"|"aclOp"|"progress"|"workflowOp"|"workflowResponse"|"tokenOp"|"auditQuery"|"authorityGrantOp"|"proxyHttpRequest"|"proxyHttpBodyChunk"|"tunnelOpen"|"tunnelData"|"tunnelClose"|"proxyHttpResponse"|"tunnelAck"|"resolveAuthorityRequest"|"connectionStatusRequest"|"submitAuditEvent"|"authorityRequestOp"|"taskSubscriptionOp"|"accessCheck"|"batchAccessCheck";
}
diff --git a/sdk/typescript/src/proto/aether/v1/WorkflowAuthorityLifetimeMode.ts b/sdk/typescript/src/proto/aether/v1/WorkflowAuthorityLifetimeMode.ts
new file mode 100644
index 0000000..dff7f53
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/WorkflowAuthorityLifetimeMode.ts
@@ -0,0 +1,14 @@
+// Original file: aether.proto
+
+export const WorkflowAuthorityLifetimeMode = {
+ WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND: 'WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND',
+ WORKFLOW_AUTHORITY_LIFETIME_DURABLE: 'WORKFLOW_AUTHORITY_LIFETIME_DURABLE',
+} as const;
+
+export type WorkflowAuthorityLifetimeMode =
+ | 'WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND'
+ | 0
+ | 'WORKFLOW_AUTHORITY_LIFETIME_DURABLE'
+ | 1
+
+export type WorkflowAuthorityLifetimeMode__Output = typeof WorkflowAuthorityLifetimeMode[keyof typeof WorkflowAuthorityLifetimeMode]
diff --git a/sdk/typescript/src/proto/aether/v1/WorkflowOperation.ts b/sdk/typescript/src/proto/aether/v1/WorkflowOperation.ts
index 6e0a104..575644e 100644
--- a/sdk/typescript/src/proto/aether/v1/WorkflowOperation.ts
+++ b/sdk/typescript/src/proto/aether/v1/WorkflowOperation.ts
@@ -1,5 +1,8 @@
// Original file: aether.proto
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+import type { WorkflowScheduleAuthorityScope as _aether_v1_WorkflowScheduleAuthorityScope, WorkflowScheduleAuthorityScope__Output as _aether_v1_WorkflowScheduleAuthorityScope__Output } from '../../aether/v1/WorkflowScheduleAuthorityScope';
+import type { WorkflowRequestContext as _aether_v1_WorkflowRequestContext, WorkflowRequestContext__Output as _aether_v1_WorkflowRequestContext__Output } from '../../aether/v1/WorkflowRequestContext';
// Original file: aether.proto
@@ -183,6 +186,21 @@ export interface WorkflowOperation {
* For LIST_EXECUTIONS
*/
'statusFilter'?: (string);
+ /**
+ * Optional caller OBO authority. Resolved by the gateway before the request
+ * is authorized and forwarded.
+ */
+ 'authorization'?: (_aether_v1_AuthorizationContext | null);
+ /**
+ * Optional requested authority for CREATE_SCHEDULE / UPSERT_SCHEDULE. The
+ * gateway derives or mints the exact WorkflowEngine schedule grant and
+ * forwards only the resulting trusted request_context.
+ */
+ 'scheduleAuthorityScope'?: (_aether_v1_WorkflowScheduleAuthorityScope | null);
+ /**
+ * Gateway-authored; caller values are always discarded.
+ */
+ 'requestContext'?: (_aether_v1_WorkflowRequestContext | null);
}
/**
@@ -217,4 +235,19 @@ export interface WorkflowOperation__Output {
* For LIST_EXECUTIONS
*/
'statusFilter': (string);
+ /**
+ * Optional caller OBO authority. Resolved by the gateway before the request
+ * is authorized and forwarded.
+ */
+ 'authorization': (_aether_v1_AuthorizationContext__Output | null);
+ /**
+ * Optional requested authority for CREATE_SCHEDULE / UPSERT_SCHEDULE. The
+ * gateway derives or mints the exact WorkflowEngine schedule grant and
+ * forwards only the resulting trusted request_context.
+ */
+ 'scheduleAuthorityScope': (_aether_v1_WorkflowScheduleAuthorityScope__Output | null);
+ /**
+ * Gateway-authored; caller values are always discarded.
+ */
+ 'requestContext': (_aether_v1_WorkflowRequestContext__Output | null);
}
diff --git a/sdk/typescript/src/proto/aether/v1/WorkflowRequestContext.ts b/sdk/typescript/src/proto/aether/v1/WorkflowRequestContext.ts
new file mode 100644
index 0000000..2c54e82
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/WorkflowRequestContext.ts
@@ -0,0 +1,46 @@
+// Original file: aether.proto
+
+import type { PrincipalRef as _aether_v1_PrincipalRef, PrincipalRef__Output as _aether_v1_PrincipalRef__Output } from '../../aether/v1/PrincipalRef';
+import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from '../../aether/v1/AuthorizationContext';
+import type { WorkflowAuthorityLifetimeMode as _aether_v1_WorkflowAuthorityLifetimeMode, WorkflowAuthorityLifetimeMode__Output as _aether_v1_WorkflowAuthorityLifetimeMode__Output } from '../../aether/v1/WorkflowAuthorityLifetimeMode';
+import type { Long } from '@grpc/proto-loader';
+
+/**
+ * Gateway-authored workflow request identity and schedule authority. The
+ * gateway clears any client-supplied value before forwarding. Schedule grant
+ * IDs remain outside action JSON, task payload/metadata, and workflow response
+ * data. Consumers must treat this object as trusted only on the authenticated
+ * WorkflowEngine connection from the gateway.
+ */
+export interface WorkflowRequestContext {
+ 'actor'?: (_aether_v1_PrincipalRef | null);
+ 'subject'?: (_aether_v1_PrincipalRef | null);
+ 'actorSessionId'?: (string);
+ 'scheduleAuthorization'?: (_aether_v1_AuthorizationContext | null);
+ 'rootGrantId'?: (string);
+ 'sourceGrantId'?: (string);
+ 'expiresAtMs'?: (number | string | Long);
+ 'policyDigest'?: (string);
+ 'lifetimeMode'?: (_aether_v1_WorkflowAuthorityLifetimeMode);
+ 'policyVersion'?: (number);
+}
+
+/**
+ * Gateway-authored workflow request identity and schedule authority. The
+ * gateway clears any client-supplied value before forwarding. Schedule grant
+ * IDs remain outside action JSON, task payload/metadata, and workflow response
+ * data. Consumers must treat this object as trusted only on the authenticated
+ * WorkflowEngine connection from the gateway.
+ */
+export interface WorkflowRequestContext__Output {
+ 'actor': (_aether_v1_PrincipalRef__Output | null);
+ 'subject': (_aether_v1_PrincipalRef__Output | null);
+ 'actorSessionId': (string);
+ 'scheduleAuthorization': (_aether_v1_AuthorizationContext__Output | null);
+ 'rootGrantId': (string);
+ 'sourceGrantId': (string);
+ 'expiresAtMs': (string);
+ 'policyDigest': (string);
+ 'lifetimeMode': (_aether_v1_WorkflowAuthorityLifetimeMode__Output);
+ 'policyVersion': (number);
+}
diff --git a/sdk/typescript/src/proto/aether/v1/WorkflowScheduleAuthorityScope.ts b/sdk/typescript/src/proto/aether/v1/WorkflowScheduleAuthorityScope.ts
new file mode 100644
index 0000000..cfb1063
--- /dev/null
+++ b/sdk/typescript/src/proto/aether/v1/WorkflowScheduleAuthorityScope.ts
@@ -0,0 +1,51 @@
+// Original file: aether.proto
+
+import type { ACLAuthorityGrantResourceScopeEntry as _aether_v1_ACLAuthorityGrantResourceScopeEntry, ACLAuthorityGrantResourceScopeEntry__Output as _aether_v1_ACLAuthorityGrantResourceScopeEntry__Output } from '../../aether/v1/ACLAuthorityGrantResourceScopeEntry';
+import type { WorkflowAuthorityLifetimeMode as _aether_v1_WorkflowAuthorityLifetimeMode, WorkflowAuthorityLifetimeMode__Output as _aether_v1_WorkflowAuthorityLifetimeMode__Output } from '../../aether/v1/WorkflowAuthorityLifetimeMode';
+import type { Long } from '@grpc/proto-loader';
+
+/**
+ * Requested ceiling for the private authority attached to one schedule. The
+ * gateway validates/attenuates this against the authenticated caller context;
+ * the WorkflowEngine never trusts it directly and never stores it in action
+ * JSON. Empty resource or operation scope is invalid.
+ */
+export interface WorkflowScheduleAuthorityScope {
+ 'workspaceScope'?: (string)[];
+ 'resourceScope'?: (_aether_v1_ACLAuthorityGrantResourceScopeEntry)[];
+ 'operationScope'?: (string)[];
+ 'maxAccessLevel'?: (number);
+ 'expiresAt'?: (number | string | Long);
+ 'renewableUntil'?: (number | string | Long);
+ 'requiredTaskAuthorityHops'?: (number);
+ 'lifetimeMode'?: (_aether_v1_WorkflowAuthorityLifetimeMode);
+ /**
+ * Version of the deterministic schedule-authority policy shape. Callers
+ * currently send 1; unknown versions fail closed instead of being silently
+ * reinterpreted after an upgrade.
+ */
+ 'policyVersion'?: (number);
+}
+
+/**
+ * Requested ceiling for the private authority attached to one schedule. The
+ * gateway validates/attenuates this against the authenticated caller context;
+ * the WorkflowEngine never trusts it directly and never stores it in action
+ * JSON. Empty resource or operation scope is invalid.
+ */
+export interface WorkflowScheduleAuthorityScope__Output {
+ 'workspaceScope': (string)[];
+ 'resourceScope': (_aether_v1_ACLAuthorityGrantResourceScopeEntry__Output)[];
+ 'operationScope': (string)[];
+ 'maxAccessLevel': (number);
+ 'expiresAt': (string);
+ 'renewableUntil': (string);
+ 'requiredTaskAuthorityHops': (number);
+ 'lifetimeMode': (_aether_v1_WorkflowAuthorityLifetimeMode__Output);
+ /**
+ * Version of the deterministic schedule-authority policy shape. Callers
+ * currently send 1; unknown versions fail closed instead of being silently
+ * reinterpreted after an upgrade.
+ */
+ 'policyVersion': (number);
+}
diff --git a/sdk/typescript/src/proto/sandbox_relay_tunnel.ts b/sdk/typescript/src/proto/sandbox_relay_tunnel.ts
index 783945b..6f8a20b 100644
--- a/sdk/typescript/src/proto/sandbox_relay_tunnel.ts
+++ b/sdk/typescript/src/proto/sandbox_relay_tunnel.ts
@@ -26,6 +26,9 @@ import type { ACLRoleRequest as _aether_v1_ACLRoleRequest, ACLRoleRequest__Outpu
import type { ACLRuleFilter as _aether_v1_ACLRuleFilter, ACLRuleFilter__Output as _aether_v1_ACLRuleFilter__Output } from './aether/v1/ACLRuleFilter';
import type { ACLRuleInfo as _aether_v1_ACLRuleInfo, ACLRuleInfo__Output as _aether_v1_ACLRuleInfo__Output } from './aether/v1/ACLRuleInfo';
import type { ACLSetFallbackRequest as _aether_v1_ACLSetFallbackRequest, ACLSetFallbackRequest__Output as _aether_v1_ACLSetFallbackRequest__Output } from './aether/v1/ACLSetFallbackRequest';
+import type { AccessCheckOperation as _aether_v1_AccessCheckOperation, AccessCheckOperation__Output as _aether_v1_AccessCheckOperation__Output } from './aether/v1/AccessCheckOperation';
+import type { AccessCheckResponse as _aether_v1_AccessCheckResponse, AccessCheckResponse__Output as _aether_v1_AccessCheckResponse__Output } from './aether/v1/AccessCheckResponse';
+import type { AccessDecisionReceipt as _aether_v1_AccessDecisionReceipt, AccessDecisionReceipt__Output as _aether_v1_AccessDecisionReceipt__Output } from './aether/v1/AccessDecisionReceipt';
import type { AdminQuery as _aether_v1_AdminQuery, AdminQuery__Output as _aether_v1_AdminQuery__Output } from './aether/v1/AdminQuery';
import type { AdminResponse as _aether_v1_AdminResponse, AdminResponse__Output as _aether_v1_AdminResponse__Output } from './aether/v1/AdminResponse';
import type { AetherGatewayClient as _aether_v1_AetherGatewayClient, AetherGatewayDefinition as _aether_v1_AetherGatewayDefinition } from './aether/v1/AetherGateway';
@@ -40,6 +43,8 @@ import type { AgentResponse as _aether_v1_AgentResponse, AgentResponse__Output a
import type { AuditEntry as _aether_v1_AuditEntry, AuditEntry__Output as _aether_v1_AuditEntry__Output } from './aether/v1/AuditEntry';
import type { AuditQuery as _aether_v1_AuditQuery, AuditQuery__Output as _aether_v1_AuditQuery__Output } from './aether/v1/AuditQuery';
import type { AuditQueryResponse as _aether_v1_AuditQueryResponse, AuditQueryResponse__Output as _aether_v1_AuditQueryResponse__Output } from './aether/v1/AuditQueryResponse';
+import type { AuthorityContinuationRequest as _aether_v1_AuthorityContinuationRequest, AuthorityContinuationRequest__Output as _aether_v1_AuthorityContinuationRequest__Output } from './aether/v1/AuthorityContinuationRequest';
+import type { AuthorityContinuationScope as _aether_v1_AuthorityContinuationScope, AuthorityContinuationScope__Output as _aether_v1_AuthorityContinuationScope__Output } from './aether/v1/AuthorityContinuationScope';
import type { AuthorityGrantBatchExchangeRequest as _aether_v1_AuthorityGrantBatchExchangeRequest, AuthorityGrantBatchExchangeRequest__Output as _aether_v1_AuthorityGrantBatchExchangeRequest__Output } from './aether/v1/AuthorityGrantBatchExchangeRequest';
import type { AuthorityGrantDeriveForTargetRequest as _aether_v1_AuthorityGrantDeriveForTargetRequest, AuthorityGrantDeriveForTargetRequest__Output as _aether_v1_AuthorityGrantDeriveForTargetRequest__Output } from './aether/v1/AuthorityGrantDeriveForTargetRequest';
import type { AuthorityGrantDeriveRequest as _aether_v1_AuthorityGrantDeriveRequest, AuthorityGrantDeriveRequest__Output as _aether_v1_AuthorityGrantDeriveRequest__Output } from './aether/v1/AuthorityGrantDeriveRequest';
@@ -59,6 +64,8 @@ import type { AuthorityRequestResourceScopeEntry as _aether_v1_AuthorityRequestR
import type { AuthorityRequestRoutingTarget as _aether_v1_AuthorityRequestRoutingTarget, AuthorityRequestRoutingTarget__Output as _aether_v1_AuthorityRequestRoutingTarget__Output } from './aether/v1/AuthorityRequestRoutingTarget';
import type { AuthoritySpan as _aether_v1_AuthoritySpan, AuthoritySpan__Output as _aether_v1_AuthoritySpan__Output } from './aether/v1/AuthoritySpan';
import type { AuthorizationContext as _aether_v1_AuthorizationContext, AuthorizationContext__Output as _aether_v1_AuthorizationContext__Output } from './aether/v1/AuthorizationContext';
+import type { BatchAccessCheckOperation as _aether_v1_BatchAccessCheckOperation, BatchAccessCheckOperation__Output as _aether_v1_BatchAccessCheckOperation__Output } from './aether/v1/BatchAccessCheckOperation';
+import type { BatchAccessCheckResponse as _aether_v1_BatchAccessCheckResponse, BatchAccessCheckResponse__Output as _aether_v1_BatchAccessCheckResponse__Output } from './aether/v1/BatchAccessCheckResponse';
import type { BridgeIdentity as _aether_v1_BridgeIdentity, BridgeIdentity__Output as _aether_v1_BridgeIdentity__Output } from './aether/v1/BridgeIdentity';
import type { BuildInfo as _aether_v1_BuildInfo, BuildInfo__Output as _aether_v1_BuildInfo__Output } from './aether/v1/BuildInfo';
import type { CheckpointOperation as _aether_v1_CheckpointOperation, CheckpointOperation__Output as _aether_v1_CheckpointOperation__Output } from './aether/v1/CheckpointOperation';
@@ -77,6 +84,7 @@ import type { ErrorResponse as _aether_v1_ErrorResponse, ErrorResponse__Output a
import type { ExtensionDeclaration as _aether_v1_ExtensionDeclaration, ExtensionDeclaration__Output as _aether_v1_ExtensionDeclaration__Output } from './aether/v1/ExtensionDeclaration';
import type { FlowEdge as _aether_v1_FlowEdge, FlowEdge__Output as _aether_v1_FlowEdge__Output } from './aether/v1/FlowEdge';
import type { FlowNode as _aether_v1_FlowNode, FlowNode__Output as _aether_v1_FlowNode__Output } from './aether/v1/FlowNode';
+import type { ForwardedAuthorization as _aether_v1_ForwardedAuthorization, ForwardedAuthorization__Output as _aether_v1_ForwardedAuthorization__Output } from './aether/v1/ForwardedAuthorization';
import type { GatewayInfo as _aether_v1_GatewayInfo, GatewayInfo__Output as _aether_v1_GatewayInfo__Output } from './aether/v1/GatewayInfo';
import type { GatewayStats as _aether_v1_GatewayStats, GatewayStats__Output as _aether_v1_GatewayStats__Output } from './aether/v1/GatewayStats';
import type { HealthCheck as _aether_v1_HealthCheck, HealthCheck__Output as _aether_v1_HealthCheck__Output } from './aether/v1/HealthCheck';
@@ -107,6 +115,7 @@ import type { ResolveAuthorityRequestPayload as _aether_v1_ResolveAuthorityReque
import type { ResolveAuthorityResponse as _aether_v1_ResolveAuthorityResponse, ResolveAuthorityResponse__Output as _aether_v1_ResolveAuthorityResponse__Output } from './aether/v1/ResolveAuthorityResponse';
import type { ResolvedAuthority as _aether_v1_ResolvedAuthority, ResolvedAuthority__Output as _aether_v1_ResolvedAuthority__Output } from './aether/v1/ResolvedAuthority';
import type { ResolvedAuthorityInfo as _aether_v1_ResolvedAuthorityInfo, ResolvedAuthorityInfo__Output as _aether_v1_ResolvedAuthorityInfo__Output } from './aether/v1/ResolvedAuthorityInfo';
+import type { ResourceAccessRequest as _aether_v1_ResourceAccessRequest, ResourceAccessRequest__Output as _aether_v1_ResourceAccessRequest__Output } from './aether/v1/ResourceAccessRequest';
import type { RetryPolicy as _aether_v1_RetryPolicy, RetryPolicy__Output as _aether_v1_RetryPolicy__Output } from './aether/v1/RetryPolicy';
import type { SandboxRelayTunnelClient as _aether_v1_SandboxRelayTunnelClient, SandboxRelayTunnelDefinition as _aether_v1_SandboxRelayTunnelDefinition } from './aether/v1/SandboxRelayTunnel';
import type { SendMessage as _aether_v1_SendMessage, SendMessage__Output as _aether_v1_SendMessage__Output } from './aether/v1/SendMessage';
@@ -152,7 +161,9 @@ import type { WaitSpec as _aether_v1_WaitSpec, WaitSpec__Output as _aether_v1_Wa
import type { WatchTenantsRequest as _aether_v1_WatchTenantsRequest, WatchTenantsRequest__Output as _aether_v1_WatchTenantsRequest__Output } from './aether/v1/WatchTenantsRequest';
import type { WorkflowEngineIdentity as _aether_v1_WorkflowEngineIdentity, WorkflowEngineIdentity__Output as _aether_v1_WorkflowEngineIdentity__Output } from './aether/v1/WorkflowEngineIdentity';
import type { WorkflowOperation as _aether_v1_WorkflowOperation, WorkflowOperation__Output as _aether_v1_WorkflowOperation__Output } from './aether/v1/WorkflowOperation';
+import type { WorkflowRequestContext as _aether_v1_WorkflowRequestContext, WorkflowRequestContext__Output as _aether_v1_WorkflowRequestContext__Output } from './aether/v1/WorkflowRequestContext';
import type { WorkflowResponse as _aether_v1_WorkflowResponse, WorkflowResponse__Output as _aether_v1_WorkflowResponse__Output } from './aether/v1/WorkflowResponse';
+import type { WorkflowScheduleAuthorityScope as _aether_v1_WorkflowScheduleAuthorityScope, WorkflowScheduleAuthorityScope__Output as _aether_v1_WorkflowScheduleAuthorityScope__Output } from './aether/v1/WorkflowScheduleAuthorityScope';
import type { WorkspaceFilter as _aether_v1_WorkspaceFilter, WorkspaceFilter__Output as _aether_v1_WorkspaceFilter__Output } from './aether/v1/WorkspaceFilter';
import type { WorkspaceInfo as _aether_v1_WorkspaceInfo, WorkspaceInfo__Output as _aether_v1_WorkspaceInfo__Output } from './aether/v1/WorkspaceInfo';
import type { WorkspaceOperation as _aether_v1_WorkspaceOperation, WorkspaceOperation__Output as _aether_v1_WorkspaceOperation__Output } from './aether/v1/WorkspaceOperation';
@@ -190,6 +201,9 @@ export interface ProtoGrpcType {
ACLRuleFilter: MessageTypeDefinition<_aether_v1_ACLRuleFilter, _aether_v1_ACLRuleFilter__Output>
ACLRuleInfo: MessageTypeDefinition<_aether_v1_ACLRuleInfo, _aether_v1_ACLRuleInfo__Output>
ACLSetFallbackRequest: MessageTypeDefinition<_aether_v1_ACLSetFallbackRequest, _aether_v1_ACLSetFallbackRequest__Output>
+ AccessCheckOperation: MessageTypeDefinition<_aether_v1_AccessCheckOperation, _aether_v1_AccessCheckOperation__Output>
+ AccessCheckResponse: MessageTypeDefinition<_aether_v1_AccessCheckResponse, _aether_v1_AccessCheckResponse__Output>
+ AccessDecisionReceipt: MessageTypeDefinition<_aether_v1_AccessDecisionReceipt, _aether_v1_AccessDecisionReceipt__Output>
AccessLevel: EnumTypeDefinition
AdminQuery: MessageTypeDefinition<_aether_v1_AdminQuery, _aether_v1_AdminQuery__Output>
AdminResponse: MessageTypeDefinition<_aether_v1_AdminResponse, _aether_v1_AdminResponse__Output>
@@ -205,6 +219,8 @@ export interface ProtoGrpcType {
AuditEntry: MessageTypeDefinition<_aether_v1_AuditEntry, _aether_v1_AuditEntry__Output>
AuditQuery: MessageTypeDefinition<_aether_v1_AuditQuery, _aether_v1_AuditQuery__Output>
AuditQueryResponse: MessageTypeDefinition<_aether_v1_AuditQueryResponse, _aether_v1_AuditQueryResponse__Output>
+ AuthorityContinuationRequest: MessageTypeDefinition<_aether_v1_AuthorityContinuationRequest, _aether_v1_AuthorityContinuationRequest__Output>
+ AuthorityContinuationScope: MessageTypeDefinition<_aether_v1_AuthorityContinuationScope, _aether_v1_AuthorityContinuationScope__Output>
AuthorityGrantBatchExchangeRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantBatchExchangeRequest, _aether_v1_AuthorityGrantBatchExchangeRequest__Output>
AuthorityGrantDeriveForTargetRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantDeriveForTargetRequest, _aether_v1_AuthorityGrantDeriveForTargetRequest__Output>
AuthorityGrantDeriveRequest: MessageTypeDefinition<_aether_v1_AuthorityGrantDeriveRequest, _aether_v1_AuthorityGrantDeriveRequest__Output>
@@ -226,6 +242,8 @@ export interface ProtoGrpcType {
AuthoritySpan: MessageTypeDefinition<_aether_v1_AuthoritySpan, _aether_v1_AuthoritySpan__Output>
AuthorizationContext: MessageTypeDefinition<_aether_v1_AuthorizationContext, _aether_v1_AuthorizationContext__Output>
BackoffStrategy: EnumTypeDefinition
+ BatchAccessCheckOperation: MessageTypeDefinition<_aether_v1_BatchAccessCheckOperation, _aether_v1_BatchAccessCheckOperation__Output>
+ BatchAccessCheckResponse: MessageTypeDefinition<_aether_v1_BatchAccessCheckResponse, _aether_v1_BatchAccessCheckResponse__Output>
BridgeIdentity: MessageTypeDefinition<_aether_v1_BridgeIdentity, _aether_v1_BridgeIdentity__Output>
BuildInfo: MessageTypeDefinition<_aether_v1_BuildInfo, _aether_v1_BuildInfo__Output>
CheckpointOperation: MessageTypeDefinition<_aether_v1_CheckpointOperation, _aether_v1_CheckpointOperation__Output>
@@ -244,6 +262,7 @@ export interface ProtoGrpcType {
ExtensionDeclaration: MessageTypeDefinition<_aether_v1_ExtensionDeclaration, _aether_v1_ExtensionDeclaration__Output>
FlowEdge: MessageTypeDefinition<_aether_v1_FlowEdge, _aether_v1_FlowEdge__Output>
FlowNode: MessageTypeDefinition<_aether_v1_FlowNode, _aether_v1_FlowNode__Output>
+ ForwardedAuthorization: MessageTypeDefinition<_aether_v1_ForwardedAuthorization, _aether_v1_ForwardedAuthorization__Output>
GatewayInfo: MessageTypeDefinition<_aether_v1_GatewayInfo, _aether_v1_GatewayInfo__Output>
GatewayStats: MessageTypeDefinition<_aether_v1_GatewayStats, _aether_v1_GatewayStats__Output>
HealthCheck: MessageTypeDefinition<_aether_v1_HealthCheck, _aether_v1_HealthCheck__Output>
@@ -279,6 +298,7 @@ export interface ProtoGrpcType {
ResolveAuthorityResponse: MessageTypeDefinition<_aether_v1_ResolveAuthorityResponse, _aether_v1_ResolveAuthorityResponse__Output>
ResolvedAuthority: MessageTypeDefinition<_aether_v1_ResolvedAuthority, _aether_v1_ResolvedAuthority__Output>
ResolvedAuthorityInfo: MessageTypeDefinition<_aether_v1_ResolvedAuthorityInfo, _aether_v1_ResolvedAuthorityInfo__Output>
+ ResourceAccessRequest: MessageTypeDefinition<_aether_v1_ResourceAccessRequest, _aether_v1_ResourceAccessRequest__Output>
RetryPolicy: MessageTypeDefinition<_aether_v1_RetryPolicy, _aether_v1_RetryPolicy__Output>
/**
* SandboxRelayTunnel is the aggregator's relay-facing surface. A tenant-relay
@@ -297,6 +317,7 @@ export interface ProtoGrpcType {
SubmitAuditEventRequest: MessageTypeDefinition<_aether_v1_SubmitAuditEventRequest, _aether_v1_SubmitAuditEventRequest__Output>
SubmitAuditEventResponse: MessageTypeDefinition<_aether_v1_SubmitAuditEventResponse, _aether_v1_SubmitAuditEventResponse__Output>
SwitchWorkspace: MessageTypeDefinition<_aether_v1_SwitchWorkspace, _aether_v1_SwitchWorkspace__Output>
+ TargetOfflinePolicy: EnumTypeDefinition
TaskAssignment: MessageTypeDefinition<_aether_v1_TaskAssignment, _aether_v1_TaskAssignment__Output>
TaskAssignmentMode: EnumTypeDefinition
TaskAuthorityRequestEventRelay: MessageTypeDefinition<_aether_v1_TaskAuthorityRequestEventRelay, _aether_v1_TaskAuthorityRequestEventRelay__Output>
@@ -335,9 +356,12 @@ export interface ProtoGrpcType {
WaitReason: EnumTypeDefinition
WaitSpec: MessageTypeDefinition<_aether_v1_WaitSpec, _aether_v1_WaitSpec__Output>
WatchTenantsRequest: MessageTypeDefinition<_aether_v1_WatchTenantsRequest, _aether_v1_WatchTenantsRequest__Output>
+ WorkflowAuthorityLifetimeMode: EnumTypeDefinition
WorkflowEngineIdentity: MessageTypeDefinition<_aether_v1_WorkflowEngineIdentity, _aether_v1_WorkflowEngineIdentity__Output>
WorkflowOperation: MessageTypeDefinition<_aether_v1_WorkflowOperation, _aether_v1_WorkflowOperation__Output>
+ WorkflowRequestContext: MessageTypeDefinition<_aether_v1_WorkflowRequestContext, _aether_v1_WorkflowRequestContext__Output>
WorkflowResponse: MessageTypeDefinition<_aether_v1_WorkflowResponse, _aether_v1_WorkflowResponse__Output>
+ WorkflowScheduleAuthorityScope: MessageTypeDefinition<_aether_v1_WorkflowScheduleAuthorityScope, _aether_v1_WorkflowScheduleAuthorityScope__Output>
WorkspaceFilter: MessageTypeDefinition<_aether_v1_WorkspaceFilter, _aether_v1_WorkspaceFilter__Output>
WorkspaceInfo: MessageTypeDefinition<_aether_v1_WorkspaceInfo, _aether_v1_WorkspaceInfo__Output>
WorkspaceOperation: MessageTypeDefinition<_aether_v1_WorkspaceOperation, _aether_v1_WorkspaceOperation__Output>
diff --git a/sdk/typescript/src/proxy.ts b/sdk/typescript/src/proxy.ts
index 4342478..35ffba7 100644
--- a/sdk/typescript/src/proxy.ts
+++ b/sdk/typescript/src/proxy.ts
@@ -13,6 +13,7 @@
import type { AetherClient } from "./client.js";
import { ConnectionError, TimeoutError } from "./errors.js";
+import type { AuthorizationContext, ResourceAccessRequest } from "./types.js";
// =============================================================================
// Constants
@@ -62,6 +63,12 @@ export interface ProxyHttpOptions {
timeoutMs?: number;
/** Whether to follow HTTP redirects (default: true). */
followRedirects?: boolean;
+ /** Optional application workspace context for the proxied request. */
+ appWorkspace?: string;
+ /** Optional direct or on-behalf-of authority context. */
+ authorization?: AuthorizationContext;
+ /** Exact logical resource the gateway must authorize before delivery. */
+ checkedAccess?: ResourceAccessRequest;
/**
* Pin the request to a named terminator backend. The backend's allow-list
* still applies — explicit naming selects which backend's ACL is consulted,
@@ -135,6 +142,14 @@ export async function proxyHttp(
const body = opts.body ?? new Uint8Array(0);
const headers = opts.headers ?? {};
const followRedirects = opts.followRedirects ?? true;
+ const appWorkspace = opts.appWorkspace ?? "";
+ const authorization = opts.authorization;
+ const checkedAccess = opts.checkedAccess
+ ? {
+ ...opts.checkedAccess,
+ correlationId: opts.checkedAccess.correlationId || requestId,
+ }
+ : undefined;
const backendName = opts.backend ?? "";
const streamResponse = opts.streamResponse ?? false;
const streamIdleTimeoutMs = opts.streamIdleTimeoutMs ?? 0;
@@ -221,6 +236,9 @@ export async function proxyHttp(
bodyChunked: false,
timeoutMs,
followRedirects,
+ appWorkspace,
+ authorization,
+ checkedAccess,
backendName,
streamResponseIndefinitely: streamResponse,
streamIdleTimeoutMs,
@@ -240,6 +258,9 @@ export async function proxyHttp(
bodyChunked: true,
timeoutMs,
followRedirects,
+ appWorkspace,
+ authorization,
+ checkedAccess,
backendName,
streamResponseIndefinitely: streamResponse,
streamIdleTimeoutMs,
diff --git a/sdk/typescript/src/tasks.ts b/sdk/typescript/src/tasks.ts
index f0473cf..7d6a2fa 100644
--- a/sdk/typescript/src/tasks.ts
+++ b/sdk/typescript/src/tasks.ts
@@ -10,7 +10,7 @@
import { AetherClient } from "./client.js";
import type { AetherClientOptions } from "./client.js";
-import { MessageType, TaskAssignmentMode } from "./types.js";
+import { MessageType, TargetOfflinePolicy, TaskAssignmentMode } from "./types.js";
import { InvalidArgumentError } from "./errors.js";
import {
agentTopic,
@@ -266,9 +266,12 @@ export class TaskClient extends AetherClient {
workspace,
assignmentMode,
targetAgentId: opts.targetAgentId ?? "",
+ targetOfflinePolicy: opts.targetOfflinePolicy ?? TargetOfflinePolicy.Unspecified,
targetImplementation: opts.targetImplementation ?? "",
launchParamOverrides: opts.launchParamOverrides ?? {},
metadata: opts.metadata ?? {},
+ parentTaskId: opts.parentTaskId ?? "",
+ requiredDownstreamAuthorityHops: opts.requiredDownstreamAuthorityHops ?? 0,
},
});
}
diff --git a/sdk/typescript/src/types.ts b/sdk/typescript/src/types.ts
index 81c8882..280f63e 100644
--- a/sdk/typescript/src/types.ts
+++ b/sdk/typescript/src/types.ts
@@ -104,6 +104,18 @@ export enum TaskAssignmentMode {
Pool = 2,
}
+/**
+ * TARGETED task behavior while the exact target identity is disconnected.
+ * Unspecified preserves orchestration; Queue waits for a static worker to
+ * reconnect; Reject fails creation.
+ */
+export enum TargetOfflinePolicy {
+ Unspecified = 0,
+ Orchestrate = 1,
+ Queue = 2,
+ Reject = 3,
+}
+
/**
* Dispatch priority for tasks. Higher priority pending tasks are delivered
* before lower ones (ties break FIFO). Values are spaced to allow inserting
@@ -144,6 +156,14 @@ export interface IncomingMessage {
readonly payload: Uint8Array;
/** The type of the message (Chat, Control, ToolCall, Event, Metric). */
readonly messageType?: number;
+ /** Gateway-verified workspace context, when one applies. */
+ readonly workspace: string;
+ /** Gateway-resolved OBO subject, when the sender acted for another principal. */
+ readonly onBehalfSubject?: PrincipalRef;
+ /** Gateway-authored exact-resource receipt for a checked send. */
+ readonly accessReceipt?: AccessDecisionReceipt;
+ /** Gateway-derived leaf authority for this exact service or agent recipient. */
+ readonly forwardedAuthorization?: ForwardedAuthorization;
/** Local timestamp when the message was received. */
readonly receivedAt: Date;
}
@@ -158,6 +178,92 @@ export interface OutgoingMessage {
payload: Uint8Array;
/** The type of message. Defaults to Chat. */
messageType?: MessageType;
+ /** Optional user/application workspace context. */
+ appWorkspace?: string;
+ /** Optional on-behalf-of authority context. */
+ authorization?: AuthorizationContext;
+ /** Optional exact logical-resource check, additive to topic authorization. */
+ checkedAccess?: ResourceAccessRequest;
+ /** Explicitly derive and attach target-bound authority for the recipient. */
+ authorityContinuation?: AuthorityContinuationRequest;
+}
+
+/** Stable principal reference used by runtime authorization metadata. */
+export interface PrincipalRef {
+ readonly principalType: string;
+ readonly principalId: string;
+}
+
+/** Caller-supplied direct or on-behalf-of authorization context. */
+export interface AuthorizationContext {
+ readonly authorityMode: string;
+ readonly subject?: PrincipalRef;
+ readonly grantId?: string;
+}
+
+/** Gateway-derived, target-bound authorization continuation. */
+export interface ForwardedAuthorization {
+ readonly authorization: AuthorizationContext;
+ readonly rootGrantId: string;
+ readonly expiresAtMs: number;
+ readonly deliveryTarget: string;
+ readonly bindingId: string;
+ readonly scope: AuthorityContinuationScope;
+}
+
+/** Scope ceiling for a target-bound authority continuation. */
+export interface AuthorityContinuationScope {
+ readonly workspaceScope: string[];
+ readonly resourceScope: AuthorityGrantResourceScopeEntry[];
+ readonly operationScope: string[];
+ readonly maxAccessLevel: number;
+}
+
+export interface AuthorityGrantResourceScopeEntry {
+ readonly resourceType: string;
+ readonly patterns: string[];
+}
+
+export enum AuthorityContinuationScopeMode {
+ Unspecified = 0,
+ InheritParent = 1,
+ Attenuate = 2,
+}
+
+/** Request for gateway-derived recipient authority. */
+export interface AuthorityContinuationRequest {
+ readonly scopeMode: AuthorityContinuationScopeMode;
+ readonly bindingId?: string;
+ readonly scope?: AuthorityContinuationScope;
+}
+
+/** Exact logical-resource tuple evaluated by the Aether gateway. */
+export interface ResourceAccessRequest {
+ readonly resourceType: string;
+ readonly resourceId: string;
+ readonly operation: string;
+ readonly workspace?: string;
+ readonly requiredAccessLevel: number;
+ readonly correlationId: string;
+}
+
+/** Gateway-authored, short-lived result of one exact resource check. */
+export interface AccessDecisionReceipt {
+ readonly decisionId: string;
+ readonly request: ResourceAccessRequest;
+ readonly allowed: boolean;
+ readonly decision: string;
+ readonly effectiveAccessLevel: number;
+ readonly actor?: PrincipalRef;
+ readonly subject?: PrincipalRef;
+ readonly rootSubject?: PrincipalRef;
+ readonly authorityMode: string;
+ readonly grantId: string;
+ readonly rootGrantId: string;
+ readonly evaluatedAtMs: number;
+ readonly expiresAtMs: number;
+ readonly denialCode: string;
+ readonly deliveryTarget: string;
}
/**
@@ -214,6 +320,25 @@ export interface ConnectionAck {
/**
* A task assignment received by orchestrators.
*/
+export interface TaskAssignmentResolvedAuthority {
+ readonly rootSubject?: AuthorityGrantPrincipalRef;
+ readonly audienceType: string;
+ readonly audienceId: string;
+ readonly maxAccessLevel: number;
+ readonly workspaceScope: string[];
+ readonly expiresAtMs: number;
+}
+
+/**
+ * Task-scoped authority prepared by the gateway for the assigned executor.
+ */
+export interface TaskAssignmentAuthorization {
+ readonly authorityMode: string;
+ readonly subject?: AuthorityGrantPrincipalRef;
+ readonly grantId: string;
+ readonly resolved?: TaskAssignmentResolvedAuthority;
+}
+
export interface TaskAssignment {
readonly taskId: string;
readonly taskType: string;
@@ -225,6 +350,11 @@ export interface TaskAssignment {
readonly targetImplementation: string;
readonly workspace: string;
readonly specifier: string;
+ readonly payload: Uint8Array;
+ readonly taskClass: number;
+ readonly checkpointKey: string;
+ readonly resumeSessionId: string;
+ readonly authorization?: TaskAssignmentAuthorization;
}
// =============================================================================
diff --git a/sdk/typescript/src/users.ts b/sdk/typescript/src/users.ts
index afb2739..b235b4f 100644
--- a/sdk/typescript/src/users.ts
+++ b/sdk/typescript/src/users.ts
@@ -13,7 +13,7 @@
import { AetherClient } from "./client.js";
import type { AetherClientOptions } from "./client.js";
-import { MessageType, TaskAssignmentMode } from "./types.js";
+import { MessageType, TargetOfflinePolicy, TaskAssignmentMode } from "./types.js";
import type { MessageHandler } from "./types.js";
import { InvalidArgumentError } from "./errors.js";
import {
@@ -322,9 +322,12 @@ export class UserClient extends AetherClient {
workspace,
assignmentMode,
targetAgentId: opts.targetAgentId ?? "",
+ targetOfflinePolicy: opts.targetOfflinePolicy ?? TargetOfflinePolicy.Unspecified,
targetImplementation: opts.targetImplementation ?? "",
launchParamOverrides: opts.launchParamOverrides ?? {},
metadata: opts.metadata ?? {},
+ parentTaskId: opts.parentTaskId ?? "",
+ requiredDownstreamAuthorityHops: opts.requiredDownstreamAuthorityHops ?? 0,
},
});
}
diff --git a/sdk/typescript/src/workflow.ts b/sdk/typescript/src/workflow.ts
index 72c414e..b91fd52 100644
--- a/sdk/typescript/src/workflow.ts
+++ b/sdk/typescript/src/workflow.ts
@@ -23,6 +23,9 @@ import {
} from "./topics.js";
import type { Metric } from "./metrics-builder.js";
+/** Current deterministic WorkflowScheduleAuthorityScope policy shape. */
+export const WORKFLOW_SCHEDULE_AUTHORITY_POLICY_VERSION = 1;
+
// =============================================================================
// Workflow Engine Client Options
// =============================================================================
diff --git a/server/Dockerfile b/server/Dockerfile
index 73ba865..7b743bb 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -4,7 +4,7 @@
# builds when the runner and target differ (e.g. amd64 runner producing
# arm64 binaries).
# Build context must be the repo root: docker build -f server/Dockerfile .
-FROM --platform=$BUILDPLATFORM golang:1.25.12-alpine AS builder
+FROM --platform=$BUILDPLATFORM golang:1.25.14-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
diff --git a/server/Dockerfile.aetherlite b/server/Dockerfile.aetherlite
new file mode 100644
index 0000000..71d96cb
--- /dev/null
+++ b/server/Dockerfile.aetherlite
@@ -0,0 +1,12 @@
+# AetherLite — embedded single-binary deployment.
+#
+# The general Aether image already contains the aetherlite binary. This wrapper
+# selects it as the runtime while retaining the shared certificates, timezone
+# data, non-root user, and health check. Clear the gateway image's default
+# config argument: AetherLite may run from environment/defaults or accept its
+# own config explicitly at `docker run` time.
+ARG BASE_IMAGE=ghcr.io/scitrera/aether:latest
+FROM ${BASE_IMAGE}
+
+ENTRYPOINT ["aetherlite"]
+CMD []
diff --git a/server/Dockerfile.aetherlite-dev b/server/Dockerfile.aetherlite-dev
index 74ac718..bb0b76e 100644
--- a/server/Dockerfile.aetherlite-dev
+++ b/server/Dockerfile.aetherlite-dev
@@ -1,9 +1,9 @@
-# AetherLite — easy-start embedded mode -- preconfigured development version
-# Shares all layers with the base aetherlite image
+# AetherLite — easy-start embedded mode, preconfigured for development.
+# Shares all layers with the base aetherlite image and inherits its entrypoint.
ARG BASE_IMAGE=ghcr.io/scitrera/aetherlite:latest
FROM ${BASE_IMAGE}
# !! DEVELOPMENT ONLY !! Not for production deployment. Hardcodes dev-mode + insecure admin.
ENV AETHER_ALLOW_DEV_MODE=true \
- AETHERLITE_DEV=true \
- AETHERLITE_INSECURE_ADMIN=true
+ AETHER_DEV=true \
+ AETHER_INSECURE_ADMIN=true
diff --git a/server/cmd/aetherlite/main.go b/server/cmd/aetherlite/main.go
index 0412f8d..262ad68 100644
--- a/server/cmd/aetherlite/main.go
+++ b/server/cmd/aetherlite/main.go
@@ -18,6 +18,7 @@ import (
"time"
pb "github.com/scitrera/aether/api/proto"
+ aclcore "github.com/scitrera/aether/server/internal/acl"
"github.com/scitrera/aether/server/internal/admin"
"github.com/scitrera/aether/server/internal/audit"
"github.com/scitrera/aether/server/internal/auth"
@@ -499,11 +500,29 @@ func main() {
dispatcher = orchestration.NewPollingTaskDispatcher(taskStore)
}
+ // Quota defaults come from the `quotas:` config block, falling back to the
+ // same built-in values cmd/gateway uses. These were hardcoded, so the block
+ // was silently ignored in lite mode — and because the per-identity message
+ // rate also feeds the workspace rate limiter, a deployment could not raise
+ // its message throughput at all (the limit stuck at 100/s no matter what
+ // gateway.message_rate_limit or quotas.max_message_rate_per_identity said).
quotaDefaults := quota.DefaultQuotas{
- MaxConnectionsPerWorkspace: 1000,
- MaxMessageRatePerIdentity: 100,
- MaxKVKeysPerNamespace: 10000,
- MaxKVValueSize: 1048576,
+ MaxConnectionsPerWorkspace: cfg.Quotas.MaxConnectionsPerWorkspace,
+ MaxMessageRatePerIdentity: cfg.Quotas.MaxMessageRatePerIdentity,
+ MaxKVKeysPerNamespace: cfg.Quotas.MaxKVKeysPerNamespace,
+ MaxKVValueSize: cfg.Quotas.MaxKVValueSize,
+ }
+ if quotaDefaults.MaxConnectionsPerWorkspace <= 0 {
+ quotaDefaults.MaxConnectionsPerWorkspace = 1000
+ }
+ if quotaDefaults.MaxMessageRatePerIdentity <= 0 {
+ quotaDefaults.MaxMessageRatePerIdentity = 100
+ }
+ if quotaDefaults.MaxKVKeysPerNamespace <= 0 {
+ quotaDefaults.MaxKVKeysPerNamespace = 10000
+ }
+ if quotaDefaults.MaxKVValueSize <= 0 {
+ quotaDefaults.MaxKVValueSize = 1048576 // 1MB
}
quotaManager := quota.NewMemoryQuotaManager(quotaDefaults)
@@ -563,6 +582,18 @@ func main() {
gatewayOpts = append(gatewayOpts, gateway.WithGatewayTenantID(tenantID))
}
+ // Per-client message rate limiting. Without this the gateway keeps its
+ // built-in default (100/s, burst 200) and gateway.message_rate_limit is
+ // silently ignored in lite mode — the key applies only to the workspace
+ // limiter below, so raising it appears to do nothing. Mirrors cmd/gateway.
+ if cfg.Gateway.MessageRateLimit > 0 {
+ burst := cfg.Gateway.MessageRateBurst
+ if burst <= 0 {
+ burst = int(cfg.Gateway.MessageRateLimit * 2)
+ }
+ gatewayOpts = append(gatewayOpts, gateway.WithMessageRateLimit(cfg.Gateway.MessageRateLimit, burst))
+ }
+
// Workspace rate limiter.
workspaceRL := quota.NewWorkspaceRateLimiter(cfg.Gateway.MessageRateLimit)
gatewayOpts = append(gatewayOpts, gateway.WithWorkspaceRateLimiter(workspaceRL))
@@ -613,6 +644,14 @@ func main() {
if err != nil {
logging.Logger.Fatal().Err(err).Msg("failed to construct native sqlite acl store")
}
+ if *devMode {
+ for _, principalType := range []string{aclcore.PrincipalTypeUser, aclcore.PrincipalTypeAgent} {
+ category := aclcore.RuleCategory(principalType, aclcore.ResourceTypeWorkflowSchedule)
+ if err := sharedACLService.SetFallbackPolicy(ctx, category, aclcore.AccessManage, aclcore.SystemPrincipal); err != nil {
+ logging.Logger.Fatal().Err(err).Str("category", category).Msg("failed to enable development workflow schedule access")
+ }
+ }
+ }
// Gateway-facing ACL store. In cluster mode we wrap sharedACLService in a
// JetStream-backed decorator so the 6 authority-request lifecycle methods
@@ -647,9 +686,9 @@ func main() {
// Cleanup service.
cleanupConfig := &cleanup.Config{
- TaskPurgeInterval: cfg.Cleanup.GetTaskPurgeInterval(),
- CompletedTaskRetention: cfg.Cleanup.GetCompletedTaskRetention(),
- FailedTaskRetention: cfg.Cleanup.GetFailedTaskRetention(),
+ TaskPurgeInterval: cfg.Cleanup.GetTaskPurgeInterval(),
+ CompletedTaskRetention: cfg.Cleanup.GetCompletedTaskRetention(),
+ FailedTaskRetention: cfg.Cleanup.GetFailedTaskRetention(),
CancelledTaskRetention: cfg.Cleanup.GetCancelledTaskRetention(),
ReconciliationInterval: cfg.Cleanup.GetReconciliationInterval(),
InteractiveTaskTTL: cfg.Cleanup.GetInteractiveTaskTTL(),
diff --git a/server/cmd/gateway/main.go b/server/cmd/gateway/main.go
index 5cd3bfa..e511514 100644
--- a/server/cmd/gateway/main.go
+++ b/server/cmd/gateway/main.go
@@ -16,6 +16,7 @@ import (
"github.com/redis/go-redis/v9"
pb "github.com/scitrera/aether/api/proto"
+ aclcore "github.com/scitrera/aether/server/internal/acl"
"github.com/scitrera/aether/server/internal/admin"
"github.com/scitrera/aether/server/internal/audit"
"github.com/scitrera/aether/server/internal/auth"
@@ -571,9 +572,9 @@ func main() {
// Cleanup service (added last so orchestration is available when it runs)
cleanupConfig := &cleanup.Config{
- TaskPurgeInterval: cfg.Cleanup.GetTaskPurgeInterval(),
- CompletedTaskRetention: cfg.Cleanup.GetCompletedTaskRetention(),
- FailedTaskRetention: cfg.Cleanup.GetFailedTaskRetention(),
+ TaskPurgeInterval: cfg.Cleanup.GetTaskPurgeInterval(),
+ CompletedTaskRetention: cfg.Cleanup.GetCompletedTaskRetention(),
+ FailedTaskRetention: cfg.Cleanup.GetFailedTaskRetention(),
CancelledTaskRetention: cfg.Cleanup.GetCancelledTaskRetention(),
ReconciliationInterval: cfg.Cleanup.GetReconciliationInterval(),
InteractiveTaskTTL: cfg.Cleanup.GetInteractiveTaskTTL(),
@@ -630,6 +631,14 @@ func main() {
var sharedACLService *aclpg.Store
if db != nil {
sharedACLService = aclpg.NewWithSharedAudit(db, auditLogger, db, cfg.Gateway.GatewayID)
+ if *devMode {
+ for _, principalType := range []string{aclcore.PrincipalTypeUser, aclcore.PrincipalTypeAgent} {
+ category := aclcore.RuleCategory(principalType, aclcore.ResourceTypeWorkflowSchedule)
+ if err := sharedACLService.SetFallbackPolicy(context.Background(), category, aclcore.AccessManage, aclcore.SystemPrincipal); err != nil {
+ logging.Logger.Fatal().Err(err).Str("category", category).Msg("failed to enable development workflow schedule access")
+ }
+ }
+ }
gatewayOpts = append(gatewayOpts, gateway.WithACLService(sharedACLService))
logging.Logger.Debug().Msg("shared ACL service initialized")
}
diff --git a/server/docs/workflow-engine.md b/server/docs/workflow-engine.md
index db1eed9..77d2d6c 100644
--- a/server/docs/workflow-engine.md
+++ b/server/docs/workflow-engine.md
@@ -391,6 +391,26 @@ A leader-gated ticker (`scheduler.go`) polls on `GetSchedulerPollInterval()`:
Both run only on the elected leader (`IsLeader()`), so deadline sweeps fire once
cluster-wide.
+Recurring schedules have three explicit missed-fire policies:
+
+- `skip` (the API default when omitted) fires an ordinary single due occurrence,
+ but discards a backlog with multiple due occurrences and advances to the next
+ future time;
+- `fire_once` coalesces all due occurrences into one dispatch;
+- `fire_all` dispatches every due occurrence, advancing the durable cursor after
+ each success in batches of at most 100. A backlog larger than one batch stays
+ due for the next poll rather than being discarded.
+
+Create/upsert rejects any other value. The runtime conservatively treats an
+unrecognized value already present in storage as `fire_once`.
+
+Scheduled `create_task` actions receive `aether.schedule.id`,
+`aether.schedule.scheduled_for`, `aether.schedule.dispatched_at`, and
+`aether.schedule.miss_policy` metadata. Unless the declaration supplies its own
+key, the scheduler also derives an idempotency key from the workspace, schedule
+ID, and occurrence timestamp. This makes dispatch retry safe if task creation
+succeeds but advancing the schedule cursor fails.
+
---
## 5. DAG engine & state machines (existing)
diff --git a/server/go.mod b/server/go.mod
index 5257477..1c1f612 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -1,6 +1,6 @@
module github.com/scitrera/aether/server
-go 1.25.12
+go 1.25.14
require (
github.com/MicahParks/keyfunc/v3 v3.8.0
@@ -30,8 +30,8 @@ require (
github.com/redis/go-redis/v9 v9.17.2
github.com/robfig/cron/v3 v3.0.1
github.com/rs/zerolog v1.34.0
- github.com/scitrera/aether/api v0.2.2
- github.com/scitrera/aether/sdk/go v0.2.2
+ github.com/scitrera/aether/api v0.2.3
+ github.com/scitrera/aether/sdk/go v0.2.3
github.com/scitrera/go-backpressure v0.1.1
github.com/vmihailenco/msgpack/v5 v5.4.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
@@ -49,7 +49,7 @@ require (
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0
golang.org/x/time v0.15.0
- google.golang.org/grpc v1.81.1
+ google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.48.1
@@ -115,8 +115,8 @@ require (
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
- golang.org/x/net v0.54.0 // indirect
- golang.org/x/sys v0.44.0 // indirect
+ golang.org/x/net v0.55.0 // indirect
+ golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
diff --git a/server/go.sum b/server/go.sum
index cad5869..b05942c 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -242,8 +242,8 @@ golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
-golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
-golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
+golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
+golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -252,8 +252,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
-golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
+golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
@@ -266,8 +266,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:Q9HWtNeE7tM9npdIsEvqXj1QJIvVoeAV3rtXtS715Cw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
-google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
-google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
+google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
+google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/server/internal/acl/authority_context.go b/server/internal/acl/authority_context.go
index bb9e941..aee5c77 100644
--- a/server/internal/acl/authority_context.go
+++ b/server/internal/acl/authority_context.go
@@ -34,6 +34,11 @@ type GrantAudienceContext struct {
// (pending/running/assigned). Used when ValidWhileAudienceActive=true and
// AudienceType=task. If nil, only the AssociatedTaskID match is checked.
TaskActive func(taskID string) bool
+
+ // WorkflowScheduleID is supplied only on authenticated WorkflowEngine
+ // operations that create tasks or manage the private grant for one exact
+ // schedule.
+ WorkflowScheduleID string
}
// ResolvedAuthority is the validated authority envelope for a single request.
@@ -166,6 +171,11 @@ func validateGrantAudience(grant *AuthorityGrant, actor models.Identity, audienc
if actor.Type != models.PrincipalService || actor.CanonicalPrincipalID() != grant.AudienceID {
return ErrAuthorityGrantAudienceMismatch
}
+ case AuthorityAudienceWorkflowSchedule:
+ if actor.Type != models.PrincipalWorkflowEngine || audience.WorkflowScheduleID == "" ||
+ grant.AudienceID != audience.WorkflowScheduleID {
+ return ErrAuthorityGrantAudienceMismatch
+ }
default:
return ErrAuthorityGrantAudienceMismatch
}
diff --git a/server/internal/acl/authority_context_test.go b/server/internal/acl/authority_context_test.go
index 7805f6b..40f219a 100644
--- a/server/internal/acl/authority_context_test.go
+++ b/server/internal/acl/authority_context_test.go
@@ -50,11 +50,32 @@ func TestValidateGrantAudience(t *testing.T) {
Actor: actor,
},
},
+ {
+ name: "workflow schedule audience matches exact engine context",
+ grant: AuthorityGrant{AudienceType: AuthorityAudienceWorkflowSchedule, AudienceID: "sched-a"},
+ audience: GrantAudienceContext{
+ Actor: models.Identity{Type: models.PrincipalWorkflowEngine}, WorkflowScheduleID: "sched-a",
+ },
+ },
+ {
+ name: "workflow schedule audience rejects ordinary actor",
+ grant: AuthorityGrant{AudienceType: AuthorityAudienceWorkflowSchedule, AudienceID: "sched-a"},
+ audience: GrantAudienceContext{Actor: actor, WorkflowScheduleID: "sched-a"},
+ wantErr: ErrAuthorityGrantAudienceMismatch,
+ },
+ {
+ name: "workflow schedule audience rejects different schedule",
+ grant: AuthorityGrant{AudienceType: AuthorityAudienceWorkflowSchedule, AudienceID: "sched-a"},
+ audience: GrantAudienceContext{
+ Actor: models.Identity{Type: models.PrincipalWorkflowEngine}, WorkflowScheduleID: "sched-b",
+ },
+ wantErr: ErrAuthorityGrantAudienceMismatch,
+ },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- err := validateGrantAudience(&tt.grant, actor, tt.audience)
+ err := validateGrantAudience(&tt.grant, tt.audience.Actor, tt.audience)
if err != tt.wantErr {
t.Fatalf("validateGrantAudience() error = %v, want %v", err, tt.wantErr)
}
diff --git a/server/internal/acl/authority_grants.go b/server/internal/acl/authority_grants.go
index c4b2834..2b32205 100644
--- a/server/internal/acl/authority_grants.go
+++ b/server/internal/acl/authority_grants.go
@@ -13,10 +13,11 @@ import (
)
const (
- AuthorityAudienceSession = "session"
- AuthorityAudienceTask = "task"
- AuthorityAudienceAgent = "agent"
- AuthorityAudienceService = "service"
+ AuthorityAudienceSession = "session"
+ AuthorityAudienceTask = "task"
+ AuthorityAudienceAgent = "agent"
+ AuthorityAudienceService = "service"
+ AuthorityAudienceWorkflowSchedule = "workflow_schedule"
)
// AuthorityGrant is the persisted delegated-authorization capability used by
@@ -83,6 +84,33 @@ type CreateAuthorityGrantRequest struct {
Metadata map[string]interface{}
}
+// ValidateAuthorityGrantScopeAttenuation verifies the non-lifetime ceilings of
+// a proposed child/root schedule grant against a resolved source grant. Durable
+// schedule authority deliberately has its own bounded lifetime, so callers use
+// this helper before creating a new root instead of bypassing workspace,
+// resource, operation, access, or hop attenuation.
+func ValidateAuthorityGrantScopeAttenuation(parent *AuthorityGrant, req CreateAuthorityGrantRequest) error {
+ if parent == nil {
+ return fmt.Errorf("authority grant parent is required")
+ }
+ if err := parent.ValidateActiveAt(time.Now()); err != nil {
+ return err
+ }
+ if !parent.CanDelegate() {
+ return ErrAuthorityGrantDelegationDenied
+ }
+ if req.MaxAccessLevel > parent.MaxAccessLevel ||
+ !stringSliceSubset(req.WorkspaceScope, parent.WorkspaceScope) ||
+ !stringSliceSubset(req.OperationScope, parent.OperationScope) ||
+ !resourceScopeSubset(req.ResourceScope, parent.ResourceScope) {
+ return ErrAuthorityGrantScopeEscalation
+ }
+ if req.RemainingHops > parent.RemainingHops-1 {
+ return ErrAuthorityGrantDelegationDenied
+ }
+ return nil
+}
+
type AuthorityGrantFilter struct {
RootGrantID string
SubjectType string
@@ -750,7 +778,8 @@ func authorityPrincipalRef(identity models.Identity) (string, string, error) {
func isValidAuthorityAudienceType(audienceType string) bool {
switch audienceType {
- case AuthorityAudienceSession, AuthorityAudienceTask, AuthorityAudienceAgent, AuthorityAudienceService:
+ case AuthorityAudienceSession, AuthorityAudienceTask, AuthorityAudienceAgent, AuthorityAudienceService,
+ AuthorityAudienceWorkflowSchedule:
return true
default:
return false
diff --git a/server/internal/acl/types.go b/server/internal/acl/types.go
index b7f7c4e..7e906ac 100644
--- a/server/internal/acl/types.go
+++ b/server/internal/acl/types.go
@@ -34,11 +34,15 @@ const (
ResourceTypeAdmin = models.ResourceTypeAdmin
// ResourceTypeCapability gates runtime capabilities (e.g.
// "capability/metric_credit", "capability/resolve_authority").
- ResourceTypeCapability = models.ResourceTypeCapability
- ResourceTypeTask = models.ResourceTypeTask
- ResourceTypeKVScope = models.ResourceTypeKVScope
- ResourceTypeKVKey = models.ResourceTypeKVKey
- ResourceTypeServiceImpl = models.ResourceTypeServiceImpl
+ ResourceTypeCapability = models.ResourceTypeCapability
+ ResourceTypeTask = models.ResourceTypeTask
+ ResourceTypeKVScope = models.ResourceTypeKVScope
+ ResourceTypeKVKey = models.ResourceTypeKVKey
+ ResourceTypeServiceImpl = models.ResourceTypeServiceImpl
+ ResourceTypeWorkspaceExecutionView = models.ResourceTypeWorkspaceExecutionView
+ ResourceTypeToolCatalogProvider = models.ResourceTypeToolCatalogProvider
+ ResourceTypeToolCatalogEntry = models.ResourceTypeToolCatalogEntry
+ ResourceTypeWorkflowSchedule = models.ResourceTypeWorkflowSchedule
)
// Principal type strings for ACL database operations. These are the lowercase
@@ -95,6 +99,7 @@ const (
PermissionAdminAgents = "admin/agents" // admin gate — agent management
PermissionExchangeAuthorityGrants = "capability/exchange_authority_grants" // capability gate — trusted service/user-session grant exchange
PermissionAuthorityIntermediary = "capability/authority_intermediary" // capability gate — trusted intermediaries minting re-rooted task grants on hop exhaustion
+ PermissionScheduleAuthority = "capability/schedule_authority" // capability gate — trusted OBO intermediaries mint bounded durable workflow-schedule authority
PermissionMetricCredit = "capability/metric_credit" // capability gate — publish negative metric deltas (corrections / credits)
PermissionEventBroadcast = "capability/event_broadcast" // capability gate — publish event::{ws} for a workspace other than the sender's home workspace
PermissionMetricBroadcast = "capability/metric_broadcast" // capability gate — publish metric::{ws} for a workspace other than the sender's home workspace
diff --git a/server/internal/audit/types.go b/server/internal/audit/types.go
index 17891ac..89a9af2 100644
--- a/server/internal/audit/types.go
+++ b/server/internal/audit/types.go
@@ -104,6 +104,9 @@ const (
OpAuthorityGrantRenew = "authority_grant_renew"
OpAuthorityGrantRevoke = "authority_grant_revoke"
OpAuthorityIntermediary = "authority_intermediary_reroot" // a service principal exercised capability/authority_intermediary to mint a task grant that re-roots from the original subject (preserving principal chain) instead of failing at hop exhaustion
+ OpScheduleAuthority = "schedule_authority_mint"
+ OpWorkflowScheduleRead = "workflow_schedule_read"
+ OpWorkflowScheduleManage = "workflow_schedule_manage"
// Authority-request lifecycle operations (Phase 2 Stage C). Used both as
// the audit Operation column value and as the operation argument to
diff --git a/server/internal/gateway/access_check_handler.go b/server/internal/gateway/access_check_handler.go
new file mode 100644
index 0000000..cbda9d6
--- /dev/null
+++ b/server/internal/gateway/access_check_handler.go
@@ -0,0 +1,253 @@
+package gateway
+
+import (
+ "context"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/internal/audit"
+ "github.com/scitrera/aether/server/internal/logging"
+ "github.com/scitrera/aether/server/pkg/models"
+)
+
+const (
+ maxBatchAccessChecks = 100
+ defaultReceiptTTL = 30 * time.Second
+ viewBindReceiptTTL = 2 * time.Minute
+ maxReceiptTTL = 5 * time.Minute
+)
+
+type runtimeAccessChecker interface {
+ CheckAccess(context.Context, models.Identity, string, string, string, string, uuid.UUID, int) (*acl.ACLDecision, error)
+ CheckAccessWithAuthority(context.Context, models.Identity, *acl.ResolvedAuthority, string, string, string, string, uuid.UUID, int) (*acl.ACLDecision, error)
+}
+
+func validateCorrelationID(value string) error {
+ if value == "" || len(value) > 128 || strings.TrimSpace(value) != value {
+ return fmt.Errorf("correlation_id must be 1-128 characters without surrounding whitespace")
+ }
+ return nil
+}
+
+func validateRequestID(value string) error {
+ if value == "" || len(value) > 128 || strings.TrimSpace(value) != value {
+ return fmt.Errorf("request_id must be 1-128 characters without surrounding whitespace")
+ }
+ return nil
+}
+
+func validateResourceAccessRequest(req *pb.ResourceAccessRequest) error {
+ if req == nil {
+ return fmt.Errorf("access request is required")
+ }
+ fields := []struct {
+ name string
+ value string
+ max int
+ req bool
+ }{
+ {"resource_type", req.GetResourceType(), 128, true},
+ {"resource_id", req.GetResourceId(), 512, true},
+ {"operation", req.GetOperation(), 128, true},
+ {"workspace", req.GetWorkspace(), 256, false},
+ }
+ for _, field := range fields {
+ if field.req && field.value == "" {
+ return fmt.Errorf("%s is required", field.name)
+ }
+ if len(field.value) > field.max {
+ return fmt.Errorf("%s exceeds %d characters", field.name, field.max)
+ }
+ if strings.TrimSpace(field.value) != field.value {
+ return fmt.Errorf("%s must not contain surrounding whitespace", field.name)
+ }
+ }
+ if err := validateCorrelationID(req.GetCorrelationId()); err != nil {
+ return err
+ }
+ if req.GetRequiredAccessLevel() <= 0 || acl.ValidateAccessLevel(int(req.GetRequiredAccessLevel())) != nil {
+ return fmt.Errorf("required_access_level must be one of 10, 20, 30, 40, or 50")
+ }
+ return nil
+}
+
+func receiptTTL(req *pb.ResourceAccessRequest) time.Duration {
+ if req.GetResourceType() == models.ResourceTypeWorkspaceExecutionView && req.GetOperation() == "bind" {
+ return viewBindReceiptTTL
+ }
+ return defaultReceiptTTL
+}
+
+func evaluateResourceAccess(
+ ctx context.Context,
+ checker runtimeAccessChecker,
+ actor models.Identity,
+ authority *acl.ResolvedAuthority,
+ sessionID uuid.UUID,
+ req *pb.ResourceAccessRequest,
+ deliveryTarget string,
+ now time.Time,
+) (*pb.AccessDecisionReceipt, error) {
+ if err := validateResourceAccessRequest(req); err != nil {
+ return nil, err
+ }
+ if checker == nil {
+ return nil, fmt.Errorf("ACL service is unavailable")
+ }
+
+ var (
+ decision *acl.ACLDecision
+ err error
+ )
+ if authority == nil {
+ decision, err = checker.CheckAccess(ctx, actor, req.GetResourceType(), req.GetResourceId(), req.GetOperation(), req.GetWorkspace(), sessionID, int(req.GetRequiredAccessLevel()))
+ } else {
+ // Exact logical-resource checks never use the message route's
+ // actor-first fallback. OBO means subject ACL intersected with grant.
+ decision, err = checker.CheckAccessWithAuthority(ctx, actor, authority, req.GetResourceType(), req.GetResourceId(), req.GetOperation(), req.GetWorkspace(), sessionID, int(req.GetRequiredAccessLevel()))
+ }
+ if err != nil {
+ return nil, err
+ }
+ if decision == nil {
+ return nil, fmt.Errorf("ACL service returned no decision")
+ }
+
+ mode := audit.AuthorityModeDirect
+ receipt := &pb.AccessDecisionReceipt{
+ DecisionId: uuid.NewString(),
+ Request: req,
+ Allowed: decision.Allowed,
+ Decision: decision.Decision,
+ EffectiveAccessLevel: int32(decision.EffectiveAccessLevel),
+ Actor: actorPrincipalRef(actor),
+ EvaluatedAtMs: now.UnixMilli(),
+ DeliveryTarget: deliveryTarget,
+ }
+ if receipt.Decision == "" {
+ if receipt.Allowed {
+ receipt.Decision = acl.DecisionAllow
+ } else {
+ receipt.Decision = acl.DecisionDeny
+ }
+ }
+ if !receipt.Allowed {
+ receipt.DenialCode = "access_denied"
+ }
+
+ expiresAt := now.Add(receiptTTL(req))
+ if expiresAt.After(now.Add(maxReceiptTTL)) {
+ expiresAt = now.Add(maxReceiptTTL)
+ }
+ if authority != nil && authority.Grant != nil {
+ mode = audit.AuthorityModeOnBehalfOf
+ grant := authority.Grant
+ receipt.Subject = actorPrincipalRef(authority.Subject)
+ receipt.GrantId = grant.GrantID
+ receipt.RootGrantId = grant.RootGrantID
+ if grant.RootSubjectType != "" && grant.RootSubjectID != "" {
+ receipt.RootSubject = aclPrincipalRefToProto(grant.RootSubjectType, grant.RootSubjectID)
+ }
+ if !grant.ExpiresAt.IsZero() && grant.ExpiresAt.Before(expiresAt) {
+ expiresAt = grant.ExpiresAt
+ }
+ }
+ receipt.AuthorityMode = mode
+ receipt.ExpiresAtMs = expiresAt.UnixMilli()
+ return receipt, nil
+}
+
+func (s *GatewayServer) resolveAccessCheckAuthority(ctx context.Context, client *ClientSession, actor models.Identity, authz *pb.AuthorizationContext) (*acl.ResolvedAuthority, error) {
+ resolved, err := s.resolveAuthorizationContext(ctx, client, actor, authz)
+ if err != nil || resolved != nil || client == nil || client.AssociatedTaskID == "" {
+ return resolved, err
+ }
+ if actor.Type != models.PrincipalAgent && actor.Type != models.PrincipalTask {
+ return nil, nil
+ }
+ return s.loadCallerMessageAuthority(ctx, client, actor)
+}
+
+func currentClientIdentity(client *ClientSession) models.Identity {
+ client.identityMu.RLock()
+ defer client.identityMu.RUnlock()
+ return client.Identity
+}
+
+func (s *GatewayServer) handleAccessCheck(ctx context.Context, client *ClientSession, op *pb.AccessCheckOperation) {
+ response := &pb.AccessCheckResponse{}
+ if op != nil {
+ response.RequestId = op.GetRequestId()
+ }
+ if op == nil {
+ response.Error = "access check operation is required"
+ } else if err := validateRequestID(op.GetRequestId()); err != nil {
+ response.Error = err.Error()
+ } else if err := validateResourceAccessRequest(op.GetAccess()); err != nil {
+ response.Error = err.Error()
+ } else {
+ actor := currentClientIdentity(client)
+ authority, err := s.resolveAccessCheckAuthority(ctx, client, actor, op.GetAuthorization())
+ if err != nil {
+ response.Error = "invalid authorization context"
+ } else {
+ response.Decision, err = evaluateResourceAccess(ctx, s.acl, actor, authority, client.SessionUUID, op.GetAccess(), "", time.Now())
+ if err != nil {
+ logging.Logger.Error().Err(err).Str("actor", actor.String()).Str("request_id", op.GetRequestId()).Msg("runtime access evaluation failed")
+ response.Error = "access evaluation unavailable"
+ } else {
+ response.Success = true
+ }
+ }
+ }
+ _ = client.SafeSend(&pb.DownstreamMessage{Payload: &pb.DownstreamMessage_AccessCheckResponse{AccessCheckResponse: response}})
+}
+
+func (s *GatewayServer) handleBatchAccessCheck(ctx context.Context, client *ClientSession, op *pb.BatchAccessCheckOperation) {
+ response := &pb.BatchAccessCheckResponse{}
+ if op != nil {
+ response.RequestId = op.GetRequestId()
+ }
+ if op == nil {
+ response.Error = "batch access check operation is required"
+ } else if err := validateRequestID(op.GetRequestId()); err != nil {
+ response.Error = err.Error()
+ } else if len(op.GetAccess()) == 0 || len(op.GetAccess()) > maxBatchAccessChecks {
+ response.Error = fmt.Sprintf("batch must contain 1-%d access requests", maxBatchAccessChecks)
+ } else {
+ // Validate the complete batch before evaluating any item. This avoids
+ // partial authorization/audit side effects for malformed batches.
+ for i, req := range op.GetAccess() {
+ if err := validateResourceAccessRequest(req); err != nil {
+ response.Error = fmt.Sprintf("access[%d]: %s", i, err)
+ break
+ }
+ }
+ if response.Error == "" {
+ actor := currentClientIdentity(client)
+ authority, err := s.resolveAccessCheckAuthority(ctx, client, actor, op.GetAuthorization())
+ if err != nil {
+ response.Error = "invalid authorization context"
+ } else {
+ now := time.Now()
+ response.Decisions = make([]*pb.AccessDecisionReceipt, 0, len(op.GetAccess()))
+ for _, req := range op.GetAccess() {
+ decision, evalErr := evaluateResourceAccess(ctx, s.acl, actor, authority, client.SessionUUID, req, "", now)
+ if evalErr != nil {
+ logging.Logger.Error().Err(evalErr).Str("actor", actor.String()).Str("request_id", op.GetRequestId()).Msg("batch runtime access evaluation failed")
+ response.Error = "access evaluation unavailable"
+ response.Decisions = nil
+ break
+ }
+ response.Decisions = append(response.Decisions, decision)
+ }
+ response.Success = response.Error == ""
+ }
+ }
+ }
+ _ = client.SafeSend(&pb.DownstreamMessage{Payload: &pb.DownstreamMessage_BatchAccessCheckResponse{BatchAccessCheckResponse: response}})
+}
diff --git a/server/internal/gateway/access_check_handler_test.go b/server/internal/gateway/access_check_handler_test.go
new file mode 100644
index 0000000..2bb4f1f
--- /dev/null
+++ b/server/internal/gateway/access_check_handler_test.go
@@ -0,0 +1,137 @@
+package gateway
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/pkg/models"
+)
+
+type fakeRuntimeAccessChecker struct {
+ directCalls int
+ authorityCalls int
+ decision *acl.ACLDecision
+ err error
+}
+
+func (f *fakeRuntimeAccessChecker) CheckAccess(context.Context, models.Identity, string, string, string, string, uuid.UUID, int) (*acl.ACLDecision, error) {
+ f.directCalls++
+ return f.decision, f.err
+}
+
+func (f *fakeRuntimeAccessChecker) CheckAccessWithAuthority(context.Context, models.Identity, *acl.ResolvedAuthority, string, string, string, string, uuid.UUID, int) (*acl.ACLDecision, error) {
+ f.authorityCalls++
+ return f.decision, f.err
+}
+
+func validAccessRequest() *pb.ResourceAccessRequest {
+ return &pb.ResourceAccessRequest{
+ ResourceType: models.ResourceTypeToolCatalogEntry,
+ ResourceId: "provider-1/tool-1",
+ Operation: "invoke",
+ Workspace: "workspace-1",
+ RequiredAccessLevel: int32(acl.AccessReadWrite),
+ CorrelationId: "call-1",
+ }
+}
+
+func TestEvaluateResourceAccessDirect(t *testing.T) {
+ now := time.Date(2026, 8, 11, 12, 0, 0, 0, time.UTC)
+ checker := &fakeRuntimeAccessChecker{decision: &acl.ACLDecision{
+ Allowed: true,
+ Decision: acl.DecisionAllow,
+ EffectiveAccessLevel: acl.AccessManage,
+ }}
+ actor := models.Identity{Type: models.PrincipalAgent, Workspace: "workspace-1", Implementation: "harness", Specifier: "one"}
+
+ receipt, err := evaluateResourceAccess(context.Background(), checker, actor, nil, uuid.New(), validAccessRequest(), "sv::tools::one", now)
+ if err != nil {
+ t.Fatalf("evaluateResourceAccess() error = %v", err)
+ }
+ if !receipt.GetAllowed() || receipt.GetAuthorityMode() != "direct" {
+ t.Fatalf("unexpected receipt: %+v", receipt)
+ }
+ if receipt.GetDeliveryTarget() != "sv::tools::one" {
+ t.Fatalf("delivery_target = %q", receipt.GetDeliveryTarget())
+ }
+ if got, want := receipt.GetExpiresAtMs(), now.Add(defaultReceiptTTL).UnixMilli(); got != want {
+ t.Fatalf("expires_at_ms = %d, want %d", got, want)
+ }
+ if checker.directCalls != 1 || checker.authorityCalls != 0 {
+ t.Fatalf("checker calls direct=%d authority=%d", checker.directCalls, checker.authorityCalls)
+ }
+}
+
+func TestEvaluateResourceAccessOBOClampsExpiryAndDoesNotFallback(t *testing.T) {
+ now := time.Date(2026, 8, 11, 12, 0, 0, 0, time.UTC)
+ grantExpiry := now.Add(7 * time.Second)
+ checker := &fakeRuntimeAccessChecker{decision: &acl.ACLDecision{
+ Allowed: false,
+ Decision: acl.DecisionDeny,
+ EffectiveAccessLevel: acl.AccessRead,
+ }}
+ actor := models.Identity{Type: models.PrincipalService, Implementation: "harness", Specifier: "one"}
+ subject := models.Identity{Type: models.PrincipalUser, ID: "user-1", Specifier: "window-1"}
+ authority := &acl.ResolvedAuthority{
+ Actor: actor,
+ Subject: subject,
+ Grant: &acl.AuthorityGrant{
+ GrantID: "grant-1",
+ RootGrantID: "root-1",
+ RootSubjectType: "user",
+ RootSubjectID: "user-1",
+ ExpiresAt: grantExpiry,
+ },
+ }
+
+ receipt, err := evaluateResourceAccess(context.Background(), checker, actor, authority, uuid.New(), validAccessRequest(), "", now)
+ if err != nil {
+ t.Fatalf("evaluateResourceAccess() error = %v", err)
+ }
+ if receipt.GetAllowed() || receipt.GetDenialCode() != "access_denied" {
+ t.Fatalf("unexpected denial receipt: %+v", receipt)
+ }
+ if receipt.GetAuthorityMode() != "on_behalf_of" || receipt.GetGrantId() != "grant-1" || receipt.GetRootGrantId() != "root-1" {
+ t.Fatalf("missing authority lineage: %+v", receipt)
+ }
+ if receipt.GetExpiresAtMs() != grantExpiry.UnixMilli() {
+ t.Fatalf("expires_at_ms = %d, want grant expiry %d", receipt.GetExpiresAtMs(), grantExpiry.UnixMilli())
+ }
+ if checker.directCalls != 0 || checker.authorityCalls != 1 {
+ t.Fatalf("OBO check fell back: direct=%d authority=%d", checker.directCalls, checker.authorityCalls)
+ }
+}
+
+func TestValidateResourceAccessRequestRejectsNonCanonicalAndZeroAccess(t *testing.T) {
+ tests := []struct {
+ name string
+ mutate func(*pb.ResourceAccessRequest)
+ }{
+ {"surrounding whitespace", func(req *pb.ResourceAccessRequest) { req.ResourceId = " tool " }},
+ {"missing correlation", func(req *pb.ResourceAccessRequest) { req.CorrelationId = "" }},
+ {"zero access", func(req *pb.ResourceAccessRequest) { req.RequiredAccessLevel = 0 }},
+ {"unknown access", func(req *pb.ResourceAccessRequest) { req.RequiredAccessLevel = 15 }},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ req := validAccessRequest()
+ tt.mutate(req)
+ if err := validateResourceAccessRequest(req); err == nil {
+ t.Fatal("validateResourceAccessRequest() error = nil")
+ }
+ })
+ }
+}
+
+func TestReceiptTTLAllowsLongerViewBind(t *testing.T) {
+ req := validAccessRequest()
+ req.ResourceType = models.ResourceTypeWorkspaceExecutionView
+ req.Operation = "bind"
+ if got := receiptTTL(req); got != viewBindReceiptTTL {
+ t.Fatalf("receiptTTL() = %s, want %s", got, viewBindReceiptTTL)
+ }
+}
diff --git a/server/internal/gateway/authority.go b/server/internal/gateway/authority.go
index c3d1ed5..f679415 100644
--- a/server/internal/gateway/authority.go
+++ b/server/internal/gateway/authority.go
@@ -15,6 +15,22 @@ import (
)
func (s *GatewayServer) resolveAuthorizationContext(ctx context.Context, client *ClientSession, actor models.Identity, authz *pb.AuthorizationContext) (*acl.ResolvedAuthority, error) {
+ associatedTaskID := ""
+ if client != nil {
+ associatedTaskID = client.AssociatedTaskID
+ }
+ return s.resolveAuthorizationContextForTask(ctx, client, actor, authz, associatedTaskID)
+}
+
+// resolveAuthorizationContextForTask is the request-scoped variant used by
+// explicit nested task creation. A long-lived worker is not globally associated
+// with every task it executes, so the validated parent task supplies the grant
+// audience for this request without mutating the connection session.
+func (s *GatewayServer) resolveAuthorizationContextForTask(ctx context.Context, client *ClientSession, actor models.Identity, authz *pb.AuthorizationContext, associatedTaskID string) (*acl.ResolvedAuthority, error) {
+ return s.resolveAuthorizationContextForAudience(ctx, client, actor, authz, associatedTaskID, "")
+}
+
+func (s *GatewayServer) resolveAuthorizationContextForAudience(ctx context.Context, client *ClientSession, actor models.Identity, authz *pb.AuthorizationContext, associatedTaskID, workflowScheduleID string) (*acl.ResolvedAuthority, error) {
if authz == nil {
return nil, nil
}
@@ -53,7 +69,7 @@ func (s *GatewayServer) resolveAuthorizationContext(ctx context.Context, client
GrantID: authz.GetGrantId(),
}, acl.GrantAudienceContext{
SessionID: client.SessionUUID,
- AssociatedTaskID: client.AssociatedTaskID,
+ AssociatedTaskID: associatedTaskID,
Actor: actor,
SessionActive: func(sessionID uuid.UUID) bool {
// SessionRegistry.IsActive keys on the identity string (e.g.
@@ -76,6 +92,7 @@ func (s *GatewayServer) resolveAuthorizationContext(ctx context.Context, client
}
return t.Status == tasks.TaskStatusPending || t.Status == tasks.TaskStatusAssigned || t.Status == tasks.TaskStatusRunning
},
+ WorkflowScheduleID: workflowScheduleID,
})
}
diff --git a/server/internal/gateway/authority_continuation.go b/server/internal/gateway/authority_continuation.go
new file mode 100644
index 0000000..c8b4fe2
--- /dev/null
+++ b/server/internal/gateway/authority_continuation.go
@@ -0,0 +1,386 @@
+package gateway
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/internal/audit"
+ "github.com/scitrera/aether/server/pkg/models"
+)
+
+const (
+ messageAuthorityContinuationTTL = 5 * time.Minute
+ continuationMetadataKindKey = "authority_continuation"
+ continuationMetadataTargetKey = "delivery_target"
+ continuationMetadataBindingKey = "binding_id"
+ continuationMetadataModeKey = "scope_mode"
+ maxContinuationBindingIDLength = 256
+)
+
+type messageAuthorityContinuationConfig struct {
+ target models.Identity
+ audienceType string
+ audienceID string
+ bindingID string
+ workspaceScope []string
+ resourceScope map[string][]string
+ operationScope []string
+ maxAccessLevel int
+ reusable bool
+}
+
+// deriveMessageAuthorityContinuation creates a short-lived leaf grant for the
+// concrete service or agent that will receive a message. Reusable service
+// continuations may inherit the parent ceiling. Agent continuations are always
+// explicitly attenuated and minted per checked invocation.
+func (s *GatewayServer) deriveMessageAuthorityContinuation(
+ ctx context.Context,
+ authority *acl.ResolvedAuthority,
+ deliveryTarget string,
+ request *pb.AuthorityContinuationRequest,
+ accessReceipt *pb.AccessDecisionReceipt,
+ sessionID uuid.UUID,
+) (*pb.ForwardedAuthorization, error) {
+ if s.acl == nil {
+ return nil, fmt.Errorf("authority continuation requires ACL service")
+ }
+ if authority == nil || authority.Grant == nil {
+ return nil, fmt.Errorf("authority continuation requires resolved on-behalf-of authority")
+ }
+ if !authority.Grant.CanDelegate() {
+ return nil, acl.ErrAuthorityGrantDelegationDenied
+ }
+
+ config, err := resolveMessageAuthorityContinuation(authority.Grant, deliveryTarget, request, accessReceipt)
+ if err != nil {
+ return nil, err
+ }
+ now := time.Now().UTC()
+ expiresAt := now.Add(messageAuthorityContinuationTTL)
+ if authority.Grant.ExpiresAt.Before(expiresAt) {
+ expiresAt = authority.Grant.ExpiresAt
+ }
+ if !expiresAt.After(now) {
+ return nil, acl.ErrAuthorityGrantExpired
+ }
+
+ var grant *acl.AuthorityGrant
+ reused := false
+ if config.reusable {
+ candidate, findErr := s.acl.FindVisibleDerivedGrant(
+ ctx, authority.Grant.GrantID, config.target, config.audienceType, config.audienceID,
+ )
+ if findErr == nil && messageAuthorityContinuationReusable(candidate, authority.Grant, deliveryTarget, now) {
+ grant, reused = candidate, true
+ }
+ }
+ if !reused {
+ rootSubjectType := authority.Grant.RootSubjectType
+ rootSubjectID := authority.Grant.RootSubjectID
+ if rootSubjectType == "" || rootSubjectID == "" {
+ rootSubjectType = authority.Grant.SubjectType
+ rootSubjectID = authority.Grant.SubjectID
+ }
+ rootSubject, rootErr := identityFromAuthorityPrincipal(rootSubjectType, rootSubjectID)
+ if rootErr != nil {
+ return nil, fmt.Errorf("invalid continuation root subject: %w", rootErr)
+ }
+
+ parentGrantID := authority.Grant.GrantID
+ metadata := map[string]interface{}{
+ continuationMetadataKindKey: true,
+ continuationMetadataTargetKey: deliveryTarget,
+ continuationMetadataModeKey: request.GetScopeMode().String(),
+ "derived_from_grant_id": parentGrantID,
+ }
+ if config.bindingID != "" {
+ metadata[continuationMetadataBindingKey] = config.bindingID
+ metadata["access_decision_id"] = accessReceipt.GetDecisionId()
+ metadata["checked_resource_type"] = accessReceipt.GetRequest().GetResourceType()
+ metadata["checked_resource_id"] = accessReceipt.GetRequest().GetResourceId()
+ }
+ grant, err = s.acl.CreateAuthorityGrant(ctx, acl.CreateAuthorityGrantRequest{
+ Subject: authority.Subject,
+ Delegate: config.target,
+ IssuedBy: authority.Actor,
+ RootSubject: &rootSubject,
+ ParentGrantID: &parentGrantID,
+ MayDelegate: false,
+ RemainingHops: 0,
+ WorkspaceScope: cloneStringSlice(config.workspaceScope),
+ ResourceScope: cloneResourceScope(config.resourceScope),
+ OperationScope: cloneStringSlice(config.operationScope),
+ MaxAccessLevel: config.maxAccessLevel,
+ AudienceType: config.audienceType,
+ AudienceID: config.audienceID,
+ ValidWhileAudienceActive: false,
+ ExpiresAt: expiresAt,
+ RenewableUntil: expiresAt,
+ Reason: "message-authority-continuation",
+ Metadata: metadata,
+ })
+ if err != nil {
+ s.logAuthorityGrantLifecycle(ctx, authority.Actor, sessionID, audit.OpAuthorityGrantDerive, nil, false, err.Error(), map[string]interface{}{
+ "authority_continuation": true,
+ "delivery_target": deliveryTarget,
+ "binding_id": config.bindingID,
+ })
+ return nil, err
+ }
+ }
+
+ operation := audit.OpAuthorityGrantDerive
+ if reused {
+ operation = audit.OpAuthorityGrantGet
+ }
+ s.logAuthorityGrantLifecycle(ctx, authority.Actor, sessionID, operation, grant, true, "", map[string]interface{}{
+ "authority_continuation": true,
+ "delivery_target": deliveryTarget,
+ "binding_id": config.bindingID,
+ "reused_existing": reused,
+ })
+
+ rootGrantID := grant.RootGrantID
+ if rootGrantID == "" {
+ rootGrantID = grant.GrantID
+ }
+ return &pb.ForwardedAuthorization{
+ Authorization: &pb.AuthorizationContext{
+ AuthorityMode: audit.AuthorityModeOnBehalfOf,
+ Subject: identityToProtoPrincipalRef(authority.Subject),
+ GrantId: grant.GrantID,
+ },
+ RootGrantId: rootGrantID,
+ ExpiresAtMs: grant.ExpiresAt.UnixMilli(),
+ DeliveryTarget: deliveryTarget,
+ BindingId: config.bindingID,
+ Scope: authorityContinuationScope(grant),
+ }, nil
+}
+
+func resolveMessageAuthorityContinuation(
+ parent *acl.AuthorityGrant,
+ deliveryTarget string,
+ request *pb.AuthorityContinuationRequest,
+ accessReceipt *pb.AccessDecisionReceipt,
+) (messageAuthorityContinuationConfig, error) {
+ var config messageAuthorityContinuationConfig
+ if parent == nil || request == nil {
+ return config, fmt.Errorf("authority continuation request is required")
+ }
+ target, err := models.ParseIdentity(deliveryTarget)
+ if err != nil || !isConcreteContinuationTarget(target, deliveryTarget) {
+ return config, fmt.Errorf("authority continuation target must be an exact service or agent identity")
+ }
+ config.target = target
+ config.audienceID = target.CanonicalPrincipalID()
+ switch target.Type {
+ case models.PrincipalService:
+ config.audienceType = acl.AuthorityAudienceService
+ case models.PrincipalAgent:
+ config.audienceType = acl.AuthorityAudienceAgent
+ default:
+ return config, fmt.Errorf("authority continuation target must be an exact service or agent identity")
+ }
+
+ switch request.GetScopeMode() {
+ case pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT:
+ if target.Type != models.PrincipalService {
+ return config, fmt.Errorf("agent authority continuation requires explicit attenuation")
+ }
+ if request.GetBindingId() != "" || request.GetScope() != nil {
+ return config, fmt.Errorf("inherited service continuation cannot include a binding or scope")
+ }
+ config.workspaceScope = cloneStringSlice(parent.WorkspaceScope)
+ config.resourceScope = cloneResourceScope(parent.ResourceScope)
+ config.operationScope = cloneStringSlice(parent.OperationScope)
+ config.maxAccessLevel = parent.MaxAccessLevel
+ config.reusable = true
+ return config, nil
+
+ case pb.AuthorityContinuationRequest_SCOPE_MODE_ATTENUATE:
+ bindingID := request.GetBindingId()
+ if err := validateContinuationBindingID(bindingID); err != nil {
+ return config, err
+ }
+ if accessReceipt == nil || !accessReceipt.GetAllowed() || accessReceipt.GetRequest() == nil {
+ return config, fmt.Errorf("attenuated continuation requires an allowed checked-access receipt")
+ }
+ checked := accessReceipt.GetRequest()
+ if checked.GetCorrelationId() != bindingID {
+ return config, fmt.Errorf("continuation binding must match checked-access correlation")
+ }
+ scope := request.GetScope()
+ if scope == nil {
+ return config, fmt.Errorf("attenuated continuation scope is required")
+ }
+ workspaces, err := validateContinuationStringScope("workspace", scope.GetWorkspaceScope())
+ if err != nil {
+ return config, err
+ }
+ operations, err := validateContinuationStringScope("operation", scope.GetOperationScope())
+ if err != nil {
+ return config, err
+ }
+ resources, err := continuationResourceScope(scope.GetResourceScope())
+ if err != nil {
+ return config, err
+ }
+ maxAccess := int(scope.GetMaxAccessLevel())
+ if err := acl.ValidateAccessLevel(maxAccess); err != nil || maxAccess <= 0 {
+ return config, fmt.Errorf("attenuated continuation max access level is invalid")
+ }
+ if checked.GetWorkspace() == "" || len(workspaces) != 1 || workspaces[0] != checked.GetWorkspace() {
+ return config, fmt.Errorf("attenuated continuation must be confined to the checked workspace")
+ }
+ if err := acl.ValidateAuthorityGrantScopeAttenuation(parent, acl.CreateAuthorityGrantRequest{
+ WorkspaceScope: workspaces, ResourceScope: resources, OperationScope: operations,
+ MaxAccessLevel: maxAccess, RemainingHops: 0,
+ }); err != nil {
+ return config, err
+ }
+ config.bindingID = bindingID
+ config.workspaceScope = workspaces
+ config.resourceScope = resources
+ config.operationScope = operations
+ config.maxAccessLevel = maxAccess
+ return config, nil
+
+ default:
+ return config, fmt.Errorf("authority continuation scope mode is required")
+ }
+}
+
+func isConcreteContinuationTarget(target models.Identity, raw string) bool {
+ if target.CanonicalPrincipalID() == "" || target.CanonicalPrincipalID() != raw || strings.ContainsAny(raw, "*?[]") {
+ return false
+ }
+ switch target.Type {
+ case models.PrincipalService:
+ return target.Implementation != "" && target.Specifier != ""
+ case models.PrincipalAgent:
+ return target.Workspace != "" && target.Implementation != "" && target.Specifier != ""
+ default:
+ return false
+ }
+}
+
+func validateContinuationBindingID(value string) error {
+ if value == "" || value != strings.TrimSpace(value) || len(value) > maxContinuationBindingIDLength || strings.ContainsRune(value, '\x00') {
+ return fmt.Errorf("attenuated continuation binding id is invalid")
+ }
+ return nil
+}
+
+func validateContinuationStringScope(label string, values []string) ([]string, error) {
+ if len(values) == 0 {
+ return nil, fmt.Errorf("attenuated continuation %s scope is required", label)
+ }
+ out := make([]string, 0, len(values))
+ seen := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ if value == "" || value != strings.TrimSpace(value) || strings.ContainsRune(value, '\x00') {
+ return nil, fmt.Errorf("attenuated continuation %s scope contains an invalid value", label)
+ }
+ if _, exists := seen[value]; exists {
+ return nil, fmt.Errorf("attenuated continuation %s scope contains a duplicate value", label)
+ }
+ seen[value] = struct{}{}
+ out = append(out, value)
+ }
+ return out, nil
+}
+
+func continuationResourceScope(entries []*pb.ACLAuthorityGrantResourceScopeEntry) (map[string][]string, error) {
+ if len(entries) == 0 {
+ return nil, fmt.Errorf("attenuated continuation resource scope is required")
+ }
+ out := make(map[string][]string, len(entries))
+ for _, entry := range entries {
+ if entry == nil || entry.GetResourceType() == "" || entry.GetResourceType() != strings.TrimSpace(entry.GetResourceType()) || strings.ContainsRune(entry.GetResourceType(), '\x00') {
+ return nil, fmt.Errorf("attenuated continuation resource scope contains an invalid resource type")
+ }
+ if _, exists := out[entry.GetResourceType()]; exists {
+ return nil, fmt.Errorf("attenuated continuation resource scope contains a duplicate resource type")
+ }
+ patterns, err := validateContinuationStringScope("resource pattern", entry.GetPatterns())
+ if err != nil {
+ return nil, err
+ }
+ out[entry.GetResourceType()] = patterns
+ }
+ return out, nil
+}
+
+func authorityContinuationScope(grant *acl.AuthorityGrant) *pb.AuthorityContinuationScope {
+ if grant == nil {
+ return nil
+ }
+ resourceTypes := make([]string, 0, len(grant.ResourceScope))
+ for resourceType := range grant.ResourceScope {
+ resourceTypes = append(resourceTypes, resourceType)
+ }
+ sort.Strings(resourceTypes)
+ resources := make([]*pb.ACLAuthorityGrantResourceScopeEntry, 0, len(resourceTypes))
+ for _, resourceType := range resourceTypes {
+ resources = append(resources, &pb.ACLAuthorityGrantResourceScopeEntry{
+ ResourceType: resourceType,
+ Patterns: cloneStringSlice(grant.ResourceScope[resourceType]),
+ })
+ }
+ return &pb.AuthorityContinuationScope{
+ WorkspaceScope: cloneStringSlice(grant.WorkspaceScope),
+ ResourceScope: resources,
+ OperationScope: cloneStringSlice(grant.OperationScope),
+ MaxAccessLevel: int32(grant.MaxAccessLevel),
+ }
+}
+
+func messageAuthorityContinuationReusable(grant, parent *acl.AuthorityGrant, deliveryTarget string, now time.Time) bool {
+ if grant == nil || parent == nil || grant.ParentGrantID == nil || *grant.ParentGrantID != parent.GrantID {
+ return false
+ }
+ if err := grant.ValidateActiveAt(now); err != nil {
+ return false
+ }
+ if kind, ok := grant.Metadata[continuationMetadataKindKey].(bool); !ok || !kind {
+ return false
+ }
+ if target, ok := grant.Metadata[continuationMetadataTargetKey].(string); !ok || target != deliveryTarget {
+ return false
+ }
+ if mode, ok := grant.Metadata[continuationMetadataModeKey].(string); !ok || mode != pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT.String() {
+ return false
+ }
+ if grant.MayDelegate || grant.RemainingHops != 0 ||
+ grant.MaxAccessLevel != parent.MaxAccessLevel ||
+ grant.SubjectType != parent.SubjectType || grant.SubjectID != parent.SubjectID ||
+ grant.RootSubjectType != parent.RootSubjectType || grant.RootSubjectID != parent.RootSubjectID ||
+ !slices.Equal(grant.WorkspaceScope, parent.WorkspaceScope) ||
+ !slices.Equal(grant.OperationScope, parent.OperationScope) ||
+ !resourceScopesEqual(grant.ResourceScope, parent.ResourceScope) ||
+ grant.ExpiresAt.After(parent.ExpiresAt) ||
+ !grant.RenewableUntil.Equal(grant.ExpiresAt) {
+ return false
+ }
+ return true
+}
+
+func resourceScopesEqual(left, right map[string][]string) bool {
+ if len(left) != len(right) {
+ return false
+ }
+ for resourceType, patterns := range left {
+ if !slices.Equal(patterns, right[resourceType]) {
+ return false
+ }
+ }
+ return true
+}
diff --git a/server/internal/gateway/authority_continuation_test.go b/server/internal/gateway/authority_continuation_test.go
new file mode 100644
index 0000000..12cc409
--- /dev/null
+++ b/server/internal/gateway/authority_continuation_test.go
@@ -0,0 +1,317 @@
+package gateway
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+ "path/filepath"
+ "slices"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ aclsqlite "github.com/scitrera/aether/server/internal/storage/acl/sqlite"
+ "github.com/scitrera/aether/server/pkg/models"
+ _ "modernc.org/sqlite"
+)
+
+func newAuthorityContinuationHarness(t *testing.T) (*GatewayServer, *aclsqlite.Store) {
+ t.Helper()
+ dbPath := filepath.Join(t.TempDir(), "authority-continuation.db")
+ db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_journal_mode=WAL&_busy_timeout=5000", dbPath))
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ db.SetMaxOpenConns(1)
+ store, err := aclsqlite.New(db, nil, nil, "continuation-test")
+ if err != nil {
+ _ = db.Close()
+ t.Fatalf("aclsqlite.New: %v", err)
+ }
+ t.Cleanup(func() {
+ _ = store.Close()
+ _ = db.Close()
+ })
+ return &GatewayServer{acl: store, gatewayID: "continuation-test"}, store
+}
+
+func createContinuationParent(t *testing.T, store *aclsqlite.Store, remainingHops int) (*acl.ResolvedAuthority, models.Identity, models.Identity) {
+ t.Helper()
+ ctx := context.Background()
+ subject := models.Identity{Type: models.PrincipalUser, ID: "alice@example.com"}
+ actor := models.Identity{Type: models.PrincipalAgent, Workspace: "project-a", Implementation: "sahara", Specifier: "worker-1"}
+ grant, err := store.CreateAuthorityGrant(ctx, acl.CreateAuthorityGrantRequest{
+ Subject: subject,
+ Delegate: actor,
+ IssuedBy: subject,
+ MayDelegate: remainingHops > 0,
+ RemainingHops: remainingHops,
+ WorkspaceScope: []string{"project-a"},
+ ResourceScope: map[string][]string{"tool": {"workspace.*"}},
+ OperationScope: []string{"query", "describe", "invoke"},
+ MaxAccessLevel: acl.AccessReadWrite,
+ AudienceType: acl.AuthorityAudienceAgent,
+ AudienceID: actor.CanonicalPrincipalID(),
+ ExpiresAt: time.Now().UTC().Add(30 * time.Minute),
+ RenewableUntil: time.Now().UTC().Add(2 * time.Hour),
+ Reason: "continuation-test-parent",
+ })
+ if err != nil {
+ t.Fatalf("CreateAuthorityGrant(parent): %v", err)
+ }
+ resolved, err := store.ResolveAuthority(ctx, actor, acl.RequestAuthorityContext{
+ Mode: "on_behalf_of", Subject: subject, GrantID: grant.GrantID,
+ }, acl.GrantAudienceContext{Actor: actor})
+ if err != nil {
+ t.Fatalf("ResolveAuthority(parent): %v", err)
+ }
+ return resolved, actor, subject
+}
+
+func inheritedServiceContinuation() *pb.AuthorityContinuationRequest {
+ return &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT,
+ }
+}
+
+func attenuatedContinuation(bindingID, workspace string) *pb.AuthorityContinuationRequest {
+ return &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_ATTENUATE,
+ BindingId: bindingID,
+ Scope: &pb.AuthorityContinuationScope{
+ WorkspaceScope: []string{workspace},
+ ResourceScope: []*pb.ACLAuthorityGrantResourceScopeEntry{
+ {ResourceType: "tool", Patterns: []string{"workspace.*"}},
+ },
+ OperationScope: []string{"query"},
+ MaxAccessLevel: int32(acl.AccessRead),
+ },
+ }
+}
+
+func allowedContinuationReceipt(bindingID, workspace string) *pb.AccessDecisionReceipt {
+ return &pb.AccessDecisionReceipt{
+ DecisionId: "decision-" + bindingID,
+ Allowed: true,
+ Request: &pb.ResourceAccessRequest{
+ ResourceType: "tool-catalog/entry", ResourceId: "provider/tool",
+ Operation: "tool.invoke.read", Workspace: workspace,
+ RequiredAccessLevel: int32(acl.AccessRead), CorrelationId: bindingID,
+ },
+ }
+}
+
+func TestDeriveMessageAuthorityContinuation_BindsLeafAndReuses(t *testing.T) {
+ gw, store := newAuthorityContinuationHarness(t)
+ authority, _, subject := createContinuationParent(t, store, 1)
+ ctx := context.Background()
+ target := "sv::tool-catalog::catalog-7"
+
+ forwarded, err := gw.deriveMessageAuthorityContinuation(
+ ctx, authority, target, inheritedServiceContinuation(), nil, uuid.New(),
+ )
+ if err != nil {
+ t.Fatalf("deriveMessageAuthorityContinuation: %v", err)
+ }
+ if forwarded.GetDeliveryTarget() != target {
+ t.Fatalf("delivery target = %q, want %q", forwarded.GetDeliveryTarget(), target)
+ }
+ if forwarded.GetAuthorization().GetGrantId() == "" || forwarded.GetAuthorization().GetAuthorityMode() != "on_behalf_of" {
+ t.Fatalf("invalid forwarded authorization: %+v", forwarded.GetAuthorization())
+ }
+ if forwarded.GetAuthorization().GetSubject().GetPrincipalId() != subject.CanonicalPrincipalID() {
+ t.Fatalf("forwarded subject = %q, want %q", forwarded.GetAuthorization().GetSubject().GetPrincipalId(), subject.CanonicalPrincipalID())
+ }
+
+ child, err := store.GetAuthorityGrant(ctx, forwarded.GetAuthorization().GetGrantId())
+ if err != nil {
+ t.Fatalf("GetAuthorityGrant(child): %v", err)
+ }
+ if child.ParentGrantID == nil || *child.ParentGrantID != authority.Grant.GrantID {
+ t.Fatalf("child parent = %v, want %q", child.ParentGrantID, authority.Grant.GrantID)
+ }
+ if child.MayDelegate || child.RemainingHops != 0 {
+ t.Fatalf("child must be a non-delegable leaf: may_delegate=%v remaining_hops=%d", child.MayDelegate, child.RemainingHops)
+ }
+ if child.AudienceType != acl.AuthorityAudienceService || child.AudienceID != target {
+ t.Fatalf("child audience = %s/%s, want service/%s", child.AudienceType, child.AudienceID, target)
+ }
+ if child.ExpiresAt.After(time.Now().UTC().Add(messageAuthorityContinuationTTL + 5*time.Second)) {
+ t.Fatalf("child expiry %s exceeds continuation TTL", child.ExpiresAt)
+ }
+ if child.MaxAccessLevel != authority.Grant.MaxAccessLevel ||
+ !resourceScopesEqual(child.ResourceScope, authority.Grant.ResourceScope) {
+ t.Fatalf("child scope did not preserve parent attenuation")
+ }
+
+ service, parseErr := models.ParseIdentity(target)
+ if parseErr != nil {
+ t.Fatalf("ParseIdentity(target): %v", parseErr)
+ }
+ resolved, err := store.ResolveAuthority(ctx, service, acl.RequestAuthorityContext{
+ Mode: "on_behalf_of",
+ Subject: subject,
+ GrantID: child.GrantID,
+ }, acl.GrantAudienceContext{Actor: service})
+ if err != nil || resolved == nil {
+ t.Fatalf("service ResolveAuthority(child) = %+v, %v", resolved, err)
+ }
+
+ reused, err := gw.deriveMessageAuthorityContinuation(
+ ctx, authority, target, inheritedServiceContinuation(), nil, uuid.New(),
+ )
+ if err != nil {
+ t.Fatalf("deriveMessageAuthorityContinuation(reuse): %v", err)
+ }
+ if reused.GetAuthorization().GetGrantId() != child.GrantID {
+ t.Fatalf("reused grant = %q, want %q", reused.GetAuthorization().GetGrantId(), child.GrantID)
+ }
+}
+
+func TestDeriveMessageAuthorityContinuation_BindsAttenuatedAgentPerInvocation(t *testing.T) {
+ gw, store := newAuthorityContinuationHarness(t)
+ authority, _, subject := createContinuationParent(t, store, 1)
+ ctx := context.Background()
+ target := "ag::project-a::tool-host::one"
+ bindingID := "tool-call-1"
+
+ forwarded, err := gw.deriveMessageAuthorityContinuation(
+ ctx, authority, target, attenuatedContinuation(bindingID, "project-a"),
+ allowedContinuationReceipt(bindingID, "project-a"), uuid.New(),
+ )
+ if err != nil {
+ t.Fatalf("derive agent continuation: %v", err)
+ }
+ if forwarded.GetDeliveryTarget() != target || forwarded.GetBindingId() != bindingID {
+ t.Fatalf("forwarded binding = target:%q binding:%q", forwarded.GetDeliveryTarget(), forwarded.GetBindingId())
+ }
+ if forwarded.GetScope().GetMaxAccessLevel() != int32(acl.AccessRead) ||
+ !slices.Equal(forwarded.GetScope().GetOperationScope(), []string{"query"}) {
+ t.Fatalf("forwarded scope = %+v", forwarded.GetScope())
+ }
+
+ child, err := store.GetAuthorityGrant(ctx, forwarded.GetAuthorization().GetGrantId())
+ if err != nil {
+ t.Fatalf("GetAuthorityGrant(agent child): %v", err)
+ }
+ if child.AudienceType != acl.AuthorityAudienceAgent || child.AudienceID != target {
+ t.Fatalf("child audience = %s/%s, want agent/%s", child.AudienceType, child.AudienceID, target)
+ }
+ if child.MayDelegate || child.RemainingHops != 0 || child.MaxAccessLevel != acl.AccessRead {
+ t.Fatalf("unsafe agent child: %+v", child)
+ }
+ agent, parseErr := models.ParseIdentity(target)
+ if parseErr != nil {
+ t.Fatalf("ParseIdentity(agent): %v", parseErr)
+ }
+ resolved, err := store.ResolveAuthority(ctx, agent, acl.RequestAuthorityContext{
+ Mode: "on_behalf_of", Subject: subject, GrantID: child.GrantID,
+ }, acl.GrantAudienceContext{Actor: agent})
+ if err != nil || resolved == nil {
+ t.Fatalf("agent ResolveAuthority(child) = %+v, %v", resolved, err)
+ }
+
+ second, err := gw.deriveMessageAuthorityContinuation(
+ ctx, authority, target, attenuatedContinuation(bindingID, "project-a"),
+ allowedContinuationReceipt(bindingID, "project-a"), uuid.New(),
+ )
+ if err != nil {
+ t.Fatalf("derive second agent continuation: %v", err)
+ }
+ if second.GetAuthorization().GetGrantId() == child.GrantID {
+ t.Fatal("agent continuation was reused across invocations")
+ }
+}
+
+func TestDeriveMessageAuthorityContinuation_RejectsUnsafeInputsAndCascadesRevocation(t *testing.T) {
+ gw, store := newAuthorityContinuationHarness(t)
+ authority, _, _ := createContinuationParent(t, store, 1)
+ ctx := context.Background()
+
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, nil, "sv::tool-catalog::one", inheritedServiceContinuation(), nil, uuid.Nil); err == nil {
+ t.Fatal("expected missing authority to fail")
+ }
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, "sv::tool-catalog", inheritedServiceContinuation(), nil, uuid.Nil); err == nil {
+ t.Fatal("expected wildcard service target to fail")
+ }
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, "ag::project-a::worker::one", inheritedServiceContinuation(), nil, uuid.Nil); err == nil {
+ t.Fatal("expected inherited agent target to fail")
+ }
+
+ forwarded, err := gw.deriveMessageAuthorityContinuation(ctx, authority, "sv::tool-catalog::one", inheritedServiceContinuation(), nil, uuid.Nil)
+ if err != nil {
+ t.Fatalf("deriveMessageAuthorityContinuation: %v", err)
+ }
+ if err := store.RevokeAuthorityGrant(ctx, authority.Grant.GrantID); err != nil {
+ t.Fatalf("RevokeAuthorityGrant(parent): %v", err)
+ }
+ child, err := store.GetAuthorityGrant(ctx, forwarded.GetAuthorization().GetGrantId())
+ if err != nil {
+ t.Fatalf("GetAuthorityGrant(child): %v", err)
+ }
+ if err := child.ValidateActiveAt(time.Now()); !errors.Is(err, acl.ErrAuthorityGrantRevoked) {
+ t.Fatalf("child active after parent revocation: %v", err)
+ }
+
+ noHop, _, _ := createContinuationParent(t, store, 0)
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, noHop, "sv::tool-catalog::two", inheritedServiceContinuation(), nil, uuid.Nil); !errors.Is(err, acl.ErrAuthorityGrantDelegationDenied) {
+ t.Fatalf("no-hop error = %v, want delegation denied", err)
+ }
+}
+
+func TestDeriveMessageAuthorityContinuation_RejectsUnboundOrEscalatedAgentScope(t *testing.T) {
+ gw, store := newAuthorityContinuationHarness(t)
+ authority, _, _ := createContinuationParent(t, store, 1)
+ ctx := context.Background()
+ target := "ag::project-a::tool-host::one"
+ bindingID := "tool-call-1"
+
+ request := attenuatedContinuation(bindingID, "project-a")
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, target, request, nil, uuid.Nil); err == nil {
+ t.Fatal("expected missing checked receipt to fail")
+ }
+ wrongCorrelation := allowedContinuationReceipt("another-call", "project-a")
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, target, request, wrongCorrelation, uuid.Nil); err == nil {
+ t.Fatal("expected mismatched correlation to fail")
+ }
+ wrongWorkspace := allowedContinuationReceipt(bindingID, "project-b")
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, target, request, wrongWorkspace, uuid.Nil); err == nil {
+ t.Fatal("expected mismatched workspace to fail")
+ }
+ escalated := attenuatedContinuation(bindingID, "project-a")
+ escalated.Scope.MaxAccessLevel = int32(acl.AccessManage)
+ if _, err := gw.deriveMessageAuthorityContinuation(ctx, authority, target, escalated, allowedContinuationReceipt(bindingID, "project-a"), uuid.Nil); !errors.Is(err, acl.ErrAuthorityGrantScopeEscalation) {
+ t.Fatalf("scope escalation error = %v, want scope escalation", err)
+ }
+}
+
+func TestCreateTaskAuthorityGrant_RequiresDeclaredDownstreamBudget(t *testing.T) {
+ gw, store := newAuthorityContinuationHarness(t)
+ ctx := context.Background()
+ worker := models.Identity{Type: models.PrincipalAgent, Workspace: "project-a", Implementation: "sahara", Specifier: "worker-2"}
+
+ oneHop, actor, _ := createContinuationParent(t, store, 1)
+ if _, err := gw.createTaskAuthorityGrant(
+ ctx, oneHop, actor, worker,
+ acl.AuthorityAudienceAgent, worker.CanonicalPrincipalID(),
+ "task-one", "chat", "targeted", 1,
+ ); err == nil {
+ t.Fatal("expected task grant to reject a parent that cannot leave the requested downstream hop")
+ }
+
+ twoHops, actor, _ := createContinuationParent(t, store, 2)
+ grant, err := gw.createTaskAuthorityGrant(
+ ctx, twoHops, actor, worker,
+ acl.AuthorityAudienceAgent, worker.CanonicalPrincipalID(),
+ "task-two", "chat", "targeted", 1,
+ )
+ if err != nil {
+ t.Fatalf("createTaskAuthorityGrant with sufficient budget: %v", err)
+ }
+ if !grant.MayDelegate || grant.RemainingHops != 1 {
+ t.Fatalf("task grant budget = may_delegate:%v remaining:%d, want true/1", grant.MayDelegate, grant.RemainingHops)
+ }
+}
diff --git a/server/internal/gateway/authority_grant_handler.go b/server/internal/gateway/authority_grant_handler.go
index 1497a32..b049c1c 100644
--- a/server/internal/gateway/authority_grant_handler.go
+++ b/server/internal/gateway/authority_grant_handler.go
@@ -82,7 +82,7 @@ func (s *GatewayServer) handleAuthorityGrantOp(ctx context.Context, client *Clie
})
case pb.AuthorityGrantOperation_GET:
- grant, err := s.getVisibleAuthorityGrant(ctx, client, actor, op.GetGrantId())
+ grant, err := s.getVisibleAuthorityGrantForSchedule(ctx, client, actor, op.GetGrantId(), op.GetWorkflowScheduleId())
if err != nil {
logging.Logger.Error().Err(err).Str("grant_id", op.GetGrantId()).Msg("handleAuthorityGrantOp: get failed")
s.logAuthorityGrantLifecycle(ctx, actor, client.SessionUUID, audit.OpAuthorityGrantGet, nil, false, err.Error(), map[string]interface{}{
@@ -131,7 +131,7 @@ func (s *GatewayServer) handleAuthorityGrantOp(ctx context.Context, client *Clie
})
case pb.AuthorityGrantOperation_REVOKE:
- grant, err := s.revokeVisibleAuthorityGrant(ctx, client, actor, op.GetGrantId())
+ grant, err := s.revokeVisibleAuthorityGrantForSchedule(ctx, client, actor, op.GetGrantId(), op.GetWorkflowScheduleId())
if err != nil {
logging.Logger.Error().Err(err).Str("grant_id", op.GetGrantId()).Msg("handleAuthorityGrantOp: revoke failed")
s.logAuthorityGrantLifecycle(ctx, actor, client.SessionUUID, audit.OpAuthorityGrantRevoke, nil, false, err.Error(), map[string]interface{}{
@@ -518,6 +518,10 @@ func (s *GatewayServer) deriveAuthorityGrant(ctx context.Context, client *Client
}
func (s *GatewayServer) getVisibleAuthorityGrant(ctx context.Context, client *ClientSession, actor models.Identity, grantID string) (*acl.AuthorityGrant, error) {
+ return s.getVisibleAuthorityGrantForSchedule(ctx, client, actor, grantID, "")
+}
+
+func (s *GatewayServer) getVisibleAuthorityGrantForSchedule(ctx context.Context, client *ClientSession, actor models.Identity, grantID, workflowScheduleID string) (*acl.AuthorityGrant, error) {
if strings.TrimSpace(grantID) == "" {
return nil, fmt.Errorf("grant_id is required")
}
@@ -526,7 +530,14 @@ func (s *GatewayServer) getVisibleAuthorityGrant(ctx context.Context, client *Cl
if err != nil {
return nil, err
}
- if err := s.requireVisibleAuthorityGrant(ctx, client, actor, grant); err != nil {
+ if workflowScheduleID != "" {
+ if actor.Type != models.PrincipalWorkflowEngine || grant.AudienceType != acl.AuthorityAudienceWorkflowSchedule || grant.AudienceID != workflowScheduleID {
+ return nil, fmt.Errorf("workflow schedule authority context does not match grant")
+ }
+ if err := s.requireCurrentDelegateAuthorityForSchedule(ctx, client, actor, grant, workflowScheduleID); err != nil {
+ return nil, err
+ }
+ } else if err := s.requireVisibleAuthorityGrant(ctx, client, actor, grant); err != nil {
return nil, err
}
@@ -563,8 +574,8 @@ func (s *GatewayServer) renewVisibleAuthorityGrant(ctx context.Context, client *
return s.acl.RenewAuthorityGrantOpts(ctx, grant.GrantID, opts)
}
-func (s *GatewayServer) revokeVisibleAuthorityGrant(ctx context.Context, client *ClientSession, actor models.Identity, grantID string) (*acl.AuthorityGrant, error) {
- grant, err := s.getVisibleAuthorityGrant(ctx, client, actor, grantID)
+func (s *GatewayServer) revokeVisibleAuthorityGrantForSchedule(ctx context.Context, client *ClientSession, actor models.Identity, grantID, workflowScheduleID string) (*acl.AuthorityGrant, error) {
+ grant, err := s.getVisibleAuthorityGrantForSchedule(ctx, client, actor, grantID, workflowScheduleID)
if err != nil {
return nil, err
}
@@ -716,6 +727,10 @@ func (s *GatewayServer) requireVisibleAuthorityGrant(ctx context.Context, client
}
func (s *GatewayServer) requireCurrentDelegateAuthority(ctx context.Context, client *ClientSession, actor models.Identity, grant *acl.AuthorityGrant) error {
+ return s.requireCurrentDelegateAuthorityForSchedule(ctx, client, actor, grant, "")
+}
+
+func (s *GatewayServer) requireCurrentDelegateAuthorityForSchedule(ctx context.Context, client *ClientSession, actor models.Identity, grant *acl.AuthorityGrant, workflowScheduleID string) error {
if grant == nil {
return fmt.Errorf("authority grant is required")
}
@@ -733,9 +748,10 @@ func (s *GatewayServer) requireCurrentDelegateAuthority(ctx context.Context, cli
Subject: subject,
GrantID: grant.GrantID,
}, acl.GrantAudienceContext{
- SessionID: client.SessionUUID,
- AssociatedTaskID: client.AssociatedTaskID,
- Actor: actor,
+ SessionID: client.SessionUUID,
+ AssociatedTaskID: client.AssociatedTaskID,
+ Actor: actor,
+ WorkflowScheduleID: workflowScheduleID,
})
if err != nil {
return fmt.Errorf("authority grant is not valid for the current delegate context: %w", err)
diff --git a/server/internal/gateway/connect.go b/server/internal/gateway/connect.go
index 1eba56a..f75daa6 100644
--- a/server/internal/gateway/connect.go
+++ b/server/internal/gateway/connect.go
@@ -485,6 +485,10 @@ func (s *GatewayServer) Connect(stream pb.AetherGateway_ConnectServer) error {
// keeps streaming TaskEvent deliveries until UNSUBSCRIBE or
// disconnect.
go s.handleTaskSubscriptionOp(sessionCtx, client, p.TaskSubscriptionOp)
+ case *pb.UpstreamMessage_AccessCheck:
+ s.handleAccessCheck(sessionCtx, client, p.AccessCheck)
+ case *pb.UpstreamMessage_BatchAccessCheck:
+ s.handleBatchAccessCheck(sessionCtx, client, p.BatchAccessCheck)
case *pb.UpstreamMessage_WorkflowOp:
s.handleWorkflowOp(sessionCtx, client, p.WorkflowOp)
case *pb.UpstreamMessage_WorkflowResponse:
diff --git a/server/internal/gateway/kv_handler.go b/server/internal/gateway/kv_handler.go
index c845d9b..ba9e494 100644
--- a/server/internal/gateway/kv_handler.go
+++ b/server/internal/gateway/kv_handler.go
@@ -165,6 +165,65 @@ func isInfraCoordAccess(identity models.Identity, key string) bool {
}
}
+// resolveUserScopeSubject enforces the user axis of the KV scope taxonomy.
+//
+// ScopeSpec has two independent axes: Sharing decides which AGENTS rendezvous
+// on a key (exclusive embeds the agent identity, shared does not), and Identity
+// decides WHOSE data it is. "user-shared" therefore means one user, every
+// agent — the sharing axis was never meant to relax the user boundary.
+//
+// That boundary had no enforcement behind it: op.UserId is client-supplied and
+// ValidateScopeSpec only checks it is non-empty, so any caller could name
+// another user and address their namespace directly. What stood in for it was
+// an ACL default-deny on the shared user scopes — a mitigation at the wrong
+// layer, which is why the shared scopes could not be opened for a legitimate
+// same-user read without also permitting cross-user reads.
+//
+// Under an on-behalf-of grant the subject IS the user, so the user axis is
+// derivable rather than assertable: it is filled in when omitted and must match
+// when supplied. A mismatch is a caller trying to reach a namespace its grant
+// does not cover.
+//
+// Callers acting under their OWN authority are untouched: platform-server
+// legitimately writes per-user session state for the browser's user, and its
+// reach is bounded by the explicit kv_scope grants it holds. Direct user
+// principals cannot reach KV at all (the type gate in HandleKVOperation), so
+// OBO is the only path that can assert a foreign user id.
+func resolveUserScopeSubject(scope kv.KVScope, identity models.Identity, authority *acl.ResolvedAuthority, userID string) (string, error) {
+ if authority == nil || authority.Subject.Type != models.PrincipalUser {
+ return userID, nil
+ }
+ spec, ok := kv.ScopeSpecFromKVScope(scope)
+ if !ok {
+ // Unrecognized scope: leave it alone, ValidateScopeConfig rejects it.
+ return userID, nil
+ }
+ if spec.Identity != kv.IdentityScopeUser && spec.Identity != kv.IdentityScopeUserWorkspace {
+ return userID, nil
+ }
+
+ subject := authority.Subject.ID
+ if subject == "" {
+ logging.Logger.Warn().
+ Str("identity", identity.String()).Str("scope", string(scope)).
+ Msg("on-behalf-of subject carries no user id for a user-scoped KV operation")
+ return "", status.Error(codes.PermissionDenied,
+ "on-behalf-of subject has no user id for a user-scoped KV operation")
+ }
+ if userID == "" {
+ return subject, nil
+ }
+ if userID != subject {
+ logging.Logger.Warn().
+ Str("identity", identity.String()).Str("scope", string(scope)).
+ Str("requested_user", userID).Str("subject_user", subject).
+ Msg("KV user-scope mismatch: request names a different user than the on-behalf-of subject")
+ return "", status.Error(codes.PermissionDenied,
+ "user-scoped KV operation names a different user than the on-behalf-of subject")
+ }
+ return userID, nil
+}
+
// checkScopeReadPermission checks scope-level read permission (used for LIST which has no specific key).
func (h *KVHandler) checkScopeReadPermission(ctx context.Context, identity models.Identity, authority *acl.ResolvedAuthority, scope kv.KVScope, operation, workspace string, sessionID uuid.UUID) error {
if h.aclService == nil {
@@ -206,12 +265,26 @@ func (h *KVHandler) HandleKVOperation(
// to the tenant's internal KV. Like the WorkflowEngine, this only opens the
// type gate — every key it touches still requires an explicit ACL grant via
// checkKeyPermission (seeded for metrics::shard0 in acl_seed.py).
+ // Orchestrators are permitted for the same reason: they must read the tenant
+ // ProvisionSpec and per-tenant launcher credentials from KV in order to build
+ // a worker's environment. acl_seed.py already seeds NARROW grants for exactly
+ // that (orc::::* -> kv_key/provision/*, *ikv:provision:*, and
+ // *ikv:api_key:MODAL_*) — but those grants could never take effect, because
+ // this type gate rejected orchestrators before checkKeyPermission was ever
+ // consulted. The result was a worker launched with no environment: the agent
+ // came up and then failed to resolve its provider secrets. The gateway logged
+ // the real cause (PermissionDenied) while the orchestrator only ever saw a
+ // generic "[KV_ERROR] internal error processing KV operation".
+ //
+ // As with the WorkflowEngine and MetricsBridge above, this ONLY opens the type
+ // gate; every key an orchestrator touches still requires an explicit ACL grant.
if identity.Type != models.PrincipalAgent &&
identity.Type != models.PrincipalTask &&
identity.Type != models.PrincipalService &&
identity.Type != models.PrincipalWorkflowEngine &&
- identity.Type != models.PrincipalMetricsBridge {
- return status.Error(codes.PermissionDenied, "only agents, tasks, services, the metrics bridge, and the workflow engine can access KV store")
+ identity.Type != models.PrincipalMetricsBridge &&
+ identity.Type != models.PrincipalOrchestrator {
+ return status.Error(codes.PermissionDenied, "only agents, tasks, services, orchestrators, the metrics bridge, and the workflow engine can access KV store")
}
// Map proto enum scope to internal KVScope (default to workspace for backward compatibility)
@@ -242,6 +315,12 @@ func (h *KVHandler) HandleKVOperation(
workspace = identity.Workspace
}
+ // Enforce the user axis of the scope taxonomy (see resolveUserScopeSubject).
+ var err error
+ if userID, err = resolveUserScopeSubject(scope, identity, authority, userID); err != nil {
+ return err
+ }
+
// Validate scope configuration
if err := kv.ValidateScopeConfig(scope, identity, userID, workspace); err != nil {
return status.Errorf(codes.InvalidArgument, "invalid scope config: %v", err)
@@ -860,17 +939,36 @@ func (h *KVHandler) handleIncrement(
return err
}
+ // Establish the window boundary ATOMICALLY, BEFORE incrementing. SetNX writes
+ // the key with its TTL only when absent, so a counter cannot come into
+ // existence without an expiry.
+ //
+ // This replaces a set-TTL-after-first-increment approach that was load-bearing
+ // and unsound: the expiry was applied only when some caller observed
+ // counterVal == 1, through a SEPARATE Set. Any key that began life another way
+ // — that Set failing, two first-increments racing so neither saw 1, or a key
+ // written by some other path — never received a TTL, incremented forever, and
+ // pinned its principal at "limit exceeded" permanently. Nothing self-healed,
+ // because the state lives here rather than in the caller: restarting the
+ // client, the server, or anything between them changed nothing.
+ //
+ // Observed in production 2026-08-05: a MemoryLayer per-user rate-limit counter
+ // stuck at 10078 against a limit of 10000, returning 429 indefinitely and
+ // surviving every restart. The old Set also clobbered the value back to "1",
+ // silently discarding concurrent increments.
+ if ttl > 0 {
+ if _, nxErr := h.kvStore.SetNX(ctx, identity, scope, key, "0", userID, workspace, ttl); nxErr != nil {
+ logging.Logger.Warn().Err(nxErr).Str("identity", identity.String()).Str("key", key).Msg("KV INCREMENT: SetNX window init failed; falling back to post-increment TTL")
+ }
+ }
+
counterVal, err := h.kvStore.Increment(ctx, identity, scope, key, userID, workspace)
- // If a TTL is specified and this is the first increment (counterVal == 1),
- // set the expiry on the key. We re-set the key with the string representation
- // of the counter value so the TTL takes effect without losing the numeric value.
- // NOTE: This two-step approach (INCR then EXPIRE via SET) is not fully atomic.
- // For strict atomicity (e.g., sliding rate limit windows), a Lua script should
- // be used instead. This is acceptable for fixed-window rate limit use cases
- // where the window is established on the first increment.
+ // Fallback for the case where SetNX above failed: Increment may then have
+ // created the key with no expiry. counterVal == 1 proves the key was absent
+ // before this increment, so writing "1" with the TTL cannot lose a concurrent
+ // update. Without this a SetNX outage would reintroduce the permanent-pin bug.
if err == nil && ttl > 0 && counterVal == 1 {
- // Only set TTL on the first increment to establish the window boundary
if setErr := h.kvStore.Set(ctx, identity, scope, key, "1", userID, workspace, ttl); setErr != nil {
logging.Logger.Error().Err(setErr).Str("identity", identity.String()).Str("key", key).Msg("KV INCREMENT: failed to set TTL after first increment")
}
diff --git a/server/internal/gateway/kv_handler_test.go b/server/internal/gateway/kv_handler_test.go
index da022f8..ed7783f 100644
--- a/server/internal/gateway/kv_handler_test.go
+++ b/server/internal/gateway/kv_handler_test.go
@@ -96,20 +96,31 @@ func TestKVHandler_UserIdentity_ReturnsPermissionDenied(t *testing.T) {
}
}
-func TestKVHandler_OrchestratorIdentity_ReturnsPermissionDenied(t *testing.T) {
- h := newTestKVHandler(newMockKVReadWriter())
- cb, _ := captureResponses()
+// Orchestrators were originally denied KV outright. They are now permitted at the
+// TYPE gate so they can hold a private space for passing init args to the tasks
+// they create — access to any individual key still requires an explicit ACL grant
+// (acl_seed.py seeds narrow ones: provision/*, *ikv:provision:*,
+// *ikv:api_key:MODAL_*). Denying the type outright made those grants unreachable
+// and left workers launched with no environment.
+func TestKVHandler_OrchestratorIdentity_Permitted(t *testing.T) {
+ store := newMockKVReadWriter()
+ h := newTestKVHandler(store)
+ cb, msgs := captureResponses()
orchIdentity := models.Identity{Type: models.PrincipalOrchestrator, Implementation: "k8s", Specifier: "primary"}
op := &pb.KVOperation{
- Op: pb.KVOperation_GET,
- Scope: pb.KVOperation_GLOBAL,
- Key: "some-key",
+ Op: pb.KVOperation_GET,
+ Scope: pb.KVOperation_GLOBAL,
+ Key: "test-key",
+ Workspace: "ws1",
}
err := h.HandleKVOperation(context.Background(), orchIdentity, uuid.New(), nil, op, cb)
- if err == nil {
- t.Fatal("expected error for Orchestrator identity accessing KV store, got nil")
+ if err != nil {
+ t.Fatalf("unexpected error for Orchestrator identity: %v", err)
+ }
+ if len(*msgs) == 0 {
+ t.Error("expected a response message to be sent for successful GET")
}
}
diff --git a/server/internal/gateway/kv_user_scope_test.go b/server/internal/gateway/kv_user_scope_test.go
new file mode 100644
index 0000000..9221bf8
--- /dev/null
+++ b/server/internal/gateway/kv_user_scope_test.go
@@ -0,0 +1,120 @@
+package gateway
+
+import (
+ "testing"
+
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/internal/kv"
+ "github.com/scitrera/aether/server/pkg/models"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+// The Identity axis of the scope taxonomy ("whose data is this?") had no
+// enforcement: op.UserId is client-supplied and ValidateScopeSpec only checks
+// it is non-empty, so a caller could name any user and address their namespace.
+// An ACL default-deny on the shared user scopes stood in for it, which is a
+// mitigation at the wrong layer — it blocks legitimate same-user reads just as
+// hard as cross-user ones. These pin the axis itself.
+
+func oboAuthority(subjectUser string) *acl.ResolvedAuthority {
+ return &acl.ResolvedAuthority{
+ Actor: agentIdentity,
+ Subject: models.Identity{Type: models.PrincipalUser, ID: subjectUser},
+ Grant: &acl.AuthorityGrant{GrantID: "grant-1", RootSubjectType: "user", RootSubjectID: subjectUser},
+ }
+}
+
+func TestResolveUserScopeSubject_rejects_a_foreign_user(t *testing.T) {
+ // The whole point: an OBO grant for user A must not reach user B's
+ // namespace, on either shared user scope.
+ for _, scope := range []kv.KVScope{kv.ScopeUserShared, kv.ScopeUserWorkspaceShared, kv.ScopeUser, kv.ScopeUserWorkspace} {
+ got, err := resolveUserScopeSubject(scope, agentIdentity, oboAuthority("alice@example.com"), "bob@example.com")
+ if err == nil {
+ t.Fatalf("scope %s: expected denial, got user_id=%q", scope, got)
+ }
+ if status.Code(err) != codes.PermissionDenied {
+ t.Fatalf("scope %s: expected PermissionDenied, got %v", scope, status.Code(err))
+ }
+ }
+}
+
+func TestResolveUserScopeSubject_allows_the_subjects_own_namespace(t *testing.T) {
+ got, err := resolveUserScopeSubject(
+ kv.ScopeUserWorkspaceShared, agentIdentity, oboAuthority("alice@example.com"), "alice@example.com")
+ if err != nil {
+ t.Fatalf("same-user access must be allowed: %v", err)
+ }
+ if got != "alice@example.com" {
+ t.Fatalf("user_id = %q, want alice@example.com", got)
+ }
+}
+
+func TestResolveUserScopeSubject_derives_an_omitted_user_from_the_subject(t *testing.T) {
+ // Under OBO the user axis is derivable rather than assertable, so an
+ // omitted user_id is filled in instead of failing scope validation.
+ got, err := resolveUserScopeSubject(
+ kv.ScopeUserShared, agentIdentity, oboAuthority("alice@example.com"), "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if got != "alice@example.com" {
+ t.Fatalf("user_id = %q, want it derived from the subject", got)
+ }
+}
+
+func TestResolveUserScopeSubject_ignores_non_user_scopes(t *testing.T) {
+ // Global and workspace scopes have no user axis; a user_id on them is
+ // meaningless and must not be rewritten or rejected here.
+ for _, scope := range []kv.KVScope{kv.ScopeGlobal, kv.ScopeWorkspace, kv.ScopeGlobalExclusive, kv.ScopeWorkspaceExclusive} {
+ got, err := resolveUserScopeSubject(scope, agentIdentity, oboAuthority("alice@example.com"), "bob@example.com")
+ if err != nil {
+ t.Fatalf("scope %s: unexpected error %v", scope, err)
+ }
+ if got != "bob@example.com" {
+ t.Fatalf("scope %s: user_id = %q, want it untouched", scope, got)
+ }
+ }
+}
+
+func TestResolveUserScopeSubject_leaves_own_authority_callers_alone(t *testing.T) {
+ // platform-server writes per-user session state for the browser's user
+ // under its OWN service authority; its reach is bounded by the explicit
+ // kv_scope grants it holds, not by this pin. Breaking that would silently
+ // drop session state.
+ svc := models.Identity{Type: models.PrincipalService, Implementation: "platform-server", Specifier: "a"}
+ got, err := resolveUserScopeSubject(kv.ScopeUserWorkspaceShared, svc, nil, "alice@example.com")
+ if err != nil {
+ t.Fatalf("service under its own authority must be untouched: %v", err)
+ }
+ if got != "alice@example.com" {
+ t.Fatalf("user_id = %q, want it preserved", got)
+ }
+}
+
+func TestResolveUserScopeSubject_rejects_a_subject_with_no_user_id(t *testing.T) {
+ // A user-typed subject with an empty ID cannot identify a namespace;
+ // proceeding would fall back to whatever the caller supplied.
+ authority := oboAuthority("")
+ if _, err := resolveUserScopeSubject(
+ kv.ScopeUserShared, agentIdentity, authority, "bob@example.com"); err == nil {
+ t.Fatal("expected denial when the OBO subject carries no user id")
+ }
+}
+
+func TestResolveUserScopeSubject_ignores_non_user_subjects(t *testing.T) {
+ // Service-to-service OBO (a grant whose subject is not a user) has no user
+ // axis to pin; leave it to the ACL.
+ authority := &acl.ResolvedAuthority{
+ Actor: agentIdentity,
+ Subject: models.Identity{Type: models.PrincipalService, Implementation: "memorylayer", Specifier: "a"},
+ Grant: &acl.AuthorityGrant{GrantID: "grant-2"},
+ }
+ got, err := resolveUserScopeSubject(kv.ScopeUserShared, agentIdentity, authority, "bob@example.com")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if got != "bob@example.com" {
+ t.Fatalf("user_id = %q, want it untouched", got)
+ }
+}
diff --git a/server/internal/gateway/orchestration_integration.go b/server/internal/gateway/orchestration_integration.go
index 13670d7..8b8c017 100644
--- a/server/internal/gateway/orchestration_integration.go
+++ b/server/internal/gateway/orchestration_integration.go
@@ -5,6 +5,7 @@ import (
"crypto/sha256"
"database/sql"
"encoding/hex"
+ stderrors "errors"
"fmt"
"io"
"math/rand/v2"
@@ -237,6 +238,7 @@ func (s *GatewayServer) deliverQueuedTasksToAgent(
Payload: task.Payload,
}
applyHibernationHandoffToAssignment(assignment, task.Metadata)
+ applyTaskAuthorizationToAssignment(assignment, task)
err := client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_TaskAssignment{
@@ -279,6 +281,7 @@ func (s *GatewayServer) deliverQueuedTasksToAgent(
Payload: task.Payload,
}
applyHibernationHandoffToAssignment(assignment, task.Metadata)
+ applyTaskAuthorizationToAssignment(assignment, task)
if sendErr := client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_TaskAssignment{
@@ -316,6 +319,54 @@ const idemTaskTTL = 24 * time.Hour
// task_id once creation succeeds.
const idemTaskPlaceholder = "pending"
+const createTaskParentDenied = "parent task not found or not authorized"
+
+// resolveCreateTaskParent turns an optional wire parent_task_id into a native
+// task parent. Connection-associated parentage remains the zero-configuration
+// path. An explicit parent is a request-scoped execution binding for long-lived
+// workers and is accepted only for the exact assigned identity while the parent
+// is assigned or running. Every failure uses one info-hiding error.
+func (s *GatewayServer) resolveCreateTaskParent(
+ ctx context.Context,
+ client *ClientSession,
+ identity models.Identity,
+ workspace string,
+ requested string,
+) (string, *tasks.Task, error) {
+ requested = strings.TrimSpace(requested)
+ associated := ""
+ if client != nil {
+ associated = client.AssociatedTaskID
+ }
+ if requested == "" {
+ if associated == "" || s.taskStore == nil {
+ return associated, nil, nil
+ }
+ parent, err := s.taskStore.GetTask(ctx, associated)
+ if err != nil {
+ // Preserve the historical connection-associated behavior. The task
+ // service/store remains responsible for rejecting an invalid native
+ // parent; the lookup here is only for correlation inheritance.
+ return associated, nil, nil
+ }
+ return associated, parent, nil
+ }
+ if s.taskStore == nil {
+ return "", nil, stderrors.New(createTaskParentDenied)
+ }
+ parent, err := s.taskStore.GetTask(ctx, requested)
+ if err != nil || parent == nil {
+ return "", nil, stderrors.New(createTaskParentDenied)
+ }
+ if parent.Workspace != workspace || parent.AssignedTo != identity.String() {
+ return "", nil, stderrors.New(createTaskParentDenied)
+ }
+ if parent.Status != tasks.TaskStatusAssigned && parent.Status != tasks.TaskStatusRunning {
+ return "", nil, stderrors.New(createTaskParentDenied)
+ }
+ return requested, parent, nil
+}
+
// handleCreateTask processes CreateTaskRequest messages
func (s *GatewayServer) handleCreateTask(
ctx context.Context,
@@ -344,6 +395,17 @@ func (s *GatewayServer) handleCreateTask(
// Extract correlation ID for optional response path.
requestID := req.GetRequestId()
+ originatingScheduleID := strings.TrimSpace(req.GetOriginatingScheduleId())
+ if originatingScheduleID != "" && identity.Type != models.PrincipalWorkflowEngine {
+ errMsg := "originating_schedule_id is reserved for the authenticated WorkflowEngine"
+ sendClientError(client, "ERR_INVALID_ARGUMENT", errMsg)
+ if requestID != "" {
+ _ = client.SafeSend(&pb.DownstreamMessage{Payload: &pb.DownstreamMessage_CreateTask{CreateTask: &pb.CreateTaskResponse{
+ Success: false, ErrorCode: "ERR_INVALID_ARGUMENT", ErrorMessage: errMsg, RequestId: requestID,
+ }}})
+ }
+ return nil
+ }
// mintedTaskToken is populated by maybeIssueTaskToken below (only on the
// success path, only when the caller passed target_identity AND the
@@ -381,6 +443,12 @@ func (s *GatewayServer) handleCreateTask(
},
})
}
+ if req.GetRequiredDownstreamAuthorityHops() > 1 {
+ errMsg := "required_downstream_authority_hops currently supports only 0 or 1"
+ sendClientError(client, "ERR_INVALID_ARGUMENT", errMsg)
+ sendCreateTaskResponse(false, "", "", "ERR_INVALID_ARGUMENT", errMsg, "")
+ return nil
+ }
if s.orchestration == nil || s.orchestration.TaskService == nil {
s.logTaskCreateAudit(ctx, identity, client.SessionUUID, taskWorkspace, "", false, "orchestration task assignment not enabled", buildTaskCreateAuditMetadata(req, "", taskWorkspace), nil)
@@ -443,25 +511,57 @@ func (s *GatewayServer) handleCreateTask(
return nil
}
- resolvedAuthority, err := s.resolveAuthorizationContext(ctx, client, identity, req.GetAuthorization())
+ parentTaskID, parentTask, err := s.resolveCreateTaskParent(ctx, client, identity, taskWorkspace, req.GetParentTaskId())
+ if err != nil {
+ s.logTaskCreateAudit(ctx, identity, client.SessionUUID, taskWorkspace, "", false, createTaskParentDenied, buildTaskCreateAuditMetadata(req, assignmentMode, taskWorkspace), nil)
+ sendClientError(client, "ERR_PERMISSION_DENIED", createTaskParentDenied)
+ sendCreateTaskResponse(false, "", "", "ERR_PERMISSION_DENIED", createTaskParentDenied, "")
+ return nil
+ }
+
+ var resolvedAuthority *acl.ResolvedAuthority
+ if originatingScheduleID != "" {
+ resolvedAuthority, err = s.resolveAuthorizationContextForAudience(ctx, client, identity, req.GetAuthorization(), parentTaskID, originatingScheduleID)
+ } else {
+ resolvedAuthority, err = s.resolveAuthorizationContextForTask(ctx, client, identity, req.GetAuthorization(), parentTaskID)
+ }
if err != nil {
s.logTaskCreateAudit(ctx, identity, client.SessionUUID, taskWorkspace, "", false, "invalid authorization context: "+err.Error(), buildTaskCreateAuditMetadata(req, assignmentMode, taskWorkspace), nil)
- sendClientError(client, "ERR_PERMISSION_DENIED", "invalid authorization context")
- sendCreateTaskResponse(false, "", "", "ERR_PERMISSION_DENIED", "invalid authorization context", "")
+ errorCode := "ERR_PERMISSION_DENIED"
+ if originatingScheduleID != "" {
+ errorCode = "ERR_AUTHORITY_INVALID"
+ }
+ sendClientError(client, errorCode, "invalid authorization context")
+ sendCreateTaskResponse(false, "", "", errorCode, "invalid authorization context", "")
return nil
}
+ if originatingScheduleID != "" && resolvedAuthority != nil {
+ if err := s.validateWorkflowScheduleSourceAuthority(ctx, resolvedAuthority.Grant); err != nil {
+ s.logTaskCreateAudit(ctx, identity, client.SessionUUID, taskWorkspace, "", false, "invalid schedule authority source: "+err.Error(), buildTaskCreateAuditMetadata(req, assignmentMode, taskWorkspace), resolvedAuthority)
+ sendClientError(client, "ERR_AUTHORITY_INVALID", "workflow schedule authority is no longer valid")
+ sendCreateTaskResponse(false, "", "", "ERR_AUTHORITY_INVALID", "workflow schedule authority is no longer valid", "")
+ return nil
+ }
+ }
// Nested task creation: when an agent currently delivering a task under a
// task-bound authority grant calls CreateTask without an explicit
// AuthorizationContext, auto-derive from its task grant so the new task
// inherits the subject, root subject, and grant lineage.
if resolvedAuthority == nil {
- inherited, inheritedErr := s.loadCallerTaskAuthority(ctx, client, identity)
+ inherited, inheritedErr := s.loadTaskAuthorityForActor(ctx, parentTaskID, identity)
if inheritedErr != nil {
logging.Logger.Warn().Err(inheritedErr).Str("identity", identity.String()).Msg("failed to load caller task authority for nested CreateTask")
}
resolvedAuthority = inherited
}
+ if req.GetRequiredDownstreamAuthorityHops() > 0 && resolvedAuthority == nil {
+ errMsg := "required downstream authority hops require on-behalf-of task authority"
+ s.logTaskCreateAudit(ctx, identity, client.SessionUUID, taskWorkspace, "", false, errMsg, buildTaskCreateAuditMetadata(req, assignmentMode, taskWorkspace), nil)
+ sendClientError(client, "ERR_AUTHORITY_REQUIRED", errMsg)
+ sendCreateTaskResponse(false, "", "", "ERR_AUTHORITY_REQUIRED", errMsg, "")
+ return nil
+ }
// The WorkflowEngine is a system principal whose core function is to create
// tasks in response to events, in any workspace it routes for. It holds no
@@ -501,24 +601,44 @@ func (s *GatewayServer) handleCreateTask(
}
// Create task request
- metadata = applyResolvedAuthorityToTaskMetadata(metadata, resolvedAuthority)
+ // A schedule grant is a private transport credential. The derived task grant
+ // is persisted later by establishTaskAuthorityGrant, but the source schedule
+ // grant must never be copied into caller-visible task metadata.
+ if originatingScheduleID == "" {
+ metadata = applyResolvedAuthorityToTaskMetadata(metadata, resolvedAuthority)
+ }
+ correlationID := req.GetCorrelationId()
+ rootTaskID := req.GetRootTaskId()
+ if parentTask != nil {
+ if correlationID == "" {
+ correlationID = parentTask.CorrelationID
+ }
+ if rootTaskID == "" {
+ rootTaskID = parentTask.RootTaskID
+ if rootTaskID == "" {
+ rootTaskID = parentTask.TaskID
+ }
+ }
+ }
taskReq := &orchestration.CreateTaskRequest{
- TaskType: req.TaskType,
- TaskClass: int32(req.TaskClass),
- Workspace: taskWorkspace,
- AssignmentMode: assignmentMode,
- TargetAgentID: req.TargetAgentId,
- TargetImplementation: req.TargetImplementation,
- LaunchParamOverrides: launchParamOverrides,
- Metadata: metadata,
- Payload: req.Payload,
- CreatorIdentity: identity,
- ParentTaskID: client.AssociatedTaskID,
- RetryPolicy: retryPolicyFromProto(req.GetRetryPolicy()),
- Priority: int32(req.GetPriority()),
- CorrelationID: req.GetCorrelationId(),
- RootTaskID: req.GetRootTaskId(),
- CompletionEvent: completionConfigFromProto(req.GetCompletionEvent()),
+ TaskType: req.TaskType,
+ TaskClass: int32(req.TaskClass),
+ Workspace: taskWorkspace,
+ AssignmentMode: assignmentMode,
+ TargetAgentID: req.TargetAgentId,
+ TargetImplementation: req.TargetImplementation,
+ LaunchParamOverrides: launchParamOverrides,
+ Metadata: metadata,
+ Payload: req.Payload,
+ CreatorIdentity: identity,
+ ParentTaskID: parentTaskID,
+ RetryPolicy: retryPolicyFromProto(req.GetRetryPolicy()),
+ Priority: int32(req.GetPriority()),
+ CorrelationID: correlationID,
+ RootTaskID: rootTaskID,
+ CompletionEvent: completionConfigFromProto(req.GetCompletionEvent()),
+ TargetOfflinePolicy: orchestration.TargetOfflinePolicy(req.GetTargetOfflinePolicy()),
+ RequiredDownstreamAuthorityHops: int(req.GetRequiredDownstreamAuthorityHops()),
}
// Fix AA: seed the task's Authority.SubjectType/SubjectID from the resolved
// OBO subject so downstream consumers (buildTaskContext →
@@ -661,6 +781,13 @@ func (s *GatewayServer) handleCreateTask(
// Get target client session
if targetClient := s.getClientByIdentity(targetIdentity); targetClient != nil {
+ assignedTask, taskErr := s.taskStore.GetTask(ctx, response.TaskID)
+ if taskErr != nil {
+ _ = s.orchestration.TaskService.CancelTask(ctx, response.TaskID)
+ sendClientError(client, "ERR_TASK_CREATE_FAILED", "unable to load assigned task authority")
+ sendCreateTaskResponse(false, "", "", "ERR_TASK_CREATE_FAILED", "unable to load assigned task authority", "")
+ return taskErr
+ }
assignment := &pb.TaskAssignment{
TaskId: response.TaskID,
TaskType: req.TaskType,
@@ -671,6 +798,7 @@ func (s *GatewayServer) handleCreateTask(
Payload: req.Payload,
}
applyHibernationHandoffToAssignment(assignment, taskReq.Metadata)
+ applyTaskAuthorizationToAssignment(assignment, assignedTask)
err := targetClient.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_TaskAssignment{
@@ -707,6 +835,15 @@ func buildTaskCreateAuditMetadata(req *pb.CreateTaskRequest, assignmentMode, wor
if req.TargetImplementation != "" {
metadata["target_implementation"] = req.TargetImplementation
}
+ if req.ParentTaskId != "" {
+ metadata["parent_task_id"] = req.ParentTaskId
+ }
+ if req.GetRequiredDownstreamAuthorityHops() > 0 {
+ metadata["required_downstream_authority_hops"] = req.GetRequiredDownstreamAuthorityHops()
+ }
+ if req.GetOriginatingScheduleId() != "" {
+ metadata["originating_schedule_id"] = req.GetOriginatingScheduleId()
+ }
if len(req.LaunchParamOverrides) > 0 {
metadata["launch_param_overrides"] = len(req.LaunchParamOverrides)
}
@@ -964,6 +1101,31 @@ func applyHibernationHandoffToAssignment(assignment *pb.TaskAssignment, metadata
}
}
+// applyTaskAuthorizationToAssignment projects the authoritative, assignee-
+// bound task grant onto the typed assignment surface. It deliberately reads
+// Task.Authority rather than the metadata mirror, which is retained only for
+// audit and backward compatibility.
+func applyTaskAuthorizationToAssignment(assignment *pb.TaskAssignment, task *tasks.ExtendedTask) {
+ if assignment == nil || task == nil {
+ return
+ }
+ authority := task.Authority
+ if authority.AuthorityGrantID == "" && authority.SubjectType == "" && authority.SubjectID == "" {
+ return
+ }
+ authorization := &pb.AuthorizationContext{
+ AuthorityMode: authority.Mode,
+ GrantId: authority.AuthorityGrantID,
+ }
+ if authority.SubjectType != "" || authority.SubjectID != "" {
+ authorization.Subject = &pb.PrincipalRef{
+ PrincipalType: authority.SubjectType,
+ PrincipalId: authority.SubjectID,
+ }
+ }
+ assignment.Authorization = authorization
+}
+
// configureOrchestratorDispatcher sets up the callback for the orchestrator task dispatcher
func (s *GatewayServer) configureOrchestratorDispatcher() {
if s.orchestration == nil || s.orchestration.Dispatcher == nil {
@@ -1359,6 +1521,7 @@ func (s *GatewayServer) deliverPoolTaskToWorker(ctx context.Context, taskID, tar
Payload: payload,
}
applyHibernationHandoffToAssignment(assignment, task.Metadata)
+ applyTaskAuthorizationToAssignment(assignment, task)
err = worker.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_TaskAssignment{
diff --git a/server/internal/gateway/orchestration_parent_task_test.go b/server/internal/gateway/orchestration_parent_task_test.go
new file mode 100644
index 0000000..d2ed4f7
--- /dev/null
+++ b/server/internal/gateway/orchestration_parent_task_test.go
@@ -0,0 +1,161 @@
+package gateway
+
+import (
+ "context"
+ "testing"
+
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/pkg/models"
+ "github.com/scitrera/aether/server/pkg/tasks"
+)
+
+func createParentTask(t *testing.T, s *GatewayServer, parent *tasks.Task) {
+ t.Helper()
+ status := parent.Status
+ assignee := parent.AssignedTo
+ parent.Status = tasks.TaskStatusPending
+ parent.AssignedTo = ""
+ if err := s.taskStore.CreateTask(context.Background(), parent); err != nil {
+ t.Fatalf("CreateTask(parent): %v", err)
+ }
+ if assignee != "" {
+ if err := s.taskStore.AssignTask(context.Background(), parent.TaskID, assignee); err != nil {
+ t.Fatalf("AssignTask(parent): %v", err)
+ }
+ }
+ if status == tasks.TaskStatusRunning || tasks.IsTerminal(status) {
+ if err := s.taskStore.StartTask(context.Background(), parent.TaskID); err != nil {
+ t.Fatalf("StartTask(parent): %v", err)
+ }
+ }
+ if status == tasks.TaskStatusCompleted {
+ if err := s.taskStore.CompleteTask(context.Background(), parent.TaskID); err != nil {
+ t.Fatalf("CompleteTask(parent): %v", err)
+ }
+ }
+}
+
+func createTaskResponse(stream *mockStream) *pb.CreateTaskResponse {
+ stream.mu.Lock()
+ defer stream.mu.Unlock()
+ for _, message := range stream.sent {
+ if response := message.GetCreateTask(); response != nil {
+ return response
+ }
+ }
+ return nil
+}
+
+func TestHandleCreateTaskExplicitParentUsesValidatedAssignee(t *testing.T) {
+ s, _, cleanup := newIdemTestServer(t)
+ defer cleanup()
+
+ worker := callerIdentity("worker", "alice")
+ createParentTask(t, s, &tasks.Task{
+ TaskID: "parent-1",
+ TaskType: "chat_message",
+ Workspace: "ws1",
+ Status: tasks.TaskStatusRunning,
+ AssignmentMode: tasks.AssignmentModeTargeted,
+ AssignedTo: worker.String(),
+ CorrelationID: "conversation-1",
+ RootTaskID: "root-1",
+ })
+
+ stream := &mockStream{}
+ client := newTaskTestClient(stream, worker)
+ // A long-lived worker may retain a different startup-task association.
+ // Explicit parentage is request-scoped and must still select parent-1.
+ client.AssociatedTaskID = "startup-task"
+ request := &pb.CreateTaskRequest{
+ TaskType: "child",
+ Workspace: "ws1",
+ AssignmentMode: pb.TaskAssignmentMode_SELF_ASSIGN,
+ ParentTaskId: "parent-1",
+ RequestId: "create-child",
+ }
+ if err := s.handleCreateTask(context.Background(), client, worker, request); err != nil {
+ t.Fatalf("handleCreateTask: %v", err)
+ }
+ response := createTaskResponse(stream)
+ if response == nil || !response.Success || response.TaskId == "" {
+ t.Fatalf("CreateTaskResponse = %+v", response)
+ }
+ child, err := s.taskStore.GetTask(context.Background(), response.TaskId)
+ if err != nil {
+ t.Fatalf("GetTask(child): %v", err)
+ }
+ if child.ParentTaskID != "parent-1" {
+ t.Fatalf("ParentTaskID = %q", child.ParentTaskID)
+ }
+ if child.RootTaskID != "root-1" || child.CorrelationID != "conversation-1" {
+ t.Fatalf("inherited coordination = root:%q correlation:%q", child.RootTaskID, child.CorrelationID)
+ }
+}
+
+func TestHandleCreateTaskExplicitParentFailsClosed(t *testing.T) {
+ tests := []struct {
+ name string
+ caller models.Identity
+ parent *tasks.Task
+ workspace string
+ parentID string
+ }{
+ {
+ name: "missing",
+ caller: callerIdentity("worker", "alice"),
+ parent: nil, workspace: "ws1", parentID: "missing-parent",
+ },
+ {
+ name: "different assignee",
+ caller: callerIdentity("worker", "alice"),
+ parent: &tasks.Task{TaskID: "parent-other", TaskType: "chat", Workspace: "ws1", Status: tasks.TaskStatusRunning, AssignmentMode: tasks.AssignmentModeTargeted, AssignedTo: callerIdentity("worker", "bob").String()},
+ workspace: "ws1", parentID: "parent-other",
+ },
+ {
+ name: "terminal",
+ caller: callerIdentity("worker", "alice"),
+ parent: &tasks.Task{TaskID: "parent-done", TaskType: "chat", Workspace: "ws1", Status: tasks.TaskStatusCompleted, AssignmentMode: tasks.AssignmentModeTargeted, AssignedTo: callerIdentity("worker", "alice").String()},
+ workspace: "ws1", parentID: "parent-done",
+ },
+ {
+ name: "cross workspace",
+ caller: callerIdentity("worker", "alice"),
+ parent: &tasks.Task{TaskID: "parent-ws2", TaskType: "chat", Workspace: "ws2", Status: tasks.TaskStatusRunning, AssignmentMode: tasks.AssignmentModeTargeted, AssignedTo: callerIdentity("worker", "alice").String()},
+ workspace: "ws1", parentID: "parent-ws2",
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ s, _, cleanup := newIdemTestServer(t)
+ defer cleanup()
+ if test.parent != nil {
+ createParentTask(t, s, test.parent)
+ }
+ stream := &mockStream{}
+ client := newTaskTestClient(stream, test.caller)
+ request := &pb.CreateTaskRequest{
+ TaskType: "child",
+ Workspace: test.workspace,
+ AssignmentMode: pb.TaskAssignmentMode_SELF_ASSIGN,
+ ParentTaskId: test.parentID,
+ RequestId: "denied-child",
+ }
+ if err := s.handleCreateTask(context.Background(), client, test.caller, request); err != nil {
+ t.Fatalf("handleCreateTask: %v", err)
+ }
+ response := createTaskResponse(stream)
+ if response == nil || response.Success || response.ErrorCode != "ERR_PERMISSION_DENIED" || response.ErrorMessage != createTaskParentDenied {
+ t.Fatalf("CreateTaskResponse = %+v", response)
+ }
+ wantTasks := 0
+ if test.parent != nil && test.parent.Workspace == test.workspace {
+ wantTasks = 1
+ }
+ if got := countWorkspaceTasks(t, s, test.workspace); got != wantTasks {
+ t.Fatalf("workspace tasks after denied create = %d, want %d", got, wantTasks)
+ }
+ })
+ }
+}
diff --git a/server/internal/gateway/proxy_routing_test.go b/server/internal/gateway/proxy_routing_test.go
index 98bb48a..64660d3 100644
--- a/server/internal/gateway/proxy_routing_test.go
+++ b/server/internal/gateway/proxy_routing_test.go
@@ -10,7 +10,10 @@ package gateway
import (
"context"
+ "database/sql"
"errors"
+ "fmt"
+ "path/filepath"
"strings"
"sync/atomic"
"testing"
@@ -18,8 +21,11 @@ import (
"github.com/google/uuid"
pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
"github.com/scitrera/aether/server/internal/circuitbreaker"
+ aclsqlite "github.com/scitrera/aether/server/internal/storage/acl/sqlite"
"github.com/scitrera/aether/server/pkg/models"
+ _ "modernc.org/sqlite"
)
// ---------------------------------------------------------------------------
@@ -46,6 +52,27 @@ func newProxyClient(identity models.Identity, stream *mockStream) *ClientSession
return c
}
+func installProxyACLStore(t *testing.T, s *GatewayServer) *aclsqlite.Store {
+ t.Helper()
+ dbPath := filepath.Join(t.TempDir(), "proxy-acl.db")
+ db, err := sql.Open("sqlite", fmt.Sprintf("file:%s?_journal_mode=WAL&_busy_timeout=5000", dbPath))
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ db.SetMaxOpenConns(1)
+ store, err := aclsqlite.New(db, nil, nil, "proxy-test")
+ if err != nil {
+ _ = db.Close()
+ t.Fatalf("aclsqlite.New: %v", err)
+ }
+ t.Cleanup(func() {
+ _ = store.Close()
+ _ = db.Close()
+ })
+ s.acl = store
+ return store
+}
+
// ---------------------------------------------------------------------------
// 1. Basic REST route success
// ---------------------------------------------------------------------------
@@ -75,6 +102,109 @@ func TestRouteProxyHttpRequest_Success_PublishesToConcreteTopic(t *testing.T) {
}
}
+func TestRouteProxyHttpRequest_CheckedAccessStampsConcreteReceipt(t *testing.T) {
+ router := newMockMessageRouter()
+ s := newProxyTestServer(router)
+ store := installProxyACLStore(t, s)
+ stream := &mockStream{}
+ sender := models.Identity{Type: models.PrincipalAgent, Workspace: "ws1", Implementation: "caller", Specifier: "v1"}
+ client := newProxyClient(sender, stream)
+ principalID := sender.CanonicalPrincipalID()
+ principalType := acl.PrincipalTypeForModel(sender.Type)
+ if _, err := store.GrantAccess(context.Background(), principalType, principalID, acl.ResourceTypeWorkspace, "ws1", acl.AccessReadWrite, "test", "route", nil); err != nil {
+ t.Fatalf("GrantAccess(workspace): %v", err)
+ }
+ resourceID := "workspaces/ws1/entries/ref-1"
+ if _, err := store.GrantAccess(context.Background(), principalType, principalID, "vfs", resourceID, acl.AccessRead, "test", "entry", nil); err != nil {
+ t.Fatalf("GrantAccess(vfs): %v", err)
+ }
+ if decision, err := store.CheckAccess(context.Background(), sender, "vfs", resourceID, "read", "ws1", client.SessionUUID, acl.AccessRead); err != nil || decision.Denied() {
+ t.Fatalf("preflight VFS access decision=%+v err=%v", decision, err)
+ }
+
+ req := &pb.ProxyHttpRequest{
+ RequestId: "req-checked",
+ TargetTopic: "sv::data-connectors::pod-a",
+ Method: "GET",
+ Path: "/v1/vfs/ref-1",
+ CheckedAccess: &pb.ResourceAccessRequest{
+ ResourceType: "vfs", ResourceId: resourceID, Operation: "read",
+ Workspace: "ws1", RequiredAccessLevel: int32(acl.AccessRead), CorrelationId: "req-checked",
+ },
+ AccessReceipt: &pb.AccessDecisionReceipt{DecisionId: "forged"},
+ }
+ s.routeProxyEnvelope(context.Background(), client, proxyEnvelope{httpReq: req})
+
+ router.mu.Lock()
+ defer router.mu.Unlock()
+ if len(router.publishedMessages) != 1 {
+ stream.mu.Lock()
+ defer stream.mu.Unlock()
+ t.Fatalf("expected one publish, got %d; responses=%+v", len(router.publishedMessages), stream.sent)
+ }
+ delivered := unwrapProxyDownstream(t, router.publishedMessages[0].payload).GetProxyHttpRequest()
+ receipt := delivered.GetAccessReceipt()
+ if receipt == nil || !receipt.GetAllowed() || receipt.GetDecisionId() == "forged" {
+ t.Fatalf("missing gateway-authored allow receipt: %+v", receipt)
+ }
+ if receipt.GetDeliveryTarget() != "sv::data-connectors::pod-a" {
+ t.Fatalf("delivery_target = %q", receipt.GetDeliveryTarget())
+ }
+ if receipt.GetRequest().GetResourceId() != resourceID {
+ t.Fatalf("receipt resource = %+v", receipt.GetRequest())
+ }
+}
+
+func TestRouteProxyHttpRequest_CheckedAccessDeniedDoesNotPublish(t *testing.T) {
+ router := newMockMessageRouter()
+ s := newProxyTestServer(router)
+ store := installProxyACLStore(t, s)
+ stream := &mockStream{}
+ sender := models.Identity{Type: models.PrincipalAgent, Workspace: "ws1", Implementation: "caller", Specifier: "v1"}
+ client := newProxyClient(sender, stream)
+ if _, err := store.GrantAccess(context.Background(), acl.PrincipalTypeForModel(sender.Type), sender.CanonicalPrincipalID(), acl.ResourceTypeWorkspace, "ws1", acl.AccessReadWrite, "test", "route", nil); err != nil {
+ t.Fatalf("GrantAccess(workspace): %v", err)
+ }
+
+ req := &pb.ProxyHttpRequest{
+ RequestId: "req-denied",
+ TargetTopic: "sv::data-connectors::pod-a",
+ CheckedAccess: &pb.ResourceAccessRequest{
+ ResourceType: "vfs", ResourceId: "workspaces/ws1/entries/ref-1", Operation: "read",
+ Workspace: "ws1", RequiredAccessLevel: int32(acl.AccessRead), CorrelationId: "req-denied",
+ },
+ }
+ s.routeProxyEnvelope(context.Background(), client, proxyEnvelope{httpReq: req})
+
+ if len(router.publishedMessages) != 0 {
+ t.Fatalf("checked denial published %d messages", len(router.publishedMessages))
+ }
+ stream.mu.Lock()
+ defer stream.mu.Unlock()
+ if len(stream.sent) != 1 || stream.sent[0].GetProxyHttpResponse().GetError().GetKind() != pb.ProxyError_ACL_DENIED {
+ t.Fatalf("expected ACL_DENIED proxy response, got %+v", stream.sent)
+ }
+}
+
+func TestRouteProxyHttpRequest_ClearsReceiptWithoutCheckedAccess(t *testing.T) {
+ router := newMockMessageRouter()
+ s := newProxyTestServer(router)
+ client := newProxyClient(models.Identity{Type: models.PrincipalAgent, Workspace: "ws1"}, &mockStream{})
+ req := &pb.ProxyHttpRequest{
+ RequestId: "req-forged", TargetTopic: "sv::svc::one",
+ AccessReceipt: &pb.AccessDecisionReceipt{DecisionId: "forged", Allowed: true},
+ }
+ s.routeProxyEnvelope(context.Background(), client, proxyEnvelope{httpReq: req})
+
+ if len(router.publishedMessages) != 1 {
+ t.Fatalf("expected one publish, got %d", len(router.publishedMessages))
+ }
+ delivered := unwrapProxyDownstream(t, router.publishedMessages[0].payload).GetProxyHttpRequest()
+ if delivered.GetAccessReceipt() != nil {
+ t.Fatalf("caller-supplied receipt survived: %+v", delivered.GetAccessReceipt())
+ }
+}
+
func TestRouteProxyHttpRequest_PayloadTooLarge_ReturnsError(t *testing.T) {
router := newMockMessageRouter()
s := newProxyTestServer(router)
diff --git a/server/internal/gateway/routing.go b/server/internal/gateway/routing.go
index 1302d26..ca92fb0 100644
--- a/server/internal/gateway/routing.go
+++ b/server/internal/gateway/routing.go
@@ -365,6 +365,58 @@ func (s *GatewayServer) routeMessage(ctx context.Context, client *ClientSession,
return
}
+ // 0b.1 Optional exact logical-resource authorization. This is additive to
+ // the route ACL above: reaching a provider topic does not imply permission
+ // to invoke every tool or bind every execution view behind it. In OBO mode
+ // this check evaluates the subject intersected with the validated grant and
+ // deliberately does not use the route check's actor-first fallback.
+ var accessReceipt *pb.AccessDecisionReceipt
+ if checked := msg.GetCheckedAccess(); checked != nil {
+ if validateErr := validateResourceAccessRequest(checked); validateErr != nil {
+ logging.Logger.Warn().Str("from", sender.ToTopic()).Str("to", msg.TargetTopic).Err(validateErr).Msg("invalid checked message access request")
+ messageErrors.WithLabelValues(sender.Workspace, "checked_access_invalid").Inc()
+ sendClientError(client, "ERR_INVALID_ACCESS_REQUEST", validateErr.Error())
+ return
+ }
+ accessReceipt, err = evaluateResourceAccess(ctx, s.acl, sender, resolvedAuthority, sessionUUID, checked, msg.TargetTopic, time.Now())
+ if err != nil {
+ logging.Logger.Warn().Str("from", sender.ToTopic()).Str("to", msg.TargetTopic).Err(err).Msg("checked message access evaluation failed")
+ messageErrors.WithLabelValues(sender.Workspace, "checked_access_failed").Inc()
+ sendClientError(client, "ERR_AUTHORIZATION_UNAVAILABLE", "checked access evaluation unavailable", withRetryable(true))
+ return
+ }
+ if !accessReceipt.GetAllowed() {
+ logging.Logger.Warn().Str("from", sender.ToTopic()).Str("to", msg.TargetTopic).Str("resource_type", checked.GetResourceType()).Str("resource_id", checked.GetResourceId()).Msg("checked message access denied")
+ messageErrors.WithLabelValues(sender.Workspace, "checked_access_denied").Inc()
+ sendClientError(client, "ERR_PERMISSION_DENIED", "not authorized for checked logical resource")
+ return
+ }
+ }
+
+ // Authority continuation is explicit and fail-closed. At this point the
+ // route target is concrete, the sender's OBO context has been validated,
+ // and both the route and optional exact-resource checks have passed.
+ var forwardedAuthorization *pb.ForwardedAuthorization
+ if continuation := msg.GetAuthorityContinuation(); continuation != nil {
+ forwardedAuthorization, err = s.deriveMessageAuthorityContinuation(
+ ctx, resolvedAuthority, msg.TargetTopic, continuation, accessReceipt, sessionUUID,
+ )
+ if err != nil {
+ logging.Logger.Warn().Str("from", sender.ToTopic()).Str("to", msg.TargetTopic).Err(err).Msg("message authority continuation denied")
+ messageErrors.WithLabelValues(sender.Workspace, "authority_continuation_denied").Inc()
+ event := audit.NewMessageEvent(string(sender.Type), sender.String(), audit.OpMessageRouteFailed, msg.TargetTopic, sender.Workspace, sessionUUID, false, err.Error(), map[string]interface{}{
+ "from": sender.ToTopic(),
+ "to": msg.TargetTopic,
+ "message_type": msg.MessageType.String(),
+ "denied_reason": "authority_continuation_denied",
+ })
+ applyResolvedAuthorityToAuditEvent(event, resolvedAuthority)
+ s.auditLog(ctx, event)
+ sendClientError(client, "ERR_AUTHORITY_CONTINUATION_DENIED", "unable to forward authorization to message recipient")
+ return
+ }
+ }
+
// 0c. Metric negative-delta authorization. Runs after authority resolution
// so on-behalf-of grants (subject's capability/metric_credit) are honored, and
// so the rejection audit row carries full authority lineage.
@@ -473,10 +525,13 @@ func (s *GatewayServer) routeMessage(ctx context.Context, client *ClientSession,
effectiveWorkspace = sender.Workspace
}
envelope := &pb.MessageEnvelope{
- Source: sender.ToTopic(),
- Payload: msg.Payload,
- MessageType: msg.MessageType,
- TimestampMs: now.UnixMilli(),
+ Source: sender.ToTopic(),
+ Payload: msg.Payload,
+ MessageType: msg.MessageType,
+ TimestampMs: now.UnixMilli(),
+ Workspace: effectiveWorkspace,
+ AccessReceipt: accessReceipt,
+ ForwardedAuthorization: forwardedAuthorization,
}
if effectiveWorkspace != "" {
// Always allocate the map only when we have data — avoids inflating
@@ -1533,6 +1588,51 @@ func protoTaskStatusToTasks(s pb.TaskStatus) tasks.TaskStatus {
}
}
+// appendProtoTaskStatusFilter expands the coarser wire status projection back
+// to every persisted state that taskStatusToProto maps to it. Filter semantics
+// must round-trip the public projection: QUEUED includes pending, assigned, and
+// starting; FAILED includes failed and dead-letter tasks.
+func appendProtoTaskStatusFilter(dst []tasks.TaskStatus, status pb.TaskStatus) []tasks.TaskStatus {
+ var projected []tasks.TaskStatus
+ switch status {
+ case pb.TaskStatus_TASK_STATUS_QUEUED:
+ projected = []tasks.TaskStatus{
+ tasks.TaskStatusPending, tasks.TaskStatusAssigned, tasks.TaskStatusStarting,
+ }
+ case pb.TaskStatus_TASK_STATUS_FAILED:
+ projected = []tasks.TaskStatus{tasks.TaskStatusFailed, tasks.TaskStatusDLQ}
+ case pb.TaskStatus_TASK_STATUS_RUNNING,
+ pb.TaskStatus_TASK_STATUS_COMPLETED,
+ pb.TaskStatus_TASK_STATUS_CANCELLED,
+ pb.TaskStatus_TASK_STATUS_WAITING_INPUT,
+ pb.TaskStatus_TASK_STATUS_WAITING_AUTHORITY,
+ pb.TaskStatus_TASK_STATUS_WAITING_DEPENDENCY,
+ pb.TaskStatus_TASK_STATUS_HIBERNATED,
+ pb.TaskStatus_TASK_STATUS_REJECTED:
+ projected = []tasks.TaskStatus{protoTaskStatusToTasks(status)}
+ case pb.TaskStatus_TASK_STATUS_UNSPECIFIED:
+ return dst
+ default:
+ // Preserve the previous fail-closed behavior for an unknown concrete
+ // enum: include an impossible persisted status rather than silently
+ // broadening the query to every task.
+ projected = []tasks.TaskStatus{protoTaskStatusToTasks(status)}
+ }
+ for _, candidate := range projected {
+ seen := false
+ for _, existing := range dst {
+ if existing == candidate {
+ seen = true
+ break
+ }
+ }
+ if !seen {
+ dst = append(dst, candidate)
+ }
+ }
+ return dst
+}
+
// completionConfigFromProto converts the proto TaskCompletionEvent into the
// persisted model config. nil ⇒ nil (task did not opt into feed B). OnStatuses
// are mapped through the canonical proto↔model status converter.
@@ -1843,13 +1943,10 @@ func (s *GatewayServer) handleTaskQuery(ctx context.Context, client *ClientSessi
// Prefer repeated statuses over singular status
if len(query.Filter.Statuses) > 0 {
for _, s := range query.Filter.Statuses {
- if s != pb.TaskStatus_TASK_STATUS_UNSPECIFIED {
- filter.Statuses = append(filter.Statuses, protoTaskStatusToTasks(s))
- }
+ filter.Statuses = appendProtoTaskStatusFilter(filter.Statuses, s)
}
} else if query.Filter.Status != pb.TaskStatus_TASK_STATUS_UNSPECIFIED {
- status := protoTaskStatusToTasks(query.Filter.Status)
- filter.Status = &status
+ filter.Statuses = appendProtoTaskStatusFilter(filter.Statuses, query.Filter.Status)
}
filter.Workspace = query.Filter.Workspace
filter.TaskType = query.Filter.TaskType
@@ -1871,9 +1968,8 @@ func (s *GatewayServer) handleTaskQuery(ctx context.Context, client *ClientSessi
filter.CorrelationID = query.Filter.GetCorrelationId()
filter.RootTaskID = query.Filter.GetRootTaskId()
if len(query.Filter.ExcludeStatuses) > 0 {
- filter.ExcludeStatuses = make([]tasks.TaskStatus, 0, len(query.Filter.ExcludeStatuses))
for _, s := range query.Filter.ExcludeStatuses {
- filter.ExcludeStatuses = append(filter.ExcludeStatuses, protoTaskStatusToTasks(s))
+ filter.ExcludeStatuses = appendProtoTaskStatusFilter(filter.ExcludeStatuses, s)
}
}
// Phase 4 management-surface filters.
diff --git a/server/internal/gateway/routing_proxy.go b/server/internal/gateway/routing_proxy.go
index f8d966e..7cacce0 100644
--- a/server/internal/gateway/routing_proxy.go
+++ b/server/internal/gateway/routing_proxy.go
@@ -10,12 +10,12 @@ import (
"github.com/google/uuid"
pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/sdk/go/aether"
"github.com/scitrera/aether/server/internal/acl"
"github.com/scitrera/aether/server/internal/audit"
"github.com/scitrera/aether/server/internal/logging"
"github.com/scitrera/aether/server/pkg/identityheaders"
"github.com/scitrera/aether/server/pkg/models"
- "github.com/scitrera/aether/sdk/go/aether"
bp "github.com/scitrera/go-backpressure"
"google.golang.org/protobuf/proto"
)
@@ -172,6 +172,17 @@ func (s *GatewayServer) proxyACLCheck(ctx context.Context, client *ClientSession
if err != nil {
return nil, acl.AccessNone, err
}
+ // Match SendMessage semantics: an agent/task connection associated with a
+ // task may inherit that task's authority when it did not attach an explicit
+ // AuthorizationContext. This keeps route and exact-resource checks on the
+ // same authority path for checked proxy requests.
+ if resolved == nil && client != nil && client.AssociatedTaskID != "" {
+ if sender.Type == models.PrincipalAgent || sender.Type == models.PrincipalTask {
+ if autoAuth, autoErr := s.loadCallerMessageAuthority(ctx, client, sender); autoErr == nil && autoAuth != nil {
+ resolved = autoAuth
+ }
+ }
+ }
if resolved != nil {
level, checkErr := s.checkMessageSendWithAuthority(ctx, sender, target, client.SessionUUID, resolved)
return resolved, level, checkErr
@@ -343,6 +354,9 @@ func sendTunnelClose(client *ClientSession, tunnelID string, reason pb.TunnelClo
func (s *GatewayServer) routeProxyHttpRequest(ctx context.Context, client *ClientSession, sender models.Identity, req *pb.ProxyHttpRequest) {
requestID := req.GetRequestId()
target := req.GetTargetTopic()
+ // access_receipt is gateway-owned transport metadata. Clear it even when
+ // checked_access is absent so a caller can never forward a forged receipt.
+ req.AccessReceipt = nil
// 0. Body size cap.
maxBody := s.quotaEnforcer.getMaxRequestBodyBytes()
@@ -406,6 +420,30 @@ func (s *GatewayServer) routeProxyHttpRequest(ctx context.Context, client *Clien
req.Authorization.Resolved = grantToResolvedAuthorityInfo(resolvedAuthority.Grant)
}
+ // 2.6. Optional exact logical-resource authorization. This is additive to
+ // the route ACL: permission to reach a service does not grant access to
+ // every logical resource behind it. The receipt is bound to the resolved
+ // concrete delivery target and is delivered only on an allow decision.
+ if checked := req.GetCheckedAccess(); checked != nil {
+ accessReceipt, accessErr := evaluateResourceAccess(
+ ctx, s.acl, sender, resolvedAuthority, client.SessionUUID,
+ checked, concrete, time.Now(),
+ )
+ if accessErr != nil {
+ detail := fmt.Sprintf("checked access evaluation failed: %v", accessErr)
+ s.auditProxyHttpFailure(ctx, sender, concrete, requestID, client.SessionUUID, resolvedAuthority, detail)
+ sendProxyHttpError(client, requestID, pb.ProxyError_ACL_DENIED, detail)
+ return
+ }
+ if !accessReceipt.GetAllowed() {
+ detail := "checked logical-resource access denied"
+ s.auditProxyHttpFailure(ctx, sender, concrete, requestID, client.SessionUUID, resolvedAuthority, detail)
+ sendProxyHttpError(client, requestID, pb.ProxyError_ACL_DENIED, detail)
+ return
+ }
+ req.AccessReceipt = accessReceipt
+ }
+
// 3. Mint the canonical X-Auth-* trusted header set onto the envelope so
// a passthrough terminator (no Go sidecar to re-mint) can trust it
// directly. This is the single minting point — identityheaders is the
diff --git a/server/internal/gateway/routing_wildcard_test.go b/server/internal/gateway/routing_wildcard_test.go
index 6da833a..a7dabff 100644
--- a/server/internal/gateway/routing_wildcard_test.go
+++ b/server/internal/gateway/routing_wildcard_test.go
@@ -19,6 +19,7 @@ import (
pb "github.com/scitrera/aether/api/proto"
"github.com/scitrera/aether/server/internal/circuitbreaker"
"github.com/scitrera/aether/server/pkg/models"
+ "google.golang.org/protobuf/proto"
)
// newWildcardTestServer builds a GatewayServer for wildcard routing tests.
@@ -306,3 +307,57 @@ func TestRouteMessage_SvWildcard_EnvelopeCarriesConcreteTarget(t *testing.T) {
t.Errorf("published to %q, want %q", router.publishedMessages[0].topic, wantTopic)
}
}
+
+func TestRouteMessage_PayloadCannotSpoofForwardedAuthorization(t *testing.T) {
+ router := newMockMessageRouter()
+ s := newWildcardTestServer(router)
+ s.identityIndex.Store("sv::tool-catalog::pod-one", "session-one")
+ client := newWildcardClient(models.Identity{
+ Type: models.PrincipalAgent, Workspace: "ws1", Implementation: "caller", Specifier: "v1",
+ }, &mockStream{})
+
+ s.routeMessage(context.Background(), client, &pb.SendMessage{
+ TargetTopic: "sv::tool-catalog", MessageType: pb.MessageType_OPAQUE,
+ Payload: []byte(`{"forwarded_authorization":{"authorization":{"grant_id":"spoof"}}}`),
+ })
+ router.mu.Lock()
+ defer router.mu.Unlock()
+ if len(router.publishedMessages) != 1 {
+ t.Fatalf("expected one published message, got %d", len(router.publishedMessages))
+ }
+ var envelope pb.MessageEnvelope
+ if err := proto.Unmarshal(router.publishedMessages[0].payload, &envelope); err != nil {
+ t.Fatalf("unmarshal envelope: %v", err)
+ }
+ if envelope.GetForwardedAuthorization() != nil {
+ t.Fatal("application payload spoofed trusted forwarded authorization metadata")
+ }
+}
+
+func TestRouteMessage_AuthorityContinuationRequiresResolvedOBO(t *testing.T) {
+ router := newMockMessageRouter()
+ s := newWildcardTestServer(router)
+ s.identityIndex.Store("sv::tool-catalog::pod-one", "session-one")
+ stream := &mockStream{}
+ client := newWildcardClient(models.Identity{
+ Type: models.PrincipalAgent, Workspace: "ws1", Implementation: "caller", Specifier: "v1",
+ }, stream)
+
+ s.routeMessage(context.Background(), client, &pb.SendMessage{
+ TargetTopic: "sv::tool-catalog", MessageType: pb.MessageType_OPAQUE,
+ Payload: []byte("query"), AuthorityContinuation: &pb.AuthorityContinuationRequest{
+ ScopeMode: pb.AuthorityContinuationRequest_SCOPE_MODE_INHERIT_PARENT,
+ },
+ })
+ router.mu.Lock()
+ published := len(router.publishedMessages)
+ router.mu.Unlock()
+ if published != 0 {
+ t.Fatalf("direct send with continuation request published %d messages", published)
+ }
+ stream.mu.Lock()
+ defer stream.mu.Unlock()
+ if len(stream.sent) == 0 || stream.sent[0].GetError().GetCode() != "ERR_AUTHORITY_CONTINUATION_DENIED" {
+ t.Fatalf("expected ERR_AUTHORITY_CONTINUATION_DENIED, got %+v", stream.sent)
+ }
+}
diff --git a/server/internal/gateway/server.go b/server/internal/gateway/server.go
index 09afa21..d9fbb17 100644
--- a/server/internal/gateway/server.go
+++ b/server/internal/gateway/server.go
@@ -618,26 +618,17 @@ func (s *GatewayServer) doStop() {
if s.timerSeq != nil {
s.timerSeq.Stop()
}
- if s.acl != nil {
- s.acl.Close()
- logging.Logger.Info().Msg("ACL service stopped")
- }
- // Stop background cleanup jobs (reconciliation, task purge)
- if s.cleanupRunner != nil {
- s.cleanupRunner.Stop()
- logging.Logger.Info().Msg("cleanup service stopped")
- }
- // Clean up any pending workflow requests with error responses
+ // Clean up pending workflow requests before closing ACL storage so any
+ // provisional schedule grants are revoked rather than orphaned.
s.pendingWorkflowRequests.Range(func(key, value interface{}) bool {
pending, ok := value.(*pendingWorkflowRequest)
if ok {
requestID, _ := key.(string)
+ s.revokeProvisionalWorkflowGrantByID(context.Background(), pending.provisionalScheduleGrantID)
_ = pending.client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_WorkflowResponse{
WorkflowResponse: &pb.WorkflowResponse{
- Success: false,
- Error: "server shutting down",
- RequestId: requestID,
+ Success: false, Error: "server shutting down", RequestId: requestID,
},
},
})
@@ -645,6 +636,15 @@ func (s *GatewayServer) doStop() {
s.pendingWorkflowRequests.Delete(key)
return true
})
+ if s.acl != nil {
+ s.acl.Close()
+ logging.Logger.Info().Msg("ACL service stopped")
+ }
+ // Stop background cleanup jobs (reconciliation, task purge)
+ if s.cleanupRunner != nil {
+ s.cleanupRunner.Stop()
+ logging.Logger.Info().Msg("cleanup service stopped")
+ }
// orchestration cleanup
s.CleanupOrchestration()
}
diff --git a/server/internal/gateway/subscription.go b/server/internal/gateway/subscription.go
index f1c1736..4ee6bfb 100644
--- a/server/internal/gateway/subscription.go
+++ b/server/internal/gateway/subscription.go
@@ -8,10 +8,10 @@ import (
"unsafe"
pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/sdk/go/aether"
"github.com/scitrera/aether/server/internal/logging"
"github.com/scitrera/aether/server/internal/tracing"
"github.com/scitrera/aether/server/pkg/models"
- "github.com/scitrera/aether/sdk/go/aether"
bp "github.com/scitrera/go-backpressure"
"go.opentelemetry.io/otel/attribute"
"google.golang.org/protobuf/proto"
@@ -256,9 +256,12 @@ func (s *GatewayServer) createMessageHandler(client *ClientSession) func([]byte)
client.DeliverWithPriority(client.deriveDeliverCtx(), aether.PriorityRequest, &pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_Msg{
Msg: &pb.IncomingMessage{
- SourceTopic: parsed.env.Source,
- Payload: parsed.env.Payload,
- MessageType: parsed.env.MessageType,
+ SourceTopic: parsed.env.Source,
+ Payload: parsed.env.Payload,
+ MessageType: parsed.env.MessageType,
+ Workspace: parsed.env.GetWorkspace(),
+ AccessReceipt: parsed.env.GetAccessReceipt(),
+ ForwardedAuthorization: parsed.env.GetForwardedAuthorization(),
// Mirror the gateway-stamped OBO subject onto delivery so the
// recipient can identify the user the message was sent for.
OnBehalfSubject: parsed.env.GetOnBehalfSubject(),
diff --git a/server/internal/gateway/task_assignment_authorization_test.go b/server/internal/gateway/task_assignment_authorization_test.go
new file mode 100644
index 0000000..5ed4ef5
--- /dev/null
+++ b/server/internal/gateway/task_assignment_authorization_test.go
@@ -0,0 +1,43 @@
+package gateway
+
+import (
+ "testing"
+
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/pkg/tasks"
+)
+
+func TestApplyTaskAuthorizationToAssignmentUsesPersistedAuthority(t *testing.T) {
+ assignment := &pb.TaskAssignment{}
+ task := &tasks.ExtendedTask{
+ Authority: tasks.TaskAuthorityInfo{
+ Mode: "on_behalf_of",
+ SubjectType: "user",
+ SubjectID: "alice",
+ AuthorityGrantID: "grant-assignee",
+ },
+ Metadata: map[string]interface{}{
+ "authority_grant_id": "forged-metadata-grant",
+ "subject_id": "mallory",
+ },
+ }
+ applyTaskAuthorizationToAssignment(assignment, task)
+
+ if assignment.Authorization == nil {
+ t.Fatal("authorization was not projected")
+ }
+ if got := assignment.Authorization.GetGrantId(); got != "grant-assignee" {
+ t.Fatalf("grant id = %q", got)
+ }
+ if got := assignment.Authorization.GetSubject().GetPrincipalId(); got != "alice" {
+ t.Fatalf("subject id = %q", got)
+ }
+}
+
+func TestApplyTaskAuthorizationToAssignmentOmitsDirectTask(t *testing.T) {
+ assignment := &pb.TaskAssignment{}
+ applyTaskAuthorizationToAssignment(assignment, &tasks.ExtendedTask{})
+ if assignment.Authorization != nil {
+ t.Fatalf("authorization = %#v", assignment.Authorization)
+ }
+}
diff --git a/server/internal/gateway/task_authority.go b/server/internal/gateway/task_authority.go
index 3340726..f720e5e 100644
--- a/server/internal/gateway/task_authority.go
+++ b/server/internal/gateway/task_authority.go
@@ -285,7 +285,7 @@ func (s *GatewayServer) createTaskAuthorityGrant(
issuedBy models.Identity,
delegate models.Identity,
audienceType, audienceID, taskID, taskType, assignmentMode string,
- requireFurtherDelegation bool,
+ requiredRemainingHops int,
) (*acl.AuthorityGrant, error) {
if s.acl == nil {
return nil, fmt.Errorf("ACL service not available")
@@ -308,6 +308,12 @@ func (s *GatewayServer) createTaskAuthorityGrant(
remainingHops := authority.Grant.RemainingHops - 1
intermediaryReroot := false
if remainingHops < 0 {
+ // An explicit downstream-hop requirement must be attenuated from the
+ // caller's parent. Trusted intermediary re-rooting is intentionally not
+ // a substitute for capacity the request declared up front.
+ if requiredRemainingHops > 0 {
+ return nil, acl.ErrAuthorityGrantDelegationDenied
+ }
// Hop budget exhausted on the parent grant. Allow trusted intermediary
// services (sandbox-provider, etc. — anyone the operator has granted
// capability/authority_intermediary to) to re-root the new grant from the
@@ -332,8 +338,8 @@ func (s *GatewayServer) createTaskAuthorityGrant(
Str("task_id", taskID).
Msg("authority intermediary re-root: parent grant exhausted, minting fresh hop budget under capability/authority_intermediary")
}
- if requireFurtherDelegation && remainingHops < 1 {
- return nil, fmt.Errorf("task authority grant requires at least two remaining delegation hops")
+ if remainingHops < requiredRemainingHops {
+ return nil, fmt.Errorf("task authority grant leaves %d downstream delegation hops; %d required", remainingHops, requiredRemainingHops)
}
metadata := map[string]interface{}{
@@ -590,11 +596,11 @@ func (s *GatewayServer) establishTaskAuthorityGrant(
metadata := cloneTaskMetadata(taskReq.Metadata)
var (
- delegate models.Identity
- audienceType string
- audienceID string
- requireFurtherDelegates bool
- err error
+ delegate models.Identity
+ audienceType string
+ audienceID string
+ requiredRemainingHops = taskReq.RequiredDownstreamAuthorityHops
+ err error
)
switch taskReq.AssignmentMode {
@@ -602,7 +608,9 @@ func (s *GatewayServer) establishTaskAuthorityGrant(
delegate = taskGrantAnchorIdentity(taskID, taskReq.Workspace, taskReq.TaskType)
audienceType = acl.AuthorityAudienceTask
audienceID = taskID
- requireFurtherDelegates = true
+ // The pool anchor must still derive once to the selected assignee before
+ // that final execution identity receives its requested downstream budget.
+ requiredRemainingHops++
case "targeted":
delegate, err = models.ParseIdentity(taskReq.TargetAgentID)
if err != nil {
@@ -648,7 +656,7 @@ func (s *GatewayServer) establishTaskAuthorityGrant(
taskID,
taskReq.TaskType,
taskReq.AssignmentMode,
- requireFurtherDelegates,
+ requiredRemainingHops,
)
if err != nil {
return nil, err
@@ -842,11 +850,22 @@ func taskGrantRenewalTarget(grant *acl.AuthorityGrant, now time.Time) (time.Time
// The returned grant must still support further delegation (MayDelegate +
// RemainingHops > 0); otherwise the caller proceeds as direct.
func (s *GatewayServer) loadCallerTaskAuthority(ctx context.Context, client *ClientSession, actor models.Identity) (*acl.ResolvedAuthority, error) {
- if client == nil || client.AssociatedTaskID == "" || s.acl == nil || s.taskStore == nil {
+ if client == nil {
return nil, nil
}
+ return s.loadTaskAuthorityForActor(ctx, client.AssociatedTaskID, actor)
+}
- task, err := s.taskStore.GetTask(ctx, client.AssociatedTaskID)
+// loadTaskAuthorityForActor resolves delegable authority from a specific task
+// after the caller-to-parent relationship has been independently validated.
+// It is used for request-scoped explicit parentage without changing the
+// long-lived connection's AssociatedTaskID.
+func (s *GatewayServer) loadTaskAuthorityForActor(ctx context.Context, taskID string, actor models.Identity) (*acl.ResolvedAuthority, error) {
+ if taskID == "" || s.acl == nil || s.taskStore == nil {
+ return nil, nil
+ }
+
+ task, err := s.taskStore.GetTask(ctx, taskID)
if err != nil || task == nil {
return nil, nil
}
diff --git a/server/internal/gateway/task_authority_derivation_test.go b/server/internal/gateway/task_authority_derivation_test.go
index 58c8dcc..22fb0f1 100644
--- a/server/internal/gateway/task_authority_derivation_test.go
+++ b/server/internal/gateway/task_authority_derivation_test.go
@@ -134,7 +134,7 @@ func TestNestedCreateTaskDerivesAuthority(t *testing.T) {
nestedTaskID,
"child-work",
"targeted",
- false,
+ 0,
)
if err != nil {
t.Fatalf("createTaskAuthorityGrant(nested) error = %v", err)
diff --git a/server/internal/gateway/task_test.go b/server/internal/gateway/task_test.go
index 8a4fad3..16736fc 100644
--- a/server/internal/gateway/task_test.go
+++ b/server/internal/gateway/task_test.go
@@ -324,6 +324,113 @@ func newTaskTestServerWithSQLiteStore(t *testing.T) (*GatewayServer, func()) {
return s, func() { _ = db.Close() }
}
+func TestHandleTaskQuery_StatusProjectionFilters(t *testing.T) {
+ s, cleanup := newTaskTestServerWithSQLiteStore(t)
+ defer cleanup()
+
+ ctx := context.Background()
+ taskType := "status-projection-filter"
+ stored := []struct {
+ id string
+ status tasks.TaskStatus
+ }{
+ {"pending", tasks.TaskStatusPending},
+ {"assigned", tasks.TaskStatusAssigned},
+ {"starting", tasks.TaskStatusStarting},
+ {"running", tasks.TaskStatusRunning},
+ {"failed", tasks.TaskStatusFailed},
+ {"dlq", tasks.TaskStatusDLQ},
+ }
+ for _, item := range stored {
+ if err := s.taskStore.CreateTask(ctx, &tasks.Task{
+ TaskID: item.id, TaskType: taskType, Workspace: "ws1", Status: item.status,
+ }); err != nil {
+ t.Fatalf("CreateTask(%s): %v", item.id, err)
+ }
+ }
+
+ tests := []struct {
+ name string
+ filter *pb.TaskFilter
+ want map[string]bool
+ }{
+ {
+ name: "singular queued",
+ filter: &pb.TaskFilter{
+ Status: pb.TaskStatus_TASK_STATUS_QUEUED,
+ },
+ want: map[string]bool{"pending": true, "assigned": true, "starting": true},
+ },
+ {
+ name: "repeated queued and running",
+ filter: &pb.TaskFilter{
+ Statuses: []pb.TaskStatus{pb.TaskStatus_TASK_STATUS_QUEUED, pb.TaskStatus_TASK_STATUS_RUNNING},
+ },
+ want: map[string]bool{"pending": true, "assigned": true, "starting": true, "running": true},
+ },
+ {
+ name: "exclude queued",
+ filter: &pb.TaskFilter{
+ ExcludeStatuses: []pb.TaskStatus{pb.TaskStatus_TASK_STATUS_QUEUED},
+ },
+ want: map[string]bool{"running": true, "failed": true, "dlq": true},
+ },
+ {
+ name: "singular failed",
+ filter: &pb.TaskFilter{
+ Status: pb.TaskStatus_TASK_STATUS_FAILED,
+ },
+ want: map[string]bool{"failed": true, "dlq": true},
+ },
+ {
+ name: "repeated failed",
+ filter: &pb.TaskFilter{
+ Statuses: []pb.TaskStatus{pb.TaskStatus_TASK_STATUS_FAILED},
+ },
+ want: map[string]bool{"failed": true, "dlq": true},
+ },
+ {
+ name: "exclude failed",
+ filter: &pb.TaskFilter{
+ ExcludeStatuses: []pb.TaskStatus{pb.TaskStatus_TASK_STATUS_FAILED},
+ },
+ want: map[string]bool{"pending": true, "assigned": true, "starting": true, "running": true},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tt.filter.Workspace = "ws1"
+ tt.filter.TaskType = taskType
+ tt.filter.Limit = 100
+ stream := &mockStream{}
+ client := newTaskTestClient(stream, defaultAgentIdentity())
+ s.handleTaskQuery(ctx, client, &pb.TaskQuery{
+ Op: pb.TaskQuery_LIST, Filter: tt.filter, RequestId: "status-filter",
+ })
+
+ stream.mu.Lock()
+ if len(stream.sent) != 1 {
+ stream.mu.Unlock()
+ t.Fatalf("responses = %d, want 1", len(stream.sent))
+ }
+ response := stream.sent[0].GetTaskQuery()
+ stream.mu.Unlock()
+ if response == nil || !response.Success {
+ t.Fatalf("response = %#v", response)
+ }
+ if len(response.Tasks) != len(tt.want) {
+ t.Fatalf("task count = %d, want %d: %#v", len(response.Tasks), len(tt.want), response.Tasks)
+ }
+ for _, task := range response.Tasks {
+ if !tt.want[task.GetTaskId()] {
+ t.Errorf("unexpected task %q", task.GetTaskId())
+ }
+ }
+ })
+ }
+}
+
// callerIdentity returns a fully-qualified agent identity in ws1.
func callerIdentity(impl, spec string) models.Identity {
return models.Identity{
diff --git a/server/internal/gateway/workflow_authority.go b/server/internal/gateway/workflow_authority.go
new file mode 100644
index 0000000..71f8a5f
--- /dev/null
+++ b/server/internal/gateway/workflow_authority.go
@@ -0,0 +1,436 @@
+package gateway
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "strings"
+ "time"
+ "unicode/utf8"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/internal/audit"
+ "github.com/scitrera/aether/server/pkg/models"
+ "github.com/scitrera/aether/server/pkg/tasks"
+ "google.golang.org/protobuf/proto"
+)
+
+const maxDurableScheduleAuthorityTTL = 90 * 24 * time.Hour
+const workflowScheduleAuthorityPolicyVersion uint32 = 1
+
+type workflowScheduleIdentity struct {
+ ID string `json:"id"`
+ Workspace string `json:"workspace"`
+ Action *struct {
+ Type string `json:"type"`
+ TargetAgentID string `json:"target_agent_id"`
+ RequireTaskAuthority bool `json:"require_task_authority"`
+ RequiredDownstreamAuthorityHops uint32 `json:"required_downstream_authority_hops"`
+ } `json:"action"`
+}
+
+type workflowScheduleAccess struct {
+ resourceID string
+ operation string
+ workspace string
+ level int
+ manage bool
+ actionType string
+ targeted bool
+ requireTaskAuthority bool
+ actionRequiredDownstreamHops uint32
+}
+
+func (s *GatewayServer) prepareWorkflowOperation(ctx context.Context, client *ClientSession, incoming *pb.WorkflowOperation) (*pb.WorkflowOperation, *acl.AuthorityGrant, error) {
+ if incoming == nil {
+ return nil, nil, fmt.Errorf("workflow operation is required")
+ }
+ op := proto.Clone(incoming).(*pb.WorkflowOperation)
+ op.RequestContext = nil // gateway-authored only
+
+ client.identityMu.RLock()
+ actor := client.Identity
+ client.identityMu.RUnlock()
+ resolved, err := s.resolveAuthorizationContext(ctx, client, actor, op.GetAuthorization())
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid workflow authorization: %w", err)
+ }
+ subject := actor
+ if resolved != nil {
+ subject = resolved.Subject
+ }
+ op.RequestContext = &pb.WorkflowRequestContext{
+ Actor: identityToProtoPrincipalRef(actor), Subject: identityToProtoPrincipalRef(subject),
+ ActorSessionId: client.SessionUUID.String(),
+ }
+
+ access, isSchedule, err := workflowScheduleAccessForOperation(op)
+ if err != nil {
+ return nil, nil, err
+ }
+ if !isSchedule {
+ return op, nil, nil
+ }
+ if s.acl == nil {
+ return nil, nil, fmt.Errorf("workflow schedule authorization requires ACL service")
+ }
+ var decision *acl.ACLDecision
+ if resolved != nil {
+ decision, err = s.acl.CheckAccessWithAuthority(ctx, actor, resolved,
+ acl.ResourceTypeWorkflowSchedule, access.resourceID, access.operation,
+ access.workspace, client.SessionUUID, access.level)
+ } else {
+ decision, err = s.acl.CheckAccess(ctx, actor,
+ acl.ResourceTypeWorkflowSchedule, access.resourceID, access.operation,
+ access.workspace, client.SessionUUID, access.level)
+ }
+ if err != nil {
+ return nil, nil, fmt.Errorf("workflow schedule access check: %w", err)
+ }
+ if decision == nil || decision.Denied() {
+ return nil, nil, fmt.Errorf("not authorized to %s workflow schedule %s", access.operation, access.resourceID)
+ }
+
+ scope := op.GetScheduleAuthorityScope()
+ if scope == nil {
+ if access.manage && (op.GetOp() == pb.WorkflowOperation_CREATE_SCHEDULE || op.GetOp() == pb.WorkflowOperation_UPSERT_SCHEDULE) &&
+ access.actionType == "create_task" && access.requireTaskAuthority {
+ return nil, nil, fmt.Errorf("schedule action requires task authority scope")
+ }
+ return op, nil, nil
+ }
+ if !access.manage || (op.GetOp() != pb.WorkflowOperation_CREATE_SCHEDULE && op.GetOp() != pb.WorkflowOperation_UPSERT_SCHEDULE) {
+ return nil, nil, fmt.Errorf("schedule authority scope is valid only for create/upsert")
+ }
+ if access.actionType != "create_task" {
+ return nil, nil, fmt.Errorf("schedule authority scope is valid only for create_task actions")
+ }
+ if access.actionRequiredDownstreamHops > scope.GetRequiredTaskAuthorityHops() {
+ return nil, nil, fmt.Errorf("schedule action downstream authority requirement exceeds requested scope")
+ }
+ remainingHops := workflowScheduleGrantRemainingHops(access, scope)
+ grant, err := s.mintWorkflowScheduleGrant(ctx, client, actor, resolved, op.GetId(), scope, remainingHops)
+ if err != nil {
+ return nil, nil, err
+ }
+ // Source-bound grants are intersected with the parent lifetime during mint.
+ // Canonicalize the forwarded scope before hashing it so the workflow engine
+ // persists a policy digest that describes the authority it actually holds.
+ scope.ExpiresAt = grant.ExpiresAt.Unix()
+ scope.RenewableUntil = grant.RenewableUntil.Unix()
+ rootGrantID := grant.RootGrantID
+ if rootGrantID == "" {
+ rootGrantID = grant.GrantID
+ }
+ sourceGrantID := ""
+ if resolved != nil && resolved.Grant != nil {
+ sourceGrantID = resolved.Grant.GrantID
+ }
+ digest, err := workflowAuthorityPolicyDigest(scope)
+ if err != nil {
+ _, _ = s.acl.RevokeAuthorityGrantCascade(ctx, grant.GrantID)
+ return nil, nil, err
+ }
+ op.RequestContext.ScheduleAuthorization = &pb.AuthorizationContext{
+ AuthorityMode: audit.AuthorityModeOnBehalfOf,
+ Subject: identityToProtoPrincipalRef(subject),
+ GrantId: grant.GrantID,
+ }
+ op.RequestContext.RootGrantId = rootGrantID
+ op.RequestContext.SourceGrantId = sourceGrantID
+ op.RequestContext.ExpiresAtMs = grant.ExpiresAt.UnixMilli()
+ op.RequestContext.PolicyDigest = digest
+ op.RequestContext.LifetimeMode = scope.GetLifetimeMode()
+ op.RequestContext.PolicyVersion = scope.GetPolicyVersion()
+ return op, grant, nil
+}
+
+func workflowScheduleGrantRemainingHops(access workflowScheduleAccess, scope *pb.WorkflowScheduleAuthorityScope) int {
+ remainingHops := int(scope.GetRequiredTaskAuthorityHops()) + 1 // schedule -> targeted task
+ if !access.targeted {
+ remainingHops++ // schedule -> pool task anchor -> selected worker
+ }
+ return remainingHops
+}
+
+func workflowScheduleAccessForOperation(op *pb.WorkflowOperation) (workflowScheduleAccess, bool, error) {
+ if op == nil {
+ return workflowScheduleAccess{}, false, fmt.Errorf("workflow operation is required")
+ }
+ read := op.GetOp() == pb.WorkflowOperation_LIST_SCHEDULES
+ manage := op.GetOp() == pb.WorkflowOperation_CREATE_SCHEDULE ||
+ op.GetOp() == pb.WorkflowOperation_UPSERT_SCHEDULE ||
+ op.GetOp() == pb.WorkflowOperation_DELETE_SCHEDULE
+ if !read && !manage {
+ return workflowScheduleAccess{}, false, nil
+ }
+ workspace := strings.TrimSpace(op.GetWorkspace())
+ if workspace == "" || workspace == "*" {
+ return workflowScheduleAccess{}, true, fmt.Errorf("an exact workflow schedule workspace is required")
+ }
+ workspaceSegment, err := encodeWorkflowResourceSegment(workspace)
+ if err != nil {
+ return workflowScheduleAccess{}, true, fmt.Errorf("workflow schedule workspace: %w", err)
+ }
+ scheduleID := "*"
+ var actionType string
+ var targeted, requireTaskAuthority bool
+ var actionRequiredDownstreamHops uint32
+ if manage {
+ scheduleID = strings.TrimSpace(op.GetId())
+ if scheduleID == "" {
+ return workflowScheduleAccess{}, true, fmt.Errorf("workflow schedule id is required")
+ }
+ if op.GetOp() == pb.WorkflowOperation_CREATE_SCHEDULE || op.GetOp() == pb.WorkflowOperation_UPSERT_SCHEDULE {
+ var identity workflowScheduleIdentity
+ if err := json.Unmarshal(op.GetData(), &identity); err != nil {
+ return workflowScheduleAccess{}, true, fmt.Errorf("invalid workflow schedule JSON: %w", err)
+ }
+ if identity.ID != scheduleID || identity.Workspace != workspace {
+ return workflowScheduleAccess{}, true, fmt.Errorf("workflow schedule operation identity does not match JSON definition")
+ }
+ if identity.Action != nil {
+ actionType = identity.Action.Type
+ targeted = strings.TrimSpace(identity.Action.TargetAgentID) != ""
+ requireTaskAuthority = identity.Action.RequireTaskAuthority
+ actionRequiredDownstreamHops = identity.Action.RequiredDownstreamAuthorityHops
+ }
+ }
+ }
+ scheduleSegment := scheduleID
+ if scheduleID != "*" {
+ scheduleSegment, err = encodeWorkflowResourceSegment(scheduleID)
+ if err != nil {
+ return workflowScheduleAccess{}, true, fmt.Errorf("workflow schedule id: %w", err)
+ }
+ }
+ access := workflowScheduleAccess{
+ resourceID: "workspaces/" + workspaceSegment + "/schedules/" + scheduleSegment,
+ workspace: workspace, manage: manage, actionType: actionType, targeted: targeted,
+ requireTaskAuthority: requireTaskAuthority, actionRequiredDownstreamHops: actionRequiredDownstreamHops,
+ }
+ if read {
+ access.operation = audit.OpWorkflowScheduleRead
+ access.level = acl.AccessRead
+ } else {
+ access.operation = audit.OpWorkflowScheduleManage
+ access.level = acl.AccessManage
+ }
+ return access, true, nil
+}
+
+func (s *GatewayServer) mintWorkflowScheduleGrant(ctx context.Context, client *ClientSession, actor models.Identity, source *acl.ResolvedAuthority, scheduleID string, scope *pb.WorkflowScheduleAuthorityScope, remainingHops int) (*acl.AuthorityGrant, error) {
+ if scope == nil || len(scope.GetWorkspaceScope()) == 0 || len(scope.GetResourceScope()) == 0 || len(scope.GetOperationScope()) == 0 {
+ return nil, fmt.Errorf("workflow schedule authority requires non-empty workspace, resource, and operation scope")
+ }
+ if scope.GetPolicyVersion() != workflowScheduleAuthorityPolicyVersion {
+ return nil, fmt.Errorf("unsupported workflow schedule authority policy version %d", scope.GetPolicyVersion())
+ }
+ if scope.GetMaxAccessLevel() <= 0 || scope.GetRequiredTaskAuthorityHops() > 1 {
+ return nil, fmt.Errorf("invalid workflow schedule access level or downstream hop requirement")
+ }
+ now := time.Now().UTC()
+ expiresAt := time.Unix(scope.GetExpiresAt(), 0).UTC()
+ renewableUntil := time.Unix(scope.GetRenewableUntil(), 0).UTC()
+ if !expiresAt.After(now) || renewableUntil.Before(expiresAt) {
+ return nil, fmt.Errorf("workflow schedule authority expiry and renewable ceiling are invalid")
+ }
+ for _, workspace := range scope.GetWorkspaceScope() {
+ if workspace == "" || workspace == "*" || workspace == acl.WorkspaceScopeSubjectInherited {
+ return nil, fmt.Errorf("workflow schedule authority workspace scope must be exact")
+ }
+ }
+
+ subject := actor
+ if source != nil {
+ subject = source.Subject
+ }
+ delegate := models.Identity{Type: models.PrincipalWorkflowEngine}
+ request := acl.CreateAuthorityGrantRequest{
+ Subject: subject, Delegate: delegate, IssuedBy: actor,
+ MayDelegate: true, RemainingHops: remainingHops,
+ WorkspaceScope: append([]string(nil), scope.GetWorkspaceScope()...),
+ ResourceScope: protoAuthorityResourceScopeToACL(scope.GetResourceScope()),
+ OperationScope: append([]string(nil), scope.GetOperationScope()...),
+ MaxAccessLevel: int(scope.GetMaxAccessLevel()),
+ AudienceType: acl.AuthorityAudienceWorkflowSchedule, AudienceID: scheduleID,
+ ExpiresAt: expiresAt, RenewableUntil: renewableUntil,
+ Reason: "workflow-schedule:" + scheduleID,
+ Metadata: map[string]interface{}{
+ "workflow_schedule_id": scheduleID,
+ "workflow_authority_lifetime": scope.GetLifetimeMode().String(),
+ },
+ }
+
+ switch scope.GetLifetimeMode() {
+ case pb.WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND:
+ if source == nil || source.Grant == nil {
+ return nil, fmt.Errorf("source-bound workflow schedule authority requires OBO authority")
+ }
+ intersectWorkflowScheduleSourceLifetime(&request, source.Grant)
+ parentGrantID := source.Grant.GrantID
+ request.ParentGrantID = &parentGrantID
+ request.RootSubject = workflowRootSubject(source)
+ request.Metadata["source_authority_grant_id"] = parentGrantID
+ case pb.WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_DURABLE:
+ if expiresAt.After(now.Add(maxDurableScheduleAuthorityTTL)) || !renewableUntil.Equal(expiresAt) {
+ return nil, fmt.Errorf("durable workflow schedule authority must use a fixed expiry within %s", maxDurableScheduleAuthorityTTL)
+ }
+ if source == nil {
+ if actor.Type != models.PrincipalUser {
+ return nil, fmt.Errorf("durable workflow schedule authority requires a direct user or authorized OBO intermediary")
+ }
+ } else {
+ decision, err := s.acl.CheckAccess(ctx, actor, acl.ResourceTypeCapability,
+ acl.PermissionScheduleAuthority, audit.OpScheduleAuthority,
+ actor.Workspace, client.SessionUUID, acl.AccessManage)
+ if err != nil || decision == nil || decision.Denied() {
+ return nil, fmt.Errorf("actor is not authorized to mint durable workflow schedule authority")
+ }
+ if err := acl.ValidateAuthorityGrantScopeAttenuation(source.Grant, request); err != nil {
+ return nil, fmt.Errorf("durable workflow schedule authority exceeds source scope: %w", err)
+ }
+ request.Metadata["source_authority_grant_id"] = source.Grant.GrantID
+ }
+ default:
+ return nil, fmt.Errorf("unsupported workflow schedule authority lifetime mode")
+ }
+
+ grant, err := s.acl.CreateAuthorityGrant(ctx, request)
+ if err != nil {
+ return nil, fmt.Errorf("create workflow schedule authority: %w", err)
+ }
+ s.logAuthorityGrantLifecycle(ctx, actor, client.SessionUUID, audit.OpAuthorityGrantDerive, grant, true, "", map[string]interface{}{
+ "workflow_schedule_id": scheduleID,
+ "lifetime_mode": scope.GetLifetimeMode().String(),
+ })
+ return grant, nil
+}
+
+func intersectWorkflowScheduleSourceLifetime(request *acl.CreateAuthorityGrantRequest, source *acl.AuthorityGrant) {
+ if request == nil || source == nil {
+ return
+ }
+ if request.ExpiresAt.After(source.ExpiresAt) {
+ request.ExpiresAt = source.ExpiresAt
+ }
+ if request.RenewableUntil.After(source.RenewableUntil) {
+ request.RenewableUntil = source.RenewableUntil
+ }
+}
+
+func workflowRootSubject(source *acl.ResolvedAuthority) *models.Identity {
+ if source == nil || source.Grant == nil {
+ return nil
+ }
+ root, err := identityFromAuthorityPrincipal(source.Grant.RootSubjectType, source.Grant.RootSubjectID)
+ if err != nil {
+ copy := source.Subject
+ return ©
+ }
+ return &root
+}
+
+func workflowAuthorityPolicyDigest(scope *pb.WorkflowScheduleAuthorityScope) (string, error) {
+ data, err := proto.MarshalOptions{Deterministic: true}.Marshal(scope)
+ if err != nil {
+ return "", fmt.Errorf("marshal workflow authority policy: %w", err)
+ }
+ sum := sha256.Sum256(data)
+ return "sha256:" + hex.EncodeToString(sum[:]), nil
+}
+
+func encodeWorkflowResourceSegment(value string) (string, error) {
+ if value == "" || value == "!" || !utf8.ValidString(value) {
+ return "", fmt.Errorf("invalid resource segment")
+ }
+ const hexUpper = "0123456789ABCDEF"
+ var out strings.Builder
+ for _, b := range []byte(value) {
+ if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') ||
+ (b >= '0' && b <= '9') || b == '-' || b == '.' || b == '_' || b == '~' {
+ out.WriteByte(b)
+ continue
+ }
+ out.WriteByte('%')
+ out.WriteByte(hexUpper[b>>4])
+ out.WriteByte(hexUpper[b&0x0f])
+ }
+ return out.String(), nil
+}
+
+func workflowGrantID(grant *acl.AuthorityGrant) string {
+ if grant == nil {
+ return ""
+ }
+ return grant.GrantID
+}
+
+func (s *GatewayServer) revokeProvisionalWorkflowGrant(ctx context.Context, grant *acl.AuthorityGrant) {
+ if grant != nil {
+ s.revokeProvisionalWorkflowGrantByID(ctx, grant.GrantID)
+ }
+}
+
+func (s *GatewayServer) revokeProvisionalWorkflowGrantByID(ctx context.Context, grantID string) {
+ if s.acl == nil || strings.TrimSpace(grantID) == "" {
+ return
+ }
+ _, _ = s.acl.RevokeAuthorityGrantCascade(ctx, grantID)
+}
+
+// validateWorkflowScheduleSourceAuthority enforces the extra lifetime edge for
+// source-bound schedule grants. Revocation/expiry cascades are already checked
+// by ResolveAuthority; this additionally treats an inactive source session or
+// task as permanent invalidity without changing the public grant schema.
+func (s *GatewayServer) validateWorkflowScheduleSourceAuthority(ctx context.Context, grant *acl.AuthorityGrant) error {
+ if grant == nil {
+ return fmt.Errorf("workflow schedule authority grant is required")
+ }
+ lifetime, _ := grant.Metadata["workflow_authority_lifetime"].(string)
+ if lifetime != pb.WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_SOURCE_BOUND.String() {
+ return nil
+ }
+ sourceGrantID, _ := grant.Metadata["source_authority_grant_id"].(string)
+ if strings.TrimSpace(sourceGrantID) == "" || grant.ParentGrantID == nil || *grant.ParentGrantID != sourceGrantID {
+ return fmt.Errorf("source-bound schedule authority lineage is invalid")
+ }
+ source, err := s.acl.GetAuthorityGrant(ctx, sourceGrantID)
+ if err != nil {
+ return err
+ }
+ if err := source.ValidateActiveAt(time.Now()); err != nil {
+ return err
+ }
+ if !source.ValidWhileAudienceActive {
+ return nil
+ }
+ switch source.AudienceType {
+ case acl.AuthorityAudienceSession:
+ sessionID, err := uuid.Parse(source.AudienceID)
+ if err != nil || s.sessions == nil {
+ return acl.ErrAuthorityGrantAudienceMismatch
+ }
+ identity, err := s.sessions.GetSessionIdentity(ctx, sessionID.String())
+ if err != nil {
+ return acl.ErrAuthorityGrantAudienceMismatch
+ }
+ active, err := s.sessions.IsActive(ctx, identity.String())
+ if err != nil || !active {
+ return acl.ErrAuthorityGrantAudienceMismatch
+ }
+ case acl.AuthorityAudienceTask:
+ if s.taskStore == nil {
+ return acl.ErrAuthorityGrantAudienceMismatch
+ }
+ task, err := s.taskStore.GetTask(ctx, source.AudienceID)
+ if err != nil || task == nil || (task.Status != tasks.TaskStatusPending && task.Status != tasks.TaskStatusAssigned && task.Status != tasks.TaskStatusRunning) {
+ return acl.ErrAuthorityGrantAudienceMismatch
+ }
+ }
+ return nil
+}
diff --git a/server/internal/gateway/workflow_authority_test.go b/server/internal/gateway/workflow_authority_test.go
new file mode 100644
index 0000000..65bd4cf
--- /dev/null
+++ b/server/internal/gateway/workflow_authority_test.go
@@ -0,0 +1,134 @@
+package gateway
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ pb "github.com/scitrera/aether/api/proto"
+ "github.com/scitrera/aether/server/internal/acl"
+ "github.com/scitrera/aether/server/pkg/models"
+)
+
+func TestWorkflowScheduleAccessRequiresCanonicalExactIdentity(t *testing.T) {
+ data := []byte(`{"id":"sched/a","workspace":"workspace a"}`)
+ access, scheduleOp, err := workflowScheduleAccessForOperation(&pb.WorkflowOperation{
+ Op: pb.WorkflowOperation_UPSERT_SCHEDULE, Id: "sched/a", Workspace: "workspace a", Data: data,
+ })
+ if err != nil {
+ t.Fatalf("workflowScheduleAccessForOperation: %v", err)
+ }
+ if !scheduleOp || !access.manage || access.workspace != "workspace a" ||
+ access.resourceID != "workspaces/workspace%20a/schedules/sched%2Fa" {
+ t.Fatalf("unexpected schedule access: %+v schedule=%v", access, scheduleOp)
+ }
+
+ for name, op := range map[string]*pb.WorkflowOperation{
+ "wildcard workspace": {Op: pb.WorkflowOperation_LIST_SCHEDULES, Workspace: "*"},
+ "missing id": {Op: pb.WorkflowOperation_DELETE_SCHEDULE, Workspace: "workspace a"},
+ "json id mismatch": {
+ Op: pb.WorkflowOperation_CREATE_SCHEDULE, Id: "other", Workspace: "workspace a", Data: data,
+ },
+ "json workspace mismatch": {
+ Op: pb.WorkflowOperation_CREATE_SCHEDULE, Id: "sched/a", Workspace: "other", Data: data,
+ },
+ } {
+ t.Run(name, func(t *testing.T) {
+ if _, isSchedule, err := workflowScheduleAccessForOperation(op); !isSchedule || err == nil {
+ t.Fatalf("got isSchedule=%v err=%v, want schedule validation error", isSchedule, err)
+ }
+ })
+ }
+}
+
+func TestPrepareWorkflowOperationReplacesForgedRequestContext(t *testing.T) {
+ client := &ClientSession{
+ Identity: models.Identity{Type: models.PrincipalUser, ID: "user-a"},
+ SessionUUID: uuid.New(),
+ }
+ incoming := &pb.WorkflowOperation{
+ Op: pb.WorkflowOperation_CREATE_RULE,
+ RequestContext: &pb.WorkflowRequestContext{
+ ScheduleAuthorization: &pb.AuthorizationContext{GrantId: "forged"},
+ RootGrantId: "forged-root", PolicyDigest: "forged-digest",
+ },
+ }
+ forwarded, grant, err := (&GatewayServer{}).prepareWorkflowOperation(context.Background(), client, incoming)
+ if err != nil {
+ t.Fatalf("prepareWorkflowOperation: %v", err)
+ }
+ if grant != nil || forwarded.GetRequestContext() == nil {
+ t.Fatalf("grant=%v context=%v", grant, forwarded.GetRequestContext())
+ }
+ trusted := forwarded.GetRequestContext()
+ if trusted.GetScheduleAuthorization() != nil || trusted.GetRootGrantId() != "" || trusted.GetPolicyDigest() != "" ||
+ trusted.GetActor().GetPrincipalId() != "user-a" || trusted.GetActorSessionId() != client.SessionUUID.String() {
+ t.Fatalf("forged context survived: %+v", trusted)
+ }
+ if incoming.GetRequestContext().GetScheduleAuthorization().GetGrantId() != "forged" {
+ t.Fatal("prepareWorkflowOperation mutated the caller's protobuf")
+ }
+}
+
+func TestWorkflowAuthorityPolicyDigestIsDeterministicAndVersionSensitive(t *testing.T) {
+ scope := &pb.WorkflowScheduleAuthorityScope{
+ WorkspaceScope: []string{"ws-a"}, OperationScope: []string{"task_create"},
+ MaxAccessLevel: 20, PolicyVersion: 1,
+ }
+ first, err := workflowAuthorityPolicyDigest(scope)
+ if err != nil {
+ t.Fatal(err)
+ }
+ second, err := workflowAuthorityPolicyDigest(scope)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if first == "" || first != second {
+ t.Fatalf("digest is not deterministic: first=%q second=%q", first, second)
+ }
+ scope.PolicyVersion = 2
+ changed, err := workflowAuthorityPolicyDigest(scope)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if changed == first {
+ t.Fatalf("policy version did not affect digest: %q", first)
+ }
+}
+
+func TestWorkflowScheduleGrantRemainingHopsAccountsForPoolSelection(t *testing.T) {
+ scope := &pb.WorkflowScheduleAuthorityScope{RequiredTaskAuthorityHops: 1}
+ if got := workflowScheduleGrantRemainingHops(workflowScheduleAccess{targeted: true}, scope); got != 2 {
+ t.Fatalf("targeted schedule remaining hops = %d, want 2", got)
+ }
+ if got := workflowScheduleGrantRemainingHops(workflowScheduleAccess{targeted: false}, scope); got != 3 {
+ t.Fatalf("pool schedule remaining hops = %d, want 3", got)
+ }
+}
+
+func TestIntersectWorkflowScheduleSourceLifetime(t *testing.T) {
+ now := time.Now().UTC()
+ parent := &acl.AuthorityGrant{
+ ExpiresAt: now.Add(time.Hour),
+ RenewableUntil: now.Add(2 * time.Hour),
+ }
+ request := acl.CreateAuthorityGrantRequest{
+ ExpiresAt: now.Add(24 * time.Hour),
+ RenewableUntil: now.Add(48 * time.Hour),
+ }
+
+ intersectWorkflowScheduleSourceLifetime(&request, parent)
+ if !request.ExpiresAt.Equal(parent.ExpiresAt) || !request.RenewableUntil.Equal(parent.RenewableUntil) {
+ t.Fatalf("lifetime was not intersected with source: expires=%v renewable=%v", request.ExpiresAt, request.RenewableUntil)
+ }
+
+ shorter := acl.CreateAuthorityGrantRequest{
+ ExpiresAt: now.Add(5 * time.Minute),
+ RenewableUntil: now.Add(10 * time.Minute),
+ }
+ intersectWorkflowScheduleSourceLifetime(&shorter, parent)
+ if !shorter.ExpiresAt.Equal(now.Add(5*time.Minute)) || !shorter.RenewableUntil.Equal(now.Add(10*time.Minute)) {
+ t.Fatalf("shorter requested lifetime changed: expires=%v renewable=%v", shorter.ExpiresAt, shorter.RenewableUntil)
+ }
+}
diff --git a/server/internal/gateway/workflow_handler.go b/server/internal/gateway/workflow_handler.go
index a766c0d..1a648ad 100644
--- a/server/internal/gateway/workflow_handler.go
+++ b/server/internal/gateway/workflow_handler.go
@@ -12,8 +12,9 @@ import (
// pendingWorkflowRequest tracks an in-flight WorkflowOperation waiting for a response.
type pendingWorkflowRequest struct {
- client *ClientSession
- createdAt time.Time
+ client *ClientSession
+ createdAt time.Time
+ provisionalScheduleGrantID string
}
// handleWorkflowOp forwards a WorkflowOperation from a client to the connected workflow engine.
@@ -32,6 +33,16 @@ func (s *GatewayServer) handleWorkflowOp(ctx context.Context, client *ClientSess
})
return
}
+ forwarded, provisionalGrant, err := s.prepareWorkflowOperation(ctx, client, op)
+ if err != nil {
+ _ = client.SafeSend(&pb.DownstreamMessage{
+ Payload: &pb.DownstreamMessage_WorkflowResponse{WorkflowResponse: &pb.WorkflowResponse{
+ Success: false, Error: err.Error(), RequestId: op.GetRequestId(),
+ }},
+ })
+ return
+ }
+ op = forwarded
// Ensure a request_id exists for correlation
requestID := op.RequestId
@@ -42,8 +53,8 @@ func (s *GatewayServer) handleWorkflowOp(ctx context.Context, client *ClientSess
// Store the pending request so the response can be routed back
s.pendingWorkflowRequests.Store(requestID, &pendingWorkflowRequest{
- client: client,
- createdAt: time.Now(),
+ client: client, createdAt: time.Now(),
+ provisionalScheduleGrantID: workflowGrantID(provisionalGrant),
})
// Forward the operation downstream to the workflow engine
@@ -54,6 +65,7 @@ func (s *GatewayServer) handleWorkflowOp(ctx context.Context, client *ClientSess
}); err != nil {
logging.Logger.Error().Err(err).Str("request_id", requestID).Msg("failed to forward workflow op to workflow engine")
s.pendingWorkflowRequests.Delete(requestID)
+ s.revokeProvisionalWorkflowGrant(ctx, provisionalGrant)
_ = client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_WorkflowResponse{
WorkflowResponse: &pb.WorkflowResponse{
@@ -76,6 +88,9 @@ func (s *GatewayServer) handleWorkflowResponse(ctx context.Context, client *Clie
}
origReq := val.(*pendingWorkflowRequest)
+ if !resp.GetSuccess() {
+ s.revokeProvisionalWorkflowGrantByID(ctx, origReq.provisionalScheduleGrantID)
+ }
if err := origReq.client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_WorkflowResponse{
WorkflowResponse: resp,
@@ -138,6 +153,7 @@ func (s *GatewayServer) sweepTimedOutWorkflowRequests() {
if pending.createdAt.Before(cutoff) {
requestID, _ := key.(string)
if _, deleted := s.pendingWorkflowRequests.LoadAndDelete(key); deleted {
+ s.revokeProvisionalWorkflowGrantByID(context.Background(), pending.provisionalScheduleGrantID)
logging.Logger.Warn().Str("request_id", requestID).Msg("workflow request timed out")
_ = pending.client.SafeSend(&pb.DownstreamMessage{
Payload: &pb.DownstreamMessage_WorkflowResponse{
@@ -166,6 +182,7 @@ func (s *GatewayServer) cleanupPendingWorkflowRequests(client *ClientSession) {
if pending.client == client {
requestID, _ := key.(string)
if _, deleted := s.pendingWorkflowRequests.LoadAndDelete(key); deleted {
+ s.revokeProvisionalWorkflowGrantByID(context.Background(), pending.provisionalScheduleGrantID)
logging.Logger.Debug().Str("request_id", requestID).Str("identity", client.Identity.String()).Msg("cleaning up pending workflow request on client disconnect")
}
}
diff --git a/server/internal/orchestration/disconnect_reaper.go b/server/internal/orchestration/disconnect_reaper.go
index d319288..bc22288 100644
--- a/server/internal/orchestration/disconnect_reaper.go
+++ b/server/internal/orchestration/disconnect_reaper.go
@@ -3,6 +3,7 @@ package orchestration
import (
"context"
"fmt"
+ "strings"
"time"
"github.com/scitrera/aether/server/internal/logging"
@@ -88,7 +89,14 @@ func (r *DisconnectReaper) scan(ctx context.Context) {
continue
}
// Race protection: maybe the worker reconnected between SELECT and now.
- if r.sessions != nil && r.sessions.HasActiveSessionForTask(ctx, t.TaskID) {
+ // Long-lived agent streams are not necessarily associated with each task
+ // they claim, so check both the task-bound session and assigned identity.
+ active, probeErr := r.hasActiveOwnerSession(ctx, t)
+ if probeErr != nil {
+ logging.Logger.Warn().Err(probeErr).Str("task_id", t.TaskID).Msg("disconnect reaper: owner liveness check failed; preserving task")
+ continue
+ }
+ if active {
// Reconnect happened — clear the marker (defensive; the connect
// path also clears it).
if clearErr := r.taskStore.ClearTaskDisconnected(ctx, t.TaskID); clearErr != nil {
@@ -108,3 +116,20 @@ func (r *DisconnectReaper) scan(ctx context.Context) {
Msg("disconnect reaper: task failed past grace window")
}
}
+
+func (r *DisconnectReaper) hasActiveOwnerSession(ctx context.Context, task *tasks.Task) (bool, error) {
+ if r.sessions != nil && r.sessions.HasActiveSessionForTask(ctx, task.TaskID) {
+ return true, nil
+ }
+ if r.taskService == nil || r.taskService.sessionRegistry == nil {
+ return false, nil
+ }
+ identity := strings.TrimSpace(task.AssignedTo)
+ if identity == "" {
+ identity = strings.TrimSpace(task.TargetAgentID)
+ }
+ if identity == "" {
+ return false, nil
+ }
+ return r.taskService.sessionRegistry.IsActive(ctx, identity)
+}
diff --git a/server/internal/orchestration/disconnect_reaper_test.go b/server/internal/orchestration/disconnect_reaper_test.go
index c00ab78..02a7ebf 100644
--- a/server/internal/orchestration/disconnect_reaper_test.go
+++ b/server/internal/orchestration/disconnect_reaper_test.go
@@ -242,6 +242,27 @@ func TestDisconnectReaper_StillFailsRunningTasks(t *testing.T) {
}
}
+func TestDisconnectReaper_PreservesRunningTaskWhenLongLivedOwnerReconnected(t *testing.T) {
+ store, db, cleanup := newReaperStore(t)
+ defer cleanup()
+ ctx := context.Background()
+
+ task := buildRunningTaskForReaper(t, ctx, store, "reconnected")
+ forceDisconnectedRow(t, ctx, db, task.TaskID, tasks.TaskStatusRunning, time.Now().Add(-time.Hour), 1000)
+
+ service := &TaskAssignmentService{taskStore: store, sessionRegistry: alwaysOnlineSessionRegistry{}}
+ reaper := NewDisconnectReaper(store, service, reaperLivenessAllOffline{})
+ reaper.scan(ctx)
+
+ got, err := store.GetTask(ctx, task.TaskID)
+ if err != nil {
+ t.Fatalf("GetTask after reaper scan: %v", err)
+ }
+ if got.Status != tasks.TaskStatusRunning || got.DisconnectedAt != nil {
+ t.Fatalf("reconnected long-lived owner was reaped: %+v", got)
+ }
+}
+
// fixedListStore wraps a real Store but overrides ListDisconnectedTasks to
// return a fixed slice. Used by the waiting/hibernated tests because the real
// store's status='running' filter would exclude the rows we want the reaper
diff --git a/server/internal/orchestration/task_assignment.go b/server/internal/orchestration/task_assignment.go
index d0321c2..0d2cdd8 100644
--- a/server/internal/orchestration/task_assignment.go
+++ b/server/internal/orchestration/task_assignment.go
@@ -197,8 +197,28 @@ type CreateTaskRequest struct {
// CompletionEvent, when non-nil, opts the task into "feed B": the server emits
// a domain event onto event::* when the task reaches a selected terminal status.
CompletionEvent *tasks.TaskCompletionConfig
+
+ // TargetOfflinePolicy controls TARGETED creation when the exact target is
+ // absent. Zero preserves the released orchestration behavior.
+ TargetOfflinePolicy TargetOfflinePolicy
+
+ // RequiredDownstreamAuthorityHops is the delegation capacity the final
+ // execution identity must retain after task authority is established.
+ // Transport validation currently limits this to 0 or 1.
+ RequiredDownstreamAuthorityHops int
}
+// TargetOfflinePolicy is kept independent from protobuf types so the task
+// assignment service remains transport-neutral.
+type TargetOfflinePolicy int32
+
+const (
+ TargetOfflinePolicyUnspecified TargetOfflinePolicy = iota
+ TargetOfflinePolicyOrchestrate
+ TargetOfflinePolicyQueue
+ TargetOfflinePolicyReject
+)
+
// principalTypeStringForTask maps a models.PrincipalType to the lowercase
// canonical string form used in task Authority columns ("user", "agent",
// "task", "service", etc.). These strings match the ACL canonical forms
@@ -362,6 +382,11 @@ func (tas *TaskAssignmentService) handleTargeted(ctx context.Context, req *Creat
if req.TargetAgentID == "" {
return nil, fmt.Errorf("target_agent_id required for targeted assignment")
}
+ switch req.TargetOfflinePolicy {
+ case TargetOfflinePolicyUnspecified, TargetOfflinePolicyOrchestrate, TargetOfflinePolicyQueue, TargetOfflinePolicyReject:
+ default:
+ return nil, fmt.Errorf("unsupported target offline policy %d", req.TargetOfflinePolicy)
+ }
// Parse target agent identity
targetIdentity, err := models.ParseIdentity(req.TargetAgentID)
@@ -369,15 +394,6 @@ func (tas *TaskAssignmentService) handleTargeted(ctx context.Context, req *Creat
return nil, fmt.Errorf("invalid target_agent_id: %w", err)
}
- // REQUIRED: Validate target agent implementation exists in registry
- exists, err := tas.agentRegistry.Exists(ctx, targetIdentity.Implementation)
- if err != nil {
- return nil, fmt.Errorf("failed to check agent registry: %w", err)
- }
- if !exists {
- return nil, fmt.Errorf("target agent implementation '%s' not found in registry", targetIdentity.Implementation)
- }
-
taskID := uuid.New().String()
task := &tasks.ExtendedTask{
@@ -402,6 +418,31 @@ func (tas *TaskAssignmentService) handleTargeted(ctx context.Context, req *Creat
applyRetryPolicyToTask(task)
applyCorrelationToTask(task, req)
+ // A connected exact identity is already authoritative evidence that the
+ // target can consume the task. Requiring an orchestration registry entry in
+ // that case rejects durable tasks for ad-hoc/static workers even though no
+ // launch is needed. Offline targets still require a registered implementation
+ // before this service can ask an orchestrator to start them.
+ isOnline := tas.sessionRegistry.IsOnline(targetIdentity)
+ if !isOnline {
+ switch req.TargetOfflinePolicy {
+ case TargetOfflinePolicyUnspecified, TargetOfflinePolicyOrchestrate:
+ exists, err := tas.agentRegistry.Exists(ctx, targetIdentity.Implementation)
+ if err != nil {
+ return nil, fmt.Errorf("failed to check agent registry: %w", err)
+ }
+ if !exists {
+ return nil, fmt.Errorf("target agent implementation '%s' not found in registry", targetIdentity.Implementation)
+ }
+ case TargetOfflinePolicyQueue:
+ if req.TaskType == "agent_startup" {
+ return nil, fmt.Errorf("target offline policy queue is not valid for agent_startup tasks")
+ }
+ case TargetOfflinePolicyReject:
+ return nil, fmt.Errorf("target agent %q is offline", req.TargetAgentID)
+ }
+ }
+
// Special case: if this IS a startup task (e.g., from admin API), go directly to
// createOrchestratedStartupTask which handles all duplicate prevention:
// - Checks if agent is already online
@@ -423,9 +464,6 @@ func (tas *TaskAssignmentService) handleTargeted(ctx context.Context, req *Creat
}, nil
}
- // Check if target agent is online
- isOnline := tas.sessionRegistry.IsOnline(targetIdentity)
-
if isOnline {
// Agent online: create task as pending, then assign
// This follows the proper state machine: pending -> assigned
@@ -447,6 +485,21 @@ func (tas *TaskAssignmentService) handleTargeted(ctx context.Context, req *Creat
}, nil
}
+ if req.TargetOfflinePolicy == TargetOfflinePolicyQueue {
+ // Static workers have no orchestration registry entry by design. Persist
+ // the exact-target task and let the existing reconnect delivery path claim
+ // it when that identity next appears.
+ task.QueuedForStartup = true
+ if err := tas.taskStore.CreateTask(ctx, task); err != nil {
+ return nil, fmt.Errorf("failed to create queued targeted task: %w", err)
+ }
+ logging.Logger.Info().Str("task_id", taskID).Str("agent_id", req.TargetAgentID).Msg("queued task for offline static agent")
+ return &CreateTaskResponse{
+ TaskID: taskID, Status: "pending", QueuedForStartup: true,
+ Message: "Task queued until the target agent reconnects",
+ }, nil
+ }
+
// Agent offline: need orchestration to start it
// Regular task for offline agent: queue the task AND trigger orchestration
@@ -1364,7 +1417,13 @@ func (tas *TaskAssignmentService) reconcileTasksByStatus(
continue
}
+ markedDisconnected := task.DisconnectedAt != nil && task.GraceWindowMs > 0
if identity == "" {
+ // A marked task is already owned by DisconnectReaper. Preserve its
+ // grace window even if the generic projection cannot resolve an owner.
+ if markedDisconnected {
+ continue
+ }
if err := tas.FailTask(ctx, task.TaskID, emptyIdentityFailReason); err != nil {
logging.Logger.Error().Err(err).Str("task_id", task.TaskID).Msg("reconcile: failed to mark task as failed (no identity)")
} else {
@@ -1380,7 +1439,37 @@ func (tas *TaskAssignmentService) reconcileTasksByStatus(
continue
}
+ // A running task with an explicit disconnect marker is owned by the
+ // DisconnectReaper. If the long-lived owner is online again, clear the
+ // marker now; otherwise leave the task recoverable for its grace window.
+ if markedDisconnected {
+ if active {
+ if err := tas.ClearTaskDisconnected(ctx, task.TaskID); err != nil {
+ logging.Logger.Error().Err(err).Str("task_id", task.TaskID).Str(entityLogKey, identity).Msg("reconcile: failed to clear recovered task disconnect marker")
+ } else {
+ logging.Logger.Info().Str("task_id", task.TaskID).Str(entityLogKey, identity).Msg("reconcile: cleared recovered task disconnect marker")
+ reconciled++
+ }
+ }
+ continue
+ }
+
if !active {
+ // Long-lived agent connections are not associated with every task they
+ // claim after startup, so the stream-close path cannot always stamp those
+ // task IDs directly. Backfill the marker here and let DisconnectReaper
+ // enforce the same per-task grace window. This sweep may run again while
+ // the task is disconnected; MarkTaskDisconnected and the marked-task path
+ // above make that path idempotent.
+ if task.Status == tasks.TaskStatusRunning && task.GraceWindowMs > 0 {
+ if err := tas.MarkTaskDisconnected(ctx, task.TaskID, time.Now().UTC()); err != nil {
+ logging.Logger.Error().Err(err).Str("task_id", task.TaskID).Str(entityLogKey, identity).Msg("reconcile: failed to mark task disconnected")
+ } else {
+ logging.Logger.Info().Str("task_id", task.TaskID).Str(entityLogKey, identity).Int64("grace_ms", task.GraceWindowMs).Msg("reconcile: marked orphaned running task disconnected for grace recovery")
+ reconciled++
+ }
+ continue
+ }
if err := tas.FailTask(ctx, task.TaskID, offlineFailReason); err != nil {
logging.Logger.Error().Err(err).Str("task_id", task.TaskID).Msg("reconcile: failed to mark task as failed")
} else {
diff --git a/server/internal/orchestration/task_assignment_test.go b/server/internal/orchestration/task_assignment_test.go
index 225e55e..e30991e 100644
--- a/server/internal/orchestration/task_assignment_test.go
+++ b/server/internal/orchestration/task_assignment_test.go
@@ -1028,10 +1028,10 @@ func TestCancelStaleInteractiveTasks(t *testing.T) {
now := time.Now()
old := now.Add(-2 * time.Hour)
- mk("old-interactive", taskClassInteractive, tasks.TaskStatusPending, old) // -> cancelled
- mk("young-interactive", taskClassInteractive, tasks.TaskStatusPending, now) // too young -> kept
- mk("old-background", taskClassBackground, tasks.TaskStatusPending, old) // wrong class -> kept
- mk("old-terminal", taskClassInteractive, tasks.TaskStatusCompleted, old) // terminal -> kept
+ mk("old-interactive", taskClassInteractive, tasks.TaskStatusPending, old) // -> cancelled
+ mk("young-interactive", taskClassInteractive, tasks.TaskStatusPending, now) // too young -> kept
+ mk("old-background", taskClassBackground, tasks.TaskStatusPending, old) // wrong class -> kept
+ mk("old-terminal", taskClassInteractive, tasks.TaskStatusCompleted, old) // terminal -> kept
n, err := service.CancelStaleInteractiveTasks(ctx, time.Hour)
if err != nil {
@@ -1053,7 +1053,238 @@ func TestCancelStaleInteractiveTasks(t *testing.T) {
assertStatus("old-interactive", tasks.TaskStatusCancelled) // reaped
assertStatus("young-interactive", tasks.TaskStatusPending) // under TTL
assertStatus("old-background", tasks.TaskStatusPending) // not interactive
- assertStatus("old-terminal", tasks.TaskStatusCompleted) // already terminal
+ assertStatus("old-terminal", tasks.TaskStatusCompleted) // already terminal
+}
+
+type alwaysOfflineSessionRegistry struct{}
+
+func (alwaysOfflineSessionRegistry) IsOnline(models.Identity) bool { return false }
+
+func (alwaysOfflineSessionRegistry) IsActive(context.Context, string) (bool, error) {
+ return false, nil
+}
+
+type alwaysOnlineSessionRegistry struct{}
+
+func (alwaysOnlineSessionRegistry) IsOnline(models.Identity) bool { return true }
+
+func (alwaysOnlineSessionRegistry) IsActive(context.Context, string) (bool, error) {
+ return true, nil
+}
+
+func TestTargetedOnlineAgentDoesNotRequireOrchestrationRegistration(t *testing.T) {
+ dbPath := filepath.Join(t.TempDir(), "targeted_online.db")
+ db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ t.Cleanup(func() { _ = db.Close() })
+ taskStore, err := taskssqlite.New(db)
+ if err != nil {
+ t.Fatalf("taskssqlite.New: %v", err)
+ }
+ service := NewTaskAssignmentService(taskStore, nil, alwaysOnlineSessionRegistry{}, nil, nil)
+ target := "ag::default::ad-hoc-worker::schedule-1"
+ response, err := service.CreateTask(context.Background(), &CreateTaskRequest{
+ TaskType: "scheduled", Workspace: "default", AssignmentMode: "targeted",
+ TargetAgentID: target,
+ CreatorIdentity: models.Identity{
+ Type: models.PrincipalAgent, Workspace: "_system", Implementation: "workflow", Specifier: "shard0",
+ },
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if response == nil || response.AssignedTo != target || response.Status != "assigned" {
+ t.Fatalf("targeted response = %+v", response)
+ }
+ stored, err := taskStore.GetTask(context.Background(), response.TaskID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stored.AssignedTo != target || stored.Status != tasks.TaskStatusAssigned {
+ t.Fatalf("stored targeted task = %+v", stored)
+ }
+}
+
+func TestTargetedOfflineQueueDoesNotRequireOrchestrationRegistration(t *testing.T) {
+ dbPath := filepath.Join(t.TempDir(), "targeted_offline_queue.db")
+ db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ t.Cleanup(func() { _ = db.Close() })
+ taskStore, err := taskssqlite.New(db)
+ if err != nil {
+ t.Fatalf("taskssqlite.New: %v", err)
+ }
+ service := NewTaskAssignmentService(taskStore, nil, alwaysOfflineSessionRegistry{}, nil, nil)
+ target := "ag::default::static-worker::schedule-1"
+ response, err := service.CreateTask(context.Background(), &CreateTaskRequest{
+ TaskType: "scheduled", Workspace: "default", AssignmentMode: "targeted",
+ TargetAgentID: target, TargetOfflinePolicy: TargetOfflinePolicyQueue,
+ CreatorIdentity: models.Identity{
+ Type: models.PrincipalAgent, Workspace: "_system", Implementation: "workflow", Specifier: "shard0",
+ },
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if response == nil || response.Status != "pending" || !response.QueuedForStartup || response.StartupTaskID != "" {
+ t.Fatalf("targeted queue response = %+v", response)
+ }
+ stored, err := taskStore.GetTask(context.Background(), response.TaskID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stored.TargetAgentID != target || stored.Status != tasks.TaskStatusPending || !stored.QueuedForStartup {
+ t.Fatalf("stored queued task = %+v", stored)
+ }
+ targetIdentity, err := models.ParseIdentity(target)
+ if err != nil {
+ t.Fatal(err)
+ }
+ delivered, err := service.DeliverQueuedTasks(context.Background(), targetIdentity)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(delivered) != 1 || delivered[0].TaskID != response.TaskID {
+ t.Fatalf("delivered queued tasks = %+v", delivered)
+ }
+ stored, err = taskStore.GetTask(context.Background(), response.TaskID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stored.Status != tasks.TaskStatusAssigned || stored.AssignedTo != target || stored.QueuedForStartup {
+ t.Fatalf("delivered stored task = %+v", stored)
+ }
+}
+
+func TestTargetedOfflineRejectDoesNotCreateTask(t *testing.T) {
+ dbPath := filepath.Join(t.TempDir(), "targeted_offline_reject.db")
+ db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ t.Cleanup(func() { _ = db.Close() })
+ taskStore, err := taskssqlite.New(db)
+ if err != nil {
+ t.Fatalf("taskssqlite.New: %v", err)
+ }
+ service := NewTaskAssignmentService(taskStore, nil, alwaysOfflineSessionRegistry{}, nil, nil)
+ _, err = service.CreateTask(context.Background(), &CreateTaskRequest{
+ TaskType: "scheduled", Workspace: "default", AssignmentMode: "targeted",
+ TargetAgentID: "ag::default::static-worker::schedule-1", TargetOfflinePolicy: TargetOfflinePolicyReject,
+ CreatorIdentity: models.Identity{Type: models.PrincipalAgent, ID: "workflow"},
+ })
+ if err == nil {
+ t.Fatal("offline target was accepted under reject policy")
+ }
+}
+
+func TestReconcileOrphanedTasksDefersMarkedDisconnectsToGraceReaper(t *testing.T) {
+ dbPath := filepath.Join(t.TempDir(), "orch_tasks.db")
+ db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ t.Cleanup(func() { _ = db.Close() })
+ taskStore, err := taskssqlite.New(db)
+ if err != nil {
+ t.Fatalf("taskssqlite.New: %v", err)
+ }
+
+ ctx := context.Background()
+ const agentID = "ag::ws-test::worker::one"
+ createTask := func(id string, status tasks.TaskStatus) {
+ t.Helper()
+ if err := taskStore.CreateTask(ctx, &tasks.Task{
+ TaskID: id, TaskType: "chat_message", Workspace: "ws-test",
+ Status: status, TargetAgentID: agentID,
+ TaskClass: taskClassInteractive, GraceWindowMs: DefaultGraceWindowMs(taskClassInteractive),
+ }); err != nil {
+ t.Fatalf("CreateTask(%s): %v", id, err)
+ }
+ }
+ createTask("within-disconnect-grace", tasks.TaskStatusRunning)
+ createTask("unmarked-long-lived-agent-task", tasks.TaskStatusRunning)
+ createTask("offline-starting-task", tasks.TaskStatusStarting)
+ if err := taskStore.MarkTaskDisconnected(ctx, "within-disconnect-grace", time.Now()); err != nil {
+ t.Fatalf("MarkTaskDisconnected: %v", err)
+ }
+
+ service := NewTaskAssignmentService(taskStore, nil, alwaysOfflineSessionRegistry{}, nil, nil)
+ reconciled, err := service.ReconcileOrphanedTasks(ctx)
+ if err != nil {
+ t.Fatalf("ReconcileOrphanedTasks: %v", err)
+ }
+ if reconciled != 2 {
+ t.Fatalf("reconciled = %d, want 1 disconnect marker and 1 failed starting task", reconciled)
+ }
+
+ withinGrace, err := taskStore.GetTask(ctx, "within-disconnect-grace")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if withinGrace.Status != tasks.TaskStatusRunning || withinGrace.DisconnectedAt == nil {
+ t.Fatalf("marked disconnect was not preserved for recovery: %+v", withinGrace)
+ }
+ backfilled, err := taskStore.GetTask(ctx, "unmarked-long-lived-agent-task")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if backfilled.Status != tasks.TaskStatusRunning || backfilled.DisconnectedAt == nil {
+ t.Fatalf("long-lived agent task did not gain a recovery marker: %+v", backfilled)
+ }
+ starting, err := taskStore.GetTask(ctx, "offline-starting-task")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if starting.Status != tasks.TaskStatusFailed {
+ t.Fatalf("offline starting task status = %q, want failed", starting.Status)
+ }
+}
+
+func TestReconcileOrphanedTasksClearsMarkerAfterLongLivedOwnerReconnects(t *testing.T) {
+ dbPath := filepath.Join(t.TempDir(), "orch_tasks.db")
+ db, err := sql.Open("sqlite", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
+ if err != nil {
+ t.Fatalf("sql.Open sqlite: %v", err)
+ }
+ t.Cleanup(func() { _ = db.Close() })
+ taskStore, err := taskssqlite.New(db)
+ if err != nil {
+ t.Fatalf("taskssqlite.New: %v", err)
+ }
+
+ ctx := context.Background()
+ const taskID = "reconnected-long-lived-agent-task"
+ if err := taskStore.CreateTask(ctx, &tasks.Task{
+ TaskID: taskID, TaskType: "chat_message", Workspace: "ws-test",
+ Status: tasks.TaskStatusRunning, TargetAgentID: "ag::ws-test::worker::one",
+ TaskClass: taskClassInteractive, GraceWindowMs: DefaultGraceWindowMs(taskClassInteractive),
+ }); err != nil {
+ t.Fatalf("CreateTask: %v", err)
+ }
+ if err := taskStore.MarkTaskDisconnected(ctx, taskID, time.Now()); err != nil {
+ t.Fatalf("MarkTaskDisconnected: %v", err)
+ }
+
+ service := NewTaskAssignmentService(taskStore, nil, alwaysOnlineSessionRegistry{}, nil, nil)
+ reconciled, err := service.ReconcileOrphanedTasks(ctx)
+ if err != nil {
+ t.Fatalf("ReconcileOrphanedTasks: %v", err)
+ }
+ if reconciled != 1 {
+ t.Fatalf("reconciled = %d, want cleared disconnect marker", reconciled)
+ }
+ got, err := taskStore.GetTask(ctx, taskID)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got.Status != tasks.TaskStatusRunning || got.DisconnectedAt != nil {
+ t.Fatalf("reconnected task was not restored: %+v", got)
+ }
}
// TestCancelTask_RetiresQueueRowDirectlyWithoutDispatcher is the root-cause
@@ -1234,10 +1465,10 @@ func TestCancelStaleStartupTasks(t *testing.T) {
now := time.Now()
old := now.Add(-2 * time.Hour)
- mk("old-startup", startupTaskType, tasks.TaskStatusPending, old) // -> cancelled
- mk("young-startup", startupTaskType, tasks.TaskStatusPending, now) // too young -> kept
- mk("old-other", "chat_message", tasks.TaskStatusPending, old) // wrong type -> kept
- mk("old-claimed", startupTaskType, tasks.TaskStatusAssigned, old) // claimed (not pending) -> kept
+ mk("old-startup", startupTaskType, tasks.TaskStatusPending, old) // -> cancelled
+ mk("young-startup", startupTaskType, tasks.TaskStatusPending, now) // too young -> kept
+ mk("old-other", "chat_message", tasks.TaskStatusPending, old) // wrong type -> kept
+ mk("old-claimed", startupTaskType, tasks.TaskStatusAssigned, old) // claimed (not pending) -> kept
n, err := service.CancelStaleStartupTasks(ctx, time.Hour)
if err != nil {
diff --git a/server/internal/router/badger_router.go b/server/internal/router/badger_router.go
index bf6fd47..651ffec 100644
--- a/server/internal/router/badger_router.go
+++ b/server/internal/router/badger_router.go
@@ -178,14 +178,33 @@ func (r *BadgerRouter) Publish(_ context.Context, topic string, payload []byte)
copy(snapshot, list)
r.mu.RUnlock()
+ // A directed topic with NO live subscriber is the silent-loss case: the
+ // message is durably appended, the loop below simply does not execute, and
+ // Publish returns nil — so the caller (and the gateway's routeMessage,
+ // which only logs on publish *error*) sees a completely successful send
+ // while nothing was delivered. An agent that is connected but whose
+ // subscription never registered is indistinguishable from a healthy one
+ // without this. Warn rather than Debug: for an identity topic this is
+ // always a fault, not a normal state.
+ if len(snapshot) == 0 {
+ logging.Logger.Warn().Str("topic", topic).Uint64("seq", seq).
+ Msg("badger_router: published to topic with NO live subscribers (persisted only, not delivered)")
+ } else {
+ logging.Logger.Debug().Str("topic", topic).Uint64("seq", seq).
+ Int("subscribers", len(snapshot)).
+ Msg("badger_router: publish fan-out")
+ }
+
for _, s := range snapshot {
select {
case <-s.done:
- // subscriber gone; skip
+ logging.Logger.Warn().Str("topic", topic).Str("consumer", s.name).Uint64("seq", seq).
+ Msg("badger_router: subscriber already done, dropping message")
case s.ch <- msgWithSeq{payload: payload, seq: seq}:
// delivered to drain goroutine
default:
- logging.Logger.Warn().Str("topic", topic).Str("consumer", s.name).
+ logging.Logger.Warn().Str("topic", topic).Str("consumer", s.name).Uint64("seq", seq).
+ Int("buffer", cap(s.ch)).
Msg("badger_router: subscriber channel full, dropping message")
}
}
@@ -264,6 +283,13 @@ func (r *BadgerRouter) subscribe(topic, consumerName string, handler func([]byte
if exclusive {
lockKey := topic + "\x00" + consumerName
if _, loaded := r.exclusiveLocks.LoadOrStore(lockKey, struct{}{}); loaded {
+ // consumerName is the identity string, which is IDENTICAL across
+ // every incarnation of a given agent — so a lock leaked by a prior
+ // session blocks all future subscriptions to this topic until the
+ // gateway restarts. Log it here: the error is returned to a caller
+ // that may only surface it generically.
+ logging.Logger.Warn().Str("topic", topic).Str("consumer", consumerName).
+ Msg("badger_router: exclusive consumer already active; subscription REJECTED")
return nil, fmt.Errorf("badger_router: exclusive consumer %q already active on topic %q", consumerName, topic)
}
}
@@ -341,8 +367,18 @@ func (r *BadgerRouter) subscribe(topic, consumerName string, handler func([]byte
// we record replayedUpTo so drain can discard duplicates.
r.mu.Lock()
r.subs[topic] = append(r.subs[topic], s)
+ subCount := len(r.subs[topic])
r.mu.Unlock()
+ // Pairs with the fan-out log in Publish: together these answer "was anyone
+ // listening on the topic the message went to?" without having to infer it
+ // from the absence of other logs.
+ logging.Logger.Debug().
+ Str("topic", topic).Str("consumer", consumerName).
+ Bool("exclusive", exclusive).Int("policy", int(policy)).
+ Uint64("start_seq", startSeq).Int("subscribers", subCount).
+ Msg("badger_router: subscriber registered")
+
// Replay historical messages synchronously. Any concurrent Publish calls
// queue into s.ch. We track the highest sequence replayed so that drain
// can skip those duplicates.
@@ -358,6 +394,14 @@ func (r *BadgerRouter) subscribe(topic, consumerName string, handler func([]byte
// Tell drain to skip any live messages that were already delivered by replay.
s.replayedUpTo = replayedUpTo
+ // A large replay on connect is itself a suspect: replay pushes straight at
+ // the handler, which enqueues non-blocking into the client's delivery
+ // buffer, so a backlog can shed the very messages that follow it.
+ logging.Logger.Debug().
+ Str("topic", topic).Str("consumer", consumerName).
+ Uint64("start_seq", startSeq).Uint64("replayed_up_to", replayedUpTo).
+ Msg("badger_router: replay complete")
+
// Persist the consumer offset for the replayed range. drain saves the offset
// per live message, but replay (the reconnect catch-up path) did not — so a
// named consumer that caught up via replay never committed its progress, and
diff --git a/server/internal/storage/acl/conformance_test.go b/server/internal/storage/acl/conformance_test.go
index 40b758f..ca618aa 100644
--- a/server/internal/storage/acl/conformance_test.go
+++ b/server/internal/storage/acl/conformance_test.go
@@ -380,6 +380,45 @@ func runAuthorityGrantLifecycle(t *testing.T, store acl.Store) {
if revoked.RevokedAt == nil {
t.Fatalf("expected RevokedAt to be populated after revoke")
}
+
+ // WorkflowEngine schedules use a private audience type. Keep it in the
+ // shared conformance suite so native and external stores cannot drift from
+ // the authority model accepted by the gateway.
+ scheduleReq := req
+ scheduleReq.AudienceType = acl.AuthorityAudienceWorkflowSchedule
+ scheduleActor := models.Identity{Type: models.PrincipalWorkflowEngine}
+ scheduleReq.Delegate = scheduleActor
+ scheduleReq.AudienceID = uniqueID(t, "schedule")
+ scheduleReq.Reason = "conformance workflow schedule grant"
+ scheduleGrant, err := store.CreateAuthorityGrant(ctx, scheduleReq)
+ if err != nil {
+ t.Fatalf("CreateAuthorityGrant workflow_schedule audience: %v", err)
+ }
+ scheduleGot, err := store.GetAuthorityGrant(ctx, scheduleGrant.GrantID)
+ if err != nil {
+ t.Fatalf("GetAuthorityGrant workflow_schedule audience: %v", err)
+ }
+ if scheduleGot.AudienceType != acl.AuthorityAudienceWorkflowSchedule ||
+ scheduleGot.AudienceID != scheduleReq.AudienceID {
+ t.Fatalf("workflow_schedule audience = %q:%q, want %q:%q",
+ scheduleGot.AudienceType, scheduleGot.AudienceID,
+ scheduleReq.AudienceType, scheduleReq.AudienceID)
+ }
+ resolved, err := store.ResolveAuthority(ctx, scheduleActor, acl.RequestAuthorityContext{
+ Mode: "on_behalf_of", Subject: subject, GrantID: scheduleGrant.GrantID,
+ }, acl.GrantAudienceContext{
+ Actor: scheduleActor, WorkflowScheduleID: scheduleReq.AudienceID,
+ })
+ if err != nil {
+ t.Fatalf("ResolveAuthority workflow_schedule audience: %v", err)
+ }
+ if resolved == nil || resolved.Grant == nil || resolved.Grant.GrantID != scheduleGrant.GrantID {
+ t.Fatalf("ResolveAuthority workflow_schedule audience = %+v, want grant %q",
+ resolved, scheduleGrant.GrantID)
+ }
+ if err := store.RevokeAuthorityGrant(ctx, scheduleGrant.GrantID); err != nil {
+ t.Fatalf("RevokeAuthorityGrant workflow_schedule audience: %v", err)
+ }
}
// runAuthorityRequestLifecycle is the Phase 2 Stage A sanity test for the
diff --git a/server/internal/storage/acl/sqlite/store.go b/server/internal/storage/acl/sqlite/store.go
index 2865ea0..a641f05 100644
--- a/server/internal/storage/acl/sqlite/store.go
+++ b/server/internal/storage/acl/sqlite/store.go
@@ -1681,6 +1681,11 @@ func validateGrantAudience(grant *aclstore.AuthorityGrant, actor models.Identity
if actor.Type != models.PrincipalService || actor.CanonicalPrincipalID() != grant.AudienceID {
return aclstore.ErrAuthorityGrantAudienceMismatch
}
+ case aclstore.AuthorityAudienceWorkflowSchedule:
+ if actor.Type != models.PrincipalWorkflowEngine || audience.WorkflowScheduleID == "" ||
+ grant.AudienceID != audience.WorkflowScheduleID {
+ return aclstore.ErrAuthorityGrantAudienceMismatch
+ }
default:
return aclstore.ErrAuthorityGrantAudienceMismatch
}
@@ -1740,7 +1745,8 @@ func authorityPrincipalRef(identity models.Identity) (string, string, error) {
func isValidAuthorityAudienceType(audienceType string) bool {
switch audienceType {
case aclstore.AuthorityAudienceSession, aclstore.AuthorityAudienceTask,
- aclstore.AuthorityAudienceAgent, aclstore.AuthorityAudienceService:
+ aclstore.AuthorityAudienceAgent, aclstore.AuthorityAudienceService,
+ aclstore.AuthorityAudienceWorkflowSchedule:
return true
default:
return false
diff --git a/server/internal/storage/acl/types.go b/server/internal/storage/acl/types.go
index 40a87c8..09179c6 100644
--- a/server/internal/storage/acl/types.go
+++ b/server/internal/storage/acl/types.go
@@ -121,15 +121,18 @@ const (
// Resource types — acl_rules.resource_type values. Aliased from
// pkg/models.ResourceType* via the legacy package.
const (
- ResourceTypeWorkspace = legacy.ResourceTypeWorkspace
- ResourceTypeAgent = legacy.ResourceTypeAgent
- ResourceTypePermission = legacy.ResourceTypePermission // deprecated: prefer Admin/Capability
- ResourceTypeAdmin = legacy.ResourceTypeAdmin
- ResourceTypeCapability = legacy.ResourceTypeCapability
- ResourceTypeTask = legacy.ResourceTypeTask
- ResourceTypeKVScope = legacy.ResourceTypeKVScope
- ResourceTypeKVKey = legacy.ResourceTypeKVKey
- ResourceTypeServiceImpl = legacy.ResourceTypeServiceImpl
+ ResourceTypeWorkspace = legacy.ResourceTypeWorkspace
+ ResourceTypeAgent = legacy.ResourceTypeAgent
+ ResourceTypePermission = legacy.ResourceTypePermission // deprecated: prefer Admin/Capability
+ ResourceTypeAdmin = legacy.ResourceTypeAdmin
+ ResourceTypeCapability = legacy.ResourceTypeCapability
+ ResourceTypeTask = legacy.ResourceTypeTask
+ ResourceTypeKVScope = legacy.ResourceTypeKVScope
+ ResourceTypeKVKey = legacy.ResourceTypeKVKey
+ ResourceTypeServiceImpl = legacy.ResourceTypeServiceImpl
+ ResourceTypeWorkspaceExecutionView = legacy.ResourceTypeWorkspaceExecutionView
+ ResourceTypeToolCatalogProvider = legacy.ResourceTypeToolCatalogProvider
+ ResourceTypeToolCatalogEntry = legacy.ResourceTypeToolCatalogEntry
)
// Principal types — acl_rules.principal_type values (canonical lowercase).
@@ -202,10 +205,11 @@ const (
// Authority audience types — acl_authority_grants.audience_type values.
const (
- AuthorityAudienceSession = legacy.AuthorityAudienceSession
- AuthorityAudienceTask = legacy.AuthorityAudienceTask
- AuthorityAudienceAgent = legacy.AuthorityAudienceAgent
- AuthorityAudienceService = legacy.AuthorityAudienceService
+ AuthorityAudienceSession = legacy.AuthorityAudienceSession
+ AuthorityAudienceTask = legacy.AuthorityAudienceTask
+ AuthorityAudienceAgent = legacy.AuthorityAudienceAgent
+ AuthorityAudienceService = legacy.AuthorityAudienceService
+ AuthorityAudienceWorkflowSchedule = legacy.AuthorityAudienceWorkflowSchedule
)
// Sentinel errors surfaced by the Store contract.
diff --git a/server/internal/storage/workflow/conformance_test.go b/server/internal/storage/workflow/conformance_test.go
index 67307da..d9852d4 100644
--- a/server/internal/storage/workflow/conformance_test.go
+++ b/server/internal/storage/workflow/conformance_test.go
@@ -17,9 +17,11 @@ import (
"encoding/json"
"fmt"
"path/filepath"
+ "strings"
"testing"
"time"
+ pb "github.com/scitrera/aether/api/proto"
wfstore "github.com/scitrera/aether/server/internal/storage/workflow"
wfpg "github.com/scitrera/aether/server/internal/storage/workflow/postgres"
wfsqlite "github.com/scitrera/aether/server/internal/storage/workflow/sqlite"
@@ -219,6 +221,16 @@ func runSchedulesRoundTrip(t *testing.T, store wfstore.Store) {
NextFireAt: &next,
MissPolicy: "skip",
MaxConcurrent: 1,
+ Authority: &wfstore.ScheduleAuthority{
+ Authorization: &pb.AuthorizationContext{
+ AuthorityMode: "on_behalf_of",
+ Subject: &pb.PrincipalRef{PrincipalType: "user", PrincipalId: "user-" + id},
+ GrantId: "grant-" + id,
+ },
+ RootGrantID: "root-" + id, SourceGrantID: "source-" + id,
+ ExpiresAt: next.Add(24 * time.Hour), PolicyDigest: "sha256:test", PolicyVersion: 1,
+ LifetimeMode: pb.WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_DURABLE,
+ },
}
if err := store.CreateSchedule(ctx, sc); err != nil {
t.Fatalf("CreateSchedule: %v", err)
@@ -234,6 +246,85 @@ func runSchedulesRoundTrip(t *testing.T, store wfstore.Store) {
if got == nil || got.Name != "name-"+id {
t.Fatalf("GetSchedule.Name: got %+v want name-%s", got, id)
}
+ if got.Authority == nil || got.Authority.Authorization.GetGrantId() != "grant-"+id ||
+ got.Authority.PolicyVersion != 1 || got.Authority.PolicyDigest != "sha256:test" {
+ t.Fatalf("GetSchedule.Authority: got %+v", got.Authority)
+ }
+ publicJSON, err := json.Marshal(got)
+ if err != nil {
+ t.Fatalf("marshal public schedule: %v", err)
+ }
+ if strings.Contains(string(publicJSON), "grant-"+id) || strings.Contains(string(publicJSON), "sha256:test") {
+ t.Fatalf("private schedule authority leaked into JSON: %s", publicJSON)
+ }
+
+ dispatchedAt := time.Now().UTC().Truncate(time.Second)
+ coalescedAt := next.Add(-10 * time.Minute)
+ occurrence := wfstore.ScheduleOccurrence{
+ ScheduledFor: coalescedAt, DispatchedAt: &dispatchedAt,
+ Disposition: wfstore.ScheduleDispositionCoalesced,
+ BacklogCount: 4, BacklogIndex: 1,
+ }
+ if err := store.RecordScheduleOccurrence(ctx, id, occurrence, &next); err != nil {
+ t.Fatalf("RecordScheduleOccurrence fired: %v", err)
+ }
+ got, err = store.GetSchedule(ctx, id)
+ if err != nil || got == nil || got.LastFiredAt == nil || !got.LastFiredAt.Equal(dispatchedAt) || got.LastOccurrence == nil {
+ t.Fatalf("GetSchedule after fired occurrence: got=%+v err=%v", got, err)
+ }
+ if got.LastOccurrence.Disposition != wfstore.ScheduleDispositionCoalesced || got.LastOccurrence.BacklogCount != 4 ||
+ got.LastOccurrence.DispatchedAt == nil || !got.LastOccurrence.DispatchedAt.Equal(dispatchedAt) {
+ t.Fatalf("fired occurrence = %+v", got.LastOccurrence)
+ }
+
+ skippedAt := next.Add(-5 * time.Minute)
+ skipped := wfstore.ScheduleOccurrence{
+ ScheduledFor: skippedAt, Disposition: wfstore.ScheduleDispositionSkipped,
+ Reason: wfstore.ScheduleSkipReasonMissPolicy, BacklogCount: 101, BacklogTruncated: true,
+ }
+ if err := store.RecordScheduleOccurrence(ctx, id, skipped, &next); err != nil {
+ t.Fatalf("RecordScheduleOccurrence skipped: %v", err)
+ }
+ got, err = store.GetSchedule(ctx, id)
+ if err != nil || got == nil || got.LastFiredAt == nil || !got.LastFiredAt.Equal(dispatchedAt) || got.LastOccurrence == nil {
+ t.Fatalf("GetSchedule after skipped occurrence: got=%+v err=%v", got, err)
+ }
+ if got.LastOccurrence.Disposition != wfstore.ScheduleDispositionSkipped ||
+ got.LastOccurrence.Reason != wfstore.ScheduleSkipReasonMissPolicy || got.LastOccurrence.DispatchedAt != nil ||
+ got.LastOccurrence.BacklogCount != 101 || !got.LastOccurrence.BacklogTruncated {
+ t.Fatalf("skipped occurrence = %+v", got.LastOccurrence)
+ }
+
+ payloadOnlyNext := next.Add(time.Hour)
+ sc.Action = json.RawMessage(`{"hint":"updated"}`)
+ sc.NextFireAt = &payloadOnlyNext
+ if err := store.UpsertSchedule(ctx, sc); err != nil {
+ t.Fatalf("UpsertSchedule payload-only: %v", err)
+ }
+ got, err = store.GetSchedule(ctx, id)
+ if err != nil || got == nil || got.NextFireAt == nil {
+ t.Fatalf("GetSchedule after payload-only upsert: got=%+v err=%v", got, err)
+ }
+ if !got.NextFireAt.Equal(next) {
+ t.Fatalf("payload-only upsert moved next fire: got=%v want=%v", got.NextFireAt, next)
+ }
+ if got.LastOccurrence == nil || got.LastOccurrence.Disposition != wfstore.ScheduleDispositionSkipped {
+ t.Fatalf("payload-only upsert discarded occurrence state: %+v", got.LastOccurrence)
+ }
+
+ reconfiguredNext := next.Add(2 * time.Hour)
+ sc.ScheduleExpr = "15m"
+ sc.NextFireAt = &reconfiguredNext
+ if err := store.UpsertSchedule(ctx, sc); err != nil {
+ t.Fatalf("UpsertSchedule reconfigured: %v", err)
+ }
+ got, err = store.GetSchedule(ctx, id)
+ if err != nil || got == nil || got.NextFireAt == nil {
+ t.Fatalf("GetSchedule after reconfigured upsert: got=%+v err=%v", got, err)
+ }
+ if !got.NextFireAt.Equal(reconfiguredNext) {
+ t.Fatalf("reconfigured upsert retained stale next fire: got=%v want=%v", got.NextFireAt, reconfiguredNext)
+ }
listed, err := store.ListSchedules(ctx, "ws-"+id)
if err != nil {
@@ -242,6 +333,20 @@ func runSchedulesRoundTrip(t *testing.T, store wfstore.Store) {
if !containsSchedule(listed, id) {
t.Fatalf("ListSchedules did not include %s", id)
}
+ if err := store.SetScheduleAuthorityBlocked(ctx, id, "revoked"); err != nil {
+ t.Fatalf("SetScheduleAuthorityBlocked: %v", err)
+ }
+ got, err = store.GetSchedule(ctx, id)
+ if err != nil || got == nil || got.Authority == nil || !got.Authority.Blocked || got.Authority.BlockedReason != "revoked" {
+ t.Fatalf("blocked schedule authority: got=%+v err=%v", got, err)
+ }
+ due, err := store.GetDueSchedules(ctx, reconfiguredNext.Add(time.Second))
+ if err != nil {
+ t.Fatalf("GetDueSchedules blocked: %v", err)
+ }
+ if containsSchedule(due, id) {
+ t.Fatalf("blocked schedule %s was returned as due", id)
+ }
if err := store.DeleteSchedule(ctx, id); err != nil {
t.Fatalf("DeleteSchedule: %v", err)
diff --git a/server/internal/storage/workflow/sqlite/store.go b/server/internal/storage/workflow/sqlite/store.go
index 730bcf9..2cb894e 100644
--- a/server/internal/storage/workflow/sqlite/store.go
+++ b/server/internal/storage/workflow/sqlite/store.go
@@ -30,6 +30,7 @@ import (
"github.com/rs/zerolog/log"
+ pb "github.com/scitrera/aether/api/proto"
workflow "github.com/scitrera/aether/server/internal/storage/workflow"
migrations "github.com/scitrera/aether/server/migrations/sqlite_workflow"
@@ -640,11 +641,18 @@ func (s *Store) GetDueSchedules(ctx context.Context, now time.Time) ([]Schedule,
// is the only consumer of due schedules in lite mode.
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
- WHERE enabled = 1 AND next_fire_at IS NOT NULL AND next_fire_at <= ?
+ WHERE enabled = 1 AND authority_blocked = 0
+ AND next_fire_at IS NOT NULL AND next_fire_at <= ?
ORDER BY next_fire_at ASC
`
rows, err := s.db.QueryContext(ctx, query, formatTime(now))
@@ -660,14 +668,22 @@ func (s *Store) CreateSchedule(ctx context.Context, sc *Schedule) error {
query := `
INSERT INTO workflow_schedules (id, name, workspace, schedule_type, schedule_expr, action,
workflow_id, enabled, next_fire_at, miss_policy, max_concurrent,
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?, ?, ?,
+ NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''),
+ ?, NULLIF(?, ''), ?, ?, ?, ?, ?, ?)
RETURNING created_at, updated_at
`
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason := sqliteScheduleAuthorityValues(sc.Authority)
var createdAtStr, updatedAtStr string
err := s.db.QueryRowContext(ctx, query,
sc.ID, sc.Name, sc.Workspace, sc.ScheduleType, sc.ScheduleExpr, sc.Action,
sc.WorkflowID, boolToInt(sc.Enabled), formatTimePtr(sc.NextFireAt), sc.MissPolicy, sc.MaxConcurrent,
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason,
now, now,
).Scan(&createdAtStr, &updatedAtStr)
if err != nil {
@@ -686,8 +702,14 @@ func (s *Store) DeleteSchedule(ctx context.Context, id string) error {
func (s *Store) ListSchedules(ctx context.Context, workspace string) ([]Schedule, error) {
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
WHERE workspace = ? OR workspace = '*'
@@ -704,8 +726,14 @@ func (s *Store) ListSchedules(ctx context.Context, workspace string) ([]Schedule
func (s *Store) GetSchedule(ctx context.Context, id string) (*Schedule, error) {
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
WHERE id = ?
@@ -731,24 +759,49 @@ func (s *Store) UpsertSchedule(ctx context.Context, sc *Schedule) error {
query := `
INSERT INTO workflow_schedules (id, name, workspace, schedule_type, schedule_expr, action,
workflow_id, enabled, next_fire_at, miss_policy, max_concurrent,
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, NULLIF(?, ''), ?, ?, ?, ?,
+ NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''), NULLIF(?, ''),
+ ?, NULLIF(?, ''), ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
workspace = EXCLUDED.workspace,
+ next_fire_at = CASE
+ WHEN workflow_schedules.schedule_type <> EXCLUDED.schedule_type
+ OR workflow_schedules.schedule_expr <> EXCLUDED.schedule_expr
+ THEN EXCLUDED.next_fire_at
+ ELSE workflow_schedules.next_fire_at
+ END,
schedule_type = EXCLUDED.schedule_type,
schedule_expr = EXCLUDED.schedule_expr,
action = EXCLUDED.action,
workflow_id = EXCLUDED.workflow_id,
enabled = EXCLUDED.enabled,
miss_policy = EXCLUDED.miss_policy,
- max_concurrent = EXCLUDED.max_concurrent
+ max_concurrent = EXCLUDED.max_concurrent,
+ authority_grant_id = EXCLUDED.authority_grant_id,
+ authority_subject_type = EXCLUDED.authority_subject_type,
+ authority_subject_id = EXCLUDED.authority_subject_id,
+ authority_root_grant_id = EXCLUDED.authority_root_grant_id,
+ authority_source_grant_id = EXCLUDED.authority_source_grant_id,
+ authority_expires_at = EXCLUDED.authority_expires_at,
+ authority_policy_digest = EXCLUDED.authority_policy_digest,
+ authority_policy_version = EXCLUDED.authority_policy_version,
+ authority_lifetime_mode = EXCLUDED.authority_lifetime_mode,
+ authority_blocked = EXCLUDED.authority_blocked,
+ authority_blocked_reason = EXCLUDED.authority_blocked_reason
RETURNING created_at, updated_at
`
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason := sqliteScheduleAuthorityValues(sc.Authority)
var createdAtStr, updatedAtStr string
err := s.db.QueryRowContext(ctx, query,
sc.ID, sc.Name, sc.Workspace, sc.ScheduleType, sc.ScheduleExpr, sc.Action,
sc.WorkflowID, boolToInt(sc.Enabled), formatTimePtr(sc.NextFireAt), sc.MissPolicy, sc.MaxConcurrent,
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason,
now, now,
).Scan(&createdAtStr, &updatedAtStr)
if err != nil {
@@ -759,9 +812,17 @@ func (s *Store) UpsertSchedule(ctx context.Context, sc *Schedule) error {
return nil
}
-func (s *Store) UpdateScheduleAfterFire(ctx context.Context, id string, lastFired time.Time, nextFire *time.Time) error {
- query := `UPDATE workflow_schedules SET last_fired_at = ?, next_fire_at = ? WHERE id = ?`
- _, err := s.db.ExecContext(ctx, query, formatTime(lastFired), formatTimePtr(nextFire), id)
+func (s *Store) RecordScheduleOccurrence(ctx context.Context, id string, occurrence ScheduleOccurrence, nextFire *time.Time) error {
+ query := `UPDATE workflow_schedules SET
+ last_fired_at = COALESCE(?, last_fired_at), next_fire_at = ?,
+ last_occurrence_at = ?, last_occurrence_disposition = ?,
+ last_occurrence_reason = ?, last_backlog_count = ?,
+ last_backlog_truncated = ?, last_backlog_index = ?
+ WHERE id = ?`
+ _, err := s.db.ExecContext(ctx, query,
+ formatTimePtr(occurrence.DispatchedAt), formatTimePtr(nextFire), formatTime(occurrence.ScheduledFor),
+ occurrence.Disposition, occurrence.Reason, occurrence.BacklogCount,
+ boolToInt(occurrence.BacklogTruncated), occurrence.BacklogIndex, id)
return err
}
@@ -771,6 +832,12 @@ func (s *Store) SetScheduleActiveTask(ctx context.Context, scheduleID, taskID st
return err
}
+func (s *Store) SetScheduleAuthorityBlocked(ctx context.Context, scheduleID, reason string) error {
+ query := `UPDATE workflow_schedules SET authority_blocked = 1, authority_blocked_reason = ? WHERE id = ?`
+ _, err := s.db.ExecContext(ctx, query, reason, scheduleID)
+ return err
+}
+
// scanSchedules scans multiple schedule rows. Handles inline time.Time
// parsing for all timestamp columns including the §15.4 trap: next_fire_at
// is a *time.Time that may be NULL.
@@ -779,12 +846,22 @@ func scanSchedules(rows *sql.Rows) ([]Schedule, error) {
for rows.Next() {
var sc Schedule
var enabledInt int
- var nextFireAtRaw, lastFiredAtRaw sql.NullString
+ var nextFireAtRaw, lastFiredAtRaw, occurrenceAtRaw sql.NullString
+ var disposition, reason string
+ var backlogCount, backlogTruncatedInt, backlogIndex int
+ var grantID, subjectType, subjectID, rootGrantID, sourceGrantID sql.NullString
+ var authorityExpiresAtRaw, policyDigest sql.NullString
+ var policyVersion, lifetimeMode sql.NullInt64
+ var authorityBlocked int
+ var blockedReason string
var createdAtStr, updatedAtStr string
if err := rows.Scan(
&sc.ID, &sc.Name, &sc.Workspace, &sc.ScheduleType, &sc.ScheduleExpr, &sc.Action,
- &sc.WorkflowID, &enabledInt, &nextFireAtRaw, &lastFiredAtRaw, &sc.MissPolicy,
+ &sc.WorkflowID, &enabledInt, &nextFireAtRaw, &lastFiredAtRaw,
+ &occurrenceAtRaw, &disposition, &reason, &backlogCount, &backlogTruncatedInt, &backlogIndex, &sc.MissPolicy,
&sc.MaxConcurrent, &sc.ActiveTaskID,
+ &grantID, &subjectType, &subjectID, &rootGrantID, &sourceGrantID, &authorityExpiresAtRaw,
+ &policyDigest, &policyVersion, &lifetimeMode, &authorityBlocked, &blockedReason,
&createdAtStr, &updatedAtStr,
); err != nil {
return nil, fmt.Errorf("scan schedule: %w", err)
@@ -805,11 +882,47 @@ func scanSchedules(rows *sql.Rows) ([]Schedule, error) {
sc.LastFiredAt = &t
}
}
+ if occurrenceAtRaw.Valid {
+ if occurredAt, parseErr := parseTime(occurrenceAtRaw.String); parseErr == nil {
+ sc.LastOccurrence = &ScheduleOccurrence{
+ ScheduledFor: occurredAt, Disposition: disposition, Reason: reason,
+ BacklogCount: backlogCount, BacklogTruncated: backlogTruncatedInt != 0, BacklogIndex: backlogIndex,
+ }
+ if disposition != workflow.ScheduleDispositionSkipped && sc.LastFiredAt != nil {
+ dispatchedAt := *sc.LastFiredAt
+ sc.LastOccurrence.DispatchedAt = &dispatchedAt
+ }
+ }
+ }
+ if grantID.Valid {
+ expiresAt, _ := parseTime(authorityExpiresAtRaw.String)
+ sc.Authority = &workflow.ScheduleAuthority{
+ Authorization: &pb.AuthorizationContext{
+ AuthorityMode: "on_behalf_of",
+ Subject: &pb.PrincipalRef{PrincipalType: subjectType.String, PrincipalId: subjectID.String},
+ GrantId: grantID.String,
+ },
+ RootGrantID: rootGrantID.String, SourceGrantID: sourceGrantID.String,
+ ExpiresAt: expiresAt, PolicyDigest: policyDigest.String, PolicyVersion: uint32(policyVersion.Int64),
+ LifetimeMode: pb.WorkflowAuthorityLifetimeMode(lifetimeMode.Int64),
+ Blocked: authorityBlocked != 0, BlockedReason: blockedReason,
+ }
+ }
schedules = append(schedules, sc)
}
return schedules, rows.Err()
}
+func sqliteScheduleAuthorityValues(authority *workflow.ScheduleAuthority) (grantID, subjectType, subjectID, rootGrantID, sourceGrantID string, expiresAt any, digest string, policyVersion uint32, lifetime any, blocked int, blockedReason string) {
+ if authority == nil || authority.Authorization == nil {
+ return "", "", "", "", "", nil, "", 0, nil, 0, ""
+ }
+ subject := authority.Authorization.GetSubject()
+ return authority.Authorization.GetGrantId(), subject.GetPrincipalType(), subject.GetPrincipalId(),
+ authority.RootGrantID, authority.SourceGrantID, formatTime(authority.ExpiresAt), authority.PolicyDigest, authority.PolicyVersion,
+ int32(authority.LifetimeMode), boolToInt(authority.Blocked), authority.BlockedReason
+}
+
// =============================================================================
// Joins — workflow_joins table
// =============================================================================
@@ -1371,7 +1484,7 @@ func isDuplicateColumnError(err error) bool {
// =============================================================================
// nullableText stores an empty string as SQL NULL so optional TEXT columns
-// (on_complete/on_timeout/on_partial_failure) stay NULL rather than '' when unset.
+// (on_complete/on_timeout/on_partial_failure) stay NULL rather than ” when unset.
func nullableText(s string) interface{} {
if s == "" {
return nil
@@ -1396,6 +1509,7 @@ type (
WorkflowExecution = workflow.WorkflowExecution
StepState = workflow.StepState
Schedule = workflow.Schedule
+ ScheduleOccurrence = workflow.ScheduleOccurrence
Join = workflow.Join
StateMachineDef = workflow.StateMachineDef
StateMachineInstance = workflow.StateMachineInstance
diff --git a/server/internal/storage/workflow/store.go b/server/internal/storage/workflow/store.go
index a8b1e86..ce73fb6 100644
--- a/server/internal/storage/workflow/store.go
+++ b/server/internal/storage/workflow/store.go
@@ -221,19 +221,26 @@ type Store interface {
GetSchedule(ctx context.Context, id string) (*Schedule, error)
// UpsertSchedule inserts a new schedule row, or updates the existing
- // row with the same id, preserving created_at across updates.
+ // row with the same id, preserving created_at and last_fired_at. It keeps
+ // next_fire_at for payload-only changes and replaces it when the schedule
+ // type or expression changes.
// Populates sc.CreatedAt and sc.UpdatedAt from the RETURNING clause.
UpsertSchedule(ctx context.Context, sc *Schedule) error
- // UpdateScheduleAfterFire stamps last_fired_at and rolls next_fire_at
- // forward (or to NULL for one-shot schedules) after a successful fire.
- UpdateScheduleAfterFire(ctx context.Context, id string, lastFired time.Time, nextFire *time.Time) error
+ // RecordScheduleOccurrence persists the latest bounded scheduler decision
+ // and rolls next_fire_at forward. A nil occurrence.DispatchedAt records a
+ // no-task skip without overwriting the prior real last_fired_at.
+ RecordScheduleOccurrence(ctx context.Context, id string, occurrence ScheduleOccurrence, nextFire *time.Time) error
// SetScheduleActiveTask records the task id currently running for the
// given schedule (NULL-equivalent when taskID is ""). Used by the
// max_concurrent=1 enforcement path.
SetScheduleActiveTask(ctx context.Context, scheduleID, taskID string) error
+ // SetScheduleAuthorityBlocked prevents future dispatch after a permanent
+ // schedule-authority failure. Re-authorizing through upsert clears it.
+ SetScheduleAuthorityBlocked(ctx context.Context, scheduleID, reason string) error
+
// =========================================================================
// Joins — workflow_joins table
// =========================================================================
diff --git a/server/internal/storage/workflow/types.go b/server/internal/storage/workflow/types.go
index 4d5fde5..3bdd5c1 100644
--- a/server/internal/storage/workflow/types.go
+++ b/server/internal/storage/workflow/types.go
@@ -29,6 +29,10 @@ type (
StepState = legacy.StepState
// Schedule is a workflow_schedules row.
Schedule = legacy.Schedule
+ // ScheduleAuthority is the private schedule authorization envelope.
+ ScheduleAuthority = legacy.ScheduleAuthority
+ // ScheduleOccurrence is the latest bounded scheduler decision.
+ ScheduleOccurrence = legacy.ScheduleOccurrence
// Join is a workflow_joins row.
Join = legacy.Join
// StateMachineDef is a workflow_state_machines row.
@@ -46,6 +50,17 @@ const (
ExecStatusCancelled = legacy.ExecStatusCancelled
)
+const (
+ ScheduleDispositionOrdinary = legacy.ScheduleDispositionOrdinary
+ ScheduleDispositionSkipped = legacy.ScheduleDispositionSkipped
+ ScheduleDispositionCoalesced = legacy.ScheduleDispositionCoalesced
+ ScheduleDispositionCatchUp = legacy.ScheduleDispositionCatchUp
+
+ ScheduleSkipReasonMissPolicy = legacy.ScheduleSkipReasonMissPolicy
+ ScheduleSkipReasonMaxConcurrent = legacy.ScheduleSkipReasonMaxConcurrent
+ ScheduleSkipReasonAuthorityInvalid = legacy.ScheduleSkipReasonAuthorityInvalid
+)
+
// Step status values — values that land in workflow_step_states.status.
const (
StepStatusPending = legacy.StepStatusPending
diff --git a/server/internal/version/version.go b/server/internal/version/version.go
index 44bae6a..3ae4162 100644
--- a/server/internal/version/version.go
+++ b/server/internal/version/version.go
@@ -2,4 +2,4 @@ package version
// Version is the current release version of the Aether gateway.
// This is updated automatically by scripts/update-versions.py.
-const Version = "0.2.2"
+const Version = "0.2.3"
diff --git a/server/internal/workflow/admin.go b/server/internal/workflow/admin.go
index 0e3ce17..d181a59 100644
--- a/server/internal/workflow/admin.go
+++ b/server/internal/workflow/admin.go
@@ -337,7 +337,11 @@ func (s *AdminServer) createSchedule(w http.ResponseWriter, r *http.Request) {
sc.Workspace = "*"
}
if sc.MissPolicy == "" {
- sc.MissPolicy = "skip"
+ sc.MissPolicy = ScheduleMissPolicySkip
+ }
+ if !validScheduleMissPolicy(sc.MissPolicy) {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "miss_policy must be skip, fire_once, or fire_all"})
+ return
}
sc.Enabled = true
diff --git a/server/internal/workflow/executor.go b/server/internal/workflow/executor.go
index f6fdca1..fe19f82 100644
--- a/server/internal/workflow/executor.go
+++ b/server/internal/workflow/executor.go
@@ -1,8 +1,10 @@
package workflow
import (
+ "context"
"encoding/json"
"fmt"
+ "time"
"github.com/rs/zerolog/log"
"github.com/vmihailenco/msgpack/v5"
@@ -11,6 +13,8 @@ import (
"github.com/scitrera/aether/sdk/go/aether"
)
+const scheduledTaskCreateTimeout = 10 * time.Second
+
// ActionDef defines an action to dispatch to an agent via Aether.
type ActionDef struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"` // "message" (default), "create_task"
@@ -22,7 +26,19 @@ type ActionDef struct {
// create_task fields
TaskType string `json:"task_type,omitempty" yaml:"task_type,omitempty"`
TargetImplementation string `json:"target_implementation,omitempty" yaml:"target_implementation,omitempty"`
- Payload any `json:"payload,omitempty" yaml:"payload,omitempty"`
+ // TargetAgentID selects TARGETED assignment when a schedule must run on one
+ // concrete worker (for example, a worker-authoritative filesystem view).
+ // Empty preserves the historical implementation-pooled assignment.
+ TargetAgentID string `json:"target_agent_id,omitempty" yaml:"target_agent_id,omitempty"`
+ // TargetOfflinePolicy controls exact-target behavior while that identity is
+ // disconnected: orchestrate, queue, or reject. Empty preserves the released
+ // orchestration behavior.
+ TargetOfflinePolicy string `json:"target_offline_policy,omitempty" yaml:"target_offline_policy,omitempty"`
+ Payload any `json:"payload,omitempty" yaml:"payload,omitempty"`
+ // PayloadEncoding controls how Payload becomes CreateTaskRequest.payload.
+ // Empty or "msgpack" preserves the historical wire encoding; "json" is for
+ // versioned task envelopes shared with non-msgpack consumers.
+ PayloadEncoding string `json:"payload_encoding,omitempty" yaml:"payload_encoding,omitempty"`
// Optional retry policy for create_task actions. When set, the task
// store re-pends the task with a policy-driven next_retry_at on
// FailTask. Omitted = legacy hard-coded max_retries=3 behavior.
@@ -41,6 +57,14 @@ type ActionDef struct {
// CompletionEvent opts the spawned task into "feed B": it emits a domain
// event onto the event plane at its terminal status, which a join can gather.
CompletionEvent *CompletionEventConfig `json:"completion_event,omitempty" yaml:"completion_event,omitempty"`
+ // RequireTaskAuthority makes the schedule fail closed unless it has a
+ // private gateway-minted authority envelope. The envelope is stored outside
+ // this action JSON and is attached only to the CreateTask transport request.
+ RequireTaskAuthority bool `json:"require_task_authority,omitempty" yaml:"require_task_authority,omitempty"`
+ // RequiredDownstreamAuthorityHops reserves delegation capacity on the task
+ // grant established for the scheduled task. Transport currently accepts 0
+ // or 1.
+ RequiredDownstreamAuthorityHops uint32 `json:"required_downstream_authority_hops,omitempty" yaml:"required_downstream_authority_hops,omitempty"`
}
// CompletionEventConfig is the create_task-destination form of a task's feed-B
@@ -61,15 +85,34 @@ type ToolCallPayload struct {
// Executor dispatches actions to agents via the Aether SDK.
type Executor struct {
- client *aether.WorkflowEngineClient
- defaultWorkspace string
+ client *aether.WorkflowEngineClient
+ defaultWorkspace string
+ createScheduledTaskSync func(context.Context, string, string, aether.CreateTaskOptions, time.Duration) (*aether.CreateTaskResponse, error)
+}
+
+// ScheduleAuthorityInvalidError marks a permanent authorization failure. The
+// scheduler blocks the schedule and records a no-task skip instead of retrying
+// the same invalid credential on every poll.
+type ScheduleAuthorityInvalidError struct {
+ Code string
+ Message string
+}
+
+func (e *ScheduleAuthorityInvalidError) Error() string {
+ if e.Code == "" {
+ return e.Message
+ }
+ return e.Code + ": " + e.Message
}
func NewExecutor(client *aether.WorkflowEngineClient, defaultWorkspace string) *Executor {
- return &Executor{
- client: client,
- defaultWorkspace: defaultWorkspace,
+ executor := &Executor{
+ client: client, defaultWorkspace: defaultWorkspace,
+ }
+ if client != nil {
+ executor.createScheduledTaskSync = client.CreateTaskSync
}
+ return executor
}
// DispatchAction routes an action based on its Type field.
@@ -84,6 +127,79 @@ func (e *Executor) DispatchAction(action *ActionDef) error {
}
}
+// DispatchScheduledAction confirms scheduled task creation before the scheduler
+// advances its durable occurrence cursor. A lost response leaves the cursor due;
+// the retry uses the scheduler's per-occurrence idempotency key and converges on
+// the already-created task instead of creating a duplicate. Non-task actions
+// retain their existing dispatch behavior.
+func (e *Executor) DispatchScheduledAction(ctx context.Context, action *ActionDef, scheduleID string, authorization *pb.AuthorizationContext) error {
+ if action == nil {
+ return fmt.Errorf("scheduled action is required")
+ }
+ if action.Type != "create_task" {
+ return e.DispatchAction(action)
+ }
+ if action.RequireTaskAuthority && authorization == nil {
+ return &ScheduleAuthorityInvalidError{Code: "ERR_AUTHORITY_REQUIRED", Message: "schedule action requires task authority"}
+ }
+ request, err := buildCreateTaskRequest(action, e.defaultWorkspace)
+ if err != nil {
+ return err
+ }
+ createTask := e.createScheduledTaskSync
+ if createTask == nil {
+ if e.client == nil {
+ return fmt.Errorf("scheduled task client is not configured")
+ }
+ createTask = e.client.CreateTaskSync
+ }
+ response, err := createTask(ctx, request.TaskType, request.Workspace, aether.CreateTaskOptions{
+ TargetAgentID: request.TargetAgentId,
+ TargetOfflinePolicy: request.TargetOfflinePolicy,
+ TargetIdentity: request.TargetIdentity,
+ TargetImplementation: request.TargetImplementation,
+ LaunchParamOverrides: request.LaunchParamOverrides,
+ Metadata: request.Metadata,
+ Payload: request.Payload,
+ AssignmentMode: aether.TaskAssignmentMode(request.AssignmentMode.String()),
+ TaskClass: request.TaskClass,
+ ContextID: request.ContextId,
+ RetryPolicy: request.RetryPolicy,
+ Priority: request.Priority,
+ IdempotencyKey: request.IdempotencyKey,
+ CorrelationID: request.CorrelationId,
+ RootTaskID: request.RootTaskId,
+ CompletionEvent: request.CompletionEvent,
+ ParentTaskID: request.ParentTaskId,
+ Authorization: authorization,
+ RequiredDownstreamAuthorityHops: action.RequiredDownstreamAuthorityHops,
+ OriginatingScheduleID: scheduleID,
+ }, scheduledTaskCreateTimeout)
+ if err != nil {
+ return fmt.Errorf("confirm scheduled task creation: %w", err)
+ }
+ if response == nil || !response.Success {
+ message := "no response"
+ if response != nil && response.ErrorMessage != "" {
+ message = response.ErrorMessage
+ }
+ if response != nil && isPermanentScheduleAuthorityCode(response.ErrorCode) {
+ return &ScheduleAuthorityInvalidError{Code: response.ErrorCode, Message: message}
+ }
+ return fmt.Errorf("scheduled task creation was rejected: %s", message)
+ }
+ return nil
+}
+
+func isPermanentScheduleAuthorityCode(code string) bool {
+ switch code {
+ case "ERR_AUTHORITY_INVALID", "ERR_AUTHORITY_REQUIRED", "ERR_PERMISSION_DENIED":
+ return true
+ default:
+ return false
+ }
+}
+
// dispatchMessage sends a tool call message to the target agent.
func (e *Executor) dispatchMessage(action *ActionDef) error {
if action.Agent == "" {
@@ -123,33 +239,64 @@ func (e *Executor) dispatchMessage(action *ActionDef) error {
// dispatchCreateTask creates an Aether task from a schedule action.
func (e *Executor) dispatchCreateTask(action *ActionDef) error {
- if action.TaskType == "" {
- return fmt.Errorf("task_type is required for create_task action")
+ request, err := buildCreateTaskRequest(action, e.defaultWorkspace)
+ if err != nil {
+ return err
}
+ log.Debug().
+ Str("task_type", request.TaskType).
+ Str("workspace", request.Workspace).
+ Str("target_impl", request.TargetImplementation).
+ Str("target_agent", request.TargetAgentId).
+ Msg("dispatching create_task action")
+ return e.client.Send(&pb.UpstreamMessage{
+ Payload: &pb.UpstreamMessage_CreateTask{CreateTask: request},
+ })
+}
+
+func buildCreateTaskRequest(action *ActionDef, defaultWorkspace string) (*pb.CreateTaskRequest, error) {
+ if action == nil {
+ return nil, fmt.Errorf("create_task action is required")
+ }
+ if action.TaskType == "" {
+ return nil, fmt.Errorf("task_type is required for create_task action")
+ }
workspace := action.Workspace
if workspace == "" {
- workspace = e.defaultWorkspace
+ workspace = defaultWorkspace
}
-
var payload []byte
if action.Payload != nil {
var err error
- payload, err = msgpack.Marshal(action.Payload)
- if err != nil {
- return fmt.Errorf("msgpack marshal create_task payload: %w", err)
+ switch action.PayloadEncoding {
+ case "", "msgpack":
+ payload, err = msgpack.Marshal(action.Payload)
+ if err != nil {
+ return nil, fmt.Errorf("msgpack marshal create_task payload: %w", err)
+ }
+ case "json":
+ payload, err = json.Marshal(action.Payload)
+ if err != nil {
+ return nil, fmt.Errorf("JSON marshal create_task payload: %w", err)
+ }
+ default:
+ return nil, fmt.Errorf("unsupported create_task payload_encoding %q", action.PayloadEncoding)
}
}
-
- targetImpl := action.TargetImplementation
- metadata := action.Metadata
-
- log.Debug().
- Str("task_type", action.TaskType).
- Str("workspace", workspace).
- Str("target_impl", targetImpl).
- Msg("dispatching create_task action")
-
+ assignmentMode := pb.TaskAssignmentMode_POOL
+ targetImplementation := action.TargetImplementation
+ if action.TargetAgentID != "" {
+ assignmentMode = pb.TaskAssignmentMode_TARGETED
+ targetImplementation = ""
+ }
+ offlinePolicy, err := targetOfflinePolicyToProto(action.TargetOfflinePolicy)
+ if err != nil {
+ return nil, err
+ }
+ if offlinePolicy != pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_UNSPECIFIED && assignmentMode != pb.TaskAssignmentMode_TARGETED {
+ return nil, fmt.Errorf("target_offline_policy requires target_agent_id")
+ }
var completion *pb.TaskCompletionEvent
if action.CompletionEvent != nil {
completion = &pb.TaskCompletionEvent{
@@ -157,8 +304,37 @@ func (e *Executor) dispatchCreateTask(action *ActionDef) error {
EventName: action.CompletionEvent.EventName,
}
}
+ return &pb.CreateTaskRequest{
+ TaskType: action.TaskType,
+ Workspace: workspace,
+ AssignmentMode: assignmentMode,
+ TargetImplementation: targetImplementation,
+ TargetAgentId: action.TargetAgentID,
+ Metadata: action.Metadata,
+ Payload: payload,
+ RetryPolicy: retryConfigToProto(action.Retry),
+ IdempotencyKey: action.IdempotencyKey,
+ CorrelationId: action.CorrelationID,
+ CompletionEvent: completion,
+ TaskClass: pb.TaskClass_TASK_CLASS_BACKGROUND,
+ TargetOfflinePolicy: offlinePolicy,
+ RequiredDownstreamAuthorityHops: action.RequiredDownstreamAuthorityHops,
+ }, nil
+}
- return e.CreateTaskWithType(workspace, action.TaskType, targetImpl, metadata, payload, action.Retry, action.IdempotencyKey, action.CorrelationID, completion)
+func targetOfflinePolicyToProto(value string) (pb.TargetOfflinePolicy, error) {
+ switch value {
+ case "":
+ return pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_UNSPECIFIED, nil
+ case "orchestrate":
+ return pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_ORCHESTRATE, nil
+ case "queue":
+ return pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE, nil
+ case "reject":
+ return pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_REJECT, nil
+ default:
+ return pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_UNSPECIFIED, fmt.Errorf("unsupported target_offline_policy %q", value)
+ }
}
// EmitEvent publishes a synthetic event onto the event plane (event.*) as a
@@ -252,6 +428,8 @@ func (e *Executor) DispatchTransformResult(result *TransformResult) error {
Metadata: result.Metadata,
TaskType: result.TaskType,
TargetImplementation: result.TargetImplementation,
+ TargetAgentID: result.TargetAgentID,
+ PayloadEncoding: result.PayloadEncoding,
Payload: result.Payload,
CorrelationID: result.CorrelationID,
CompletionEvent: result.CompletionEvent,
diff --git a/server/internal/workflow/executor_test.go b/server/internal/workflow/executor_test.go
new file mode 100644
index 0000000..1b8c54c
--- /dev/null
+++ b/server/internal/workflow/executor_test.go
@@ -0,0 +1,168 @@
+package workflow
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "strings"
+ "testing"
+ "time"
+
+ pb "github.com/scitrera/aether/api/proto"
+ sdk "github.com/scitrera/aether/sdk/go/aether"
+)
+
+func TestBuildCreateTaskRequestTargetsExactAgentWithJSONPayload(t *testing.T) {
+ action := &ActionDef{
+ Type: "create_task",
+ TaskType: "agent-harness.scheduled-turn.v1",
+ TargetAgentID: "ag::default::agent-harness::worker-1",
+ TargetOfflinePolicy: "queue",
+ PayloadEncoding: "json",
+ Payload: map[string]any{
+ "schema": "agent-harness.scheduled-turn.v1",
+ "binding": map[string]any{
+ "workspace_id": "project-a",
+ "view_id": "view-a",
+ },
+ },
+ Metadata: map[string]string{"scitrera.schedule_id": "daily-review"},
+ }
+
+ request, err := buildCreateTaskRequest(action, "default")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if request.AssignmentMode != pb.TaskAssignmentMode_TARGETED {
+ t.Fatalf("assignment mode = %v", request.AssignmentMode)
+ }
+ if request.TargetAgentId != action.TargetAgentID || request.TargetImplementation != "" {
+ t.Fatalf("target agent=%q implementation=%q", request.TargetAgentId, request.TargetImplementation)
+ }
+ if request.TargetOfflinePolicy != pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE {
+ t.Fatalf("target offline policy = %v", request.TargetOfflinePolicy)
+ }
+ if request.TaskClass != pb.TaskClass_TASK_CLASS_BACKGROUND {
+ t.Fatalf("task class = %v", request.TaskClass)
+ }
+ var decoded map[string]any
+ if err := json.Unmarshal(request.Payload, &decoded); err != nil {
+ t.Fatalf("payload is not JSON: %v", err)
+ }
+ if decoded["schema"] != "agent-harness.scheduled-turn.v1" {
+ t.Fatalf("payload = %#v", decoded)
+ }
+}
+
+func TestBuildCreateTaskRequestPreservesPoolMsgpackDefaults(t *testing.T) {
+ request, err := buildCreateTaskRequest(&ActionDef{
+ Type: "create_task", TaskType: "legacy", TargetImplementation: "worker", Payload: map[string]any{"x": "y"},
+ }, "default")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if request.AssignmentMode != pb.TaskAssignmentMode_POOL || request.TargetImplementation != "worker" || request.TargetAgentId != "" {
+ t.Fatalf("legacy routing changed: %#v", request)
+ }
+ if len(request.Payload) == 0 || json.Valid(request.Payload) {
+ t.Fatalf("legacy payload did not retain msgpack encoding: %x", request.Payload)
+ }
+}
+
+func TestBuildCreateTaskRequestRejectsUnknownPayloadEncoding(t *testing.T) {
+ _, err := buildCreateTaskRequest(&ActionDef{
+ Type: "create_task", TaskType: "bad", PayloadEncoding: "yaml", Payload: map[string]any{"x": "y"},
+ }, "default")
+ if err == nil {
+ t.Fatal("unknown payload encoding was accepted")
+ }
+}
+
+func TestBuildCreateTaskRequestRejectsInvalidOfflinePolicy(t *testing.T) {
+ for name, action := range map[string]*ActionDef{
+ "unknown": {
+ Type: "create_task", TaskType: "bad", TargetAgentID: "ag::default::worker::one",
+ TargetOfflinePolicy: "eventually",
+ },
+ "without exact target": {
+ Type: "create_task", TaskType: "bad", TargetImplementation: "worker",
+ TargetOfflinePolicy: "queue",
+ },
+ } {
+ t.Run(name, func(t *testing.T) {
+ if _, err := buildCreateTaskRequest(action, "default"); err == nil {
+ t.Fatal("invalid target offline policy was accepted")
+ }
+ })
+ }
+}
+
+func TestDispatchScheduledActionConfirmsTaskCreation(t *testing.T) {
+ var gotOptions sdk.CreateTaskOptions
+ executor := &Executor{
+ defaultWorkspace: "default",
+ createScheduledTaskSync: func(_ context.Context, taskType, workspace string, options sdk.CreateTaskOptions, timeout time.Duration) (*sdk.CreateTaskResponse, error) {
+ if taskType != "scheduled" || workspace != "workspace-a" || timeout != scheduledTaskCreateTimeout {
+ t.Fatalf("create args = %q %q %v", taskType, workspace, timeout)
+ }
+ gotOptions = options
+ return &sdk.CreateTaskResponse{Success: true, TaskID: "task-1"}, nil
+ },
+ }
+ action := &ActionDef{
+ Type: "create_task", TaskType: "scheduled", Workspace: "workspace-a",
+ TargetAgentID: "ag::workspace-a::worker::one", TargetOfflinePolicy: "queue",
+ PayloadEncoding: "json", Payload: map[string]string{"run": "one"},
+ Metadata: map[string]string{"scheduled_for": "now"}, IdempotencyKey: "occurrence-1",
+ RequireTaskAuthority: true, RequiredDownstreamAuthorityHops: 1,
+ }
+ authorization := &pb.AuthorizationContext{AuthorityMode: "on_behalf_of", GrantId: "schedule-grant"}
+ if err := executor.DispatchScheduledAction(context.Background(), action, "schedule-test", authorization); err != nil {
+ t.Fatal(err)
+ }
+ if gotOptions.AssignmentMode != sdk.TaskAssignmentTargeted ||
+ gotOptions.TargetAgentID != action.TargetAgentID ||
+ gotOptions.TargetOfflinePolicy != pb.TargetOfflinePolicy_TARGET_OFFLINE_POLICY_QUEUE ||
+ gotOptions.IdempotencyKey != action.IdempotencyKey ||
+ gotOptions.Authorization != authorization || gotOptions.OriginatingScheduleID != "schedule-test" ||
+ gotOptions.RequiredDownstreamAuthorityHops != 1 ||
+ gotOptions.TaskClass != pb.TaskClass_TASK_CLASS_BACKGROUND ||
+ !json.Valid(gotOptions.Payload) {
+ t.Fatalf("confirmed create options = %#v", gotOptions)
+ }
+}
+
+func TestDispatchScheduledActionClassifiesPermanentAuthorityRejection(t *testing.T) {
+ executor := &Executor{
+ defaultWorkspace: "default",
+ createScheduledTaskSync: func(context.Context, string, string, sdk.CreateTaskOptions, time.Duration) (*sdk.CreateTaskResponse, error) {
+ return &sdk.CreateTaskResponse{Success: false, ErrorCode: "ERR_AUTHORITY_INVALID", ErrorMessage: "revoked"}, nil
+ },
+ }
+ err := executor.DispatchScheduledAction(context.Background(), &ActionDef{
+ Type: "create_task", TaskType: "scheduled", RequireTaskAuthority: true,
+ }, "schedule-test", &pb.AuthorizationContext{GrantId: "grant"})
+ var authorityErr *ScheduleAuthorityInvalidError
+ if !errors.As(err, &authorityErr) || authorityErr.Code != "ERR_AUTHORITY_INVALID" {
+ t.Fatalf("dispatch error = %T %v", err, err)
+ }
+}
+
+func TestDispatchScheduledActionDoesNotConfirmRejectedOrUncertainCreation(t *testing.T) {
+ for name, create := range map[string]func(context.Context, string, string, sdk.CreateTaskOptions, time.Duration) (*sdk.CreateTaskResponse, error){
+ "rejected": func(context.Context, string, string, sdk.CreateTaskOptions, time.Duration) (*sdk.CreateTaskResponse, error) {
+ return &sdk.CreateTaskResponse{Success: false, ErrorMessage: "denied"}, nil
+ },
+ "response lost": func(context.Context, string, string, sdk.CreateTaskOptions, time.Duration) (*sdk.CreateTaskResponse, error) {
+ return nil, errors.New("timeout")
+ },
+ } {
+ t.Run(name, func(t *testing.T) {
+ executor := &Executor{defaultWorkspace: "default", createScheduledTaskSync: create}
+ err := executor.DispatchScheduledAction(context.Background(), &ActionDef{Type: "create_task", TaskType: "scheduled"}, "schedule-test", nil)
+ if err == nil || (name == "rejected" && !strings.Contains(err.Error(), "denied")) {
+ t.Fatalf("dispatch error = %v", err)
+ }
+ })
+ }
+}
diff --git a/server/internal/workflow/migrations/005_schedule_occurrence_state.sql b/server/internal/workflow/migrations/005_schedule_occurrence_state.sql
new file mode 100644
index 0000000..b6b8f49
--- /dev/null
+++ b/server/internal/workflow/migrations/005_schedule_occurrence_state.sql
@@ -0,0 +1,9 @@
+-- Bounded observability for the latest scheduler decision. Fired occurrences
+-- also carry this descriptor in task metadata; skipped occurrences have no task
+-- and therefore remain visible only on the authoritative schedule row.
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_occurrence_at TIMESTAMPTZ;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_occurrence_disposition TEXT NOT NULL DEFAULT '';
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_occurrence_reason TEXT NOT NULL DEFAULT '';
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_backlog_count INT NOT NULL DEFAULT 0;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_backlog_truncated BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS last_backlog_index INT NOT NULL DEFAULT 0;
diff --git a/server/internal/workflow/migrations/006_schedule_authority.sql b/server/internal/workflow/migrations/006_schedule_authority.sql
new file mode 100644
index 0000000..5511e46
--- /dev/null
+++ b/server/internal/workflow/migrations/006_schedule_authority.sql
@@ -0,0 +1,13 @@
+-- Private authorization for scheduled task creation. These columns are never
+-- projected into schedule JSON or task payload/metadata.
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_subject_type TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_subject_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_root_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_source_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_expires_at TIMESTAMPTZ;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_policy_digest TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_policy_version INT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_lifetime_mode INT;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_blocked BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE workflow_schedules ADD COLUMN IF NOT EXISTS authority_blocked_reason TEXT NOT NULL DEFAULT '';
diff --git a/server/internal/workflow/schedule_authority_test.go b/server/internal/workflow/schedule_authority_test.go
new file mode 100644
index 0000000..41cd239
--- /dev/null
+++ b/server/internal/workflow/schedule_authority_test.go
@@ -0,0 +1,67 @@
+package workflow
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+ "time"
+
+ pb "github.com/scitrera/aether/api/proto"
+)
+
+func TestScheduleAuthorityFromOperationFailsClosed(t *testing.T) {
+ action, _ := json.Marshal(ActionDef{
+ Type: "create_task", TaskType: "scheduled", RequireTaskAuthority: true,
+ RequiredDownstreamAuthorityHops: 1,
+ })
+ schedule := &Schedule{ID: "sched-a", Workspace: "ws-a", Action: action}
+
+ if _, err := scheduleAuthorityFromOperation(&pb.WorkflowOperation{}, schedule); err == nil ||
+ !strings.Contains(err.Error(), "requires task authority") {
+ t.Fatalf("missing authority error = %v", err)
+ }
+
+ op := &pb.WorkflowOperation{
+ ScheduleAuthorityScope: &pb.WorkflowScheduleAuthorityScope{RequiredTaskAuthorityHops: 1, PolicyVersion: 1},
+ RequestContext: &pb.WorkflowRequestContext{
+ ScheduleAuthorization: &pb.AuthorizationContext{
+ AuthorityMode: "on_behalf_of",
+ Subject: &pb.PrincipalRef{PrincipalType: "user", PrincipalId: "user-a"},
+ GrantId: "private-schedule-grant",
+ },
+ RootGrantId: "root-a", SourceGrantId: "source-a",
+ ExpiresAtMs: time.Now().Add(time.Hour).UnixMilli(), PolicyDigest: "sha256:test", PolicyVersion: 1,
+ LifetimeMode: pb.WorkflowAuthorityLifetimeMode_WORKFLOW_AUTHORITY_LIFETIME_DURABLE,
+ },
+ }
+ authority, err := scheduleAuthorityFromOperation(op, schedule)
+ if err != nil {
+ t.Fatalf("scheduleAuthorityFromOperation: %v", err)
+ }
+ if authority.Authorization.GetGrantId() != "private-schedule-grant" || authority.PolicyVersion != 1 ||
+ authority.RootGrantID != "root-a" || authority.SourceGrantID != "source-a" {
+ t.Fatalf("authority = %+v", authority)
+ }
+
+ op.RequestContext.PolicyVersion = 2
+ if _, err := scheduleAuthorityFromOperation(op, schedule); err == nil || !strings.Contains(err.Error(), "policy version") {
+ t.Fatalf("unknown policy version error = %v", err)
+ }
+}
+
+func TestScheduleAuthorityNeverMarshalsWithPublicSchedule(t *testing.T) {
+ schedule := Schedule{
+ ID: "sched-a", Workspace: "ws-a",
+ Authority: &ScheduleAuthority{
+ Authorization: &pb.AuthorizationContext{GrantId: "secret-grant"},
+ PolicyDigest: "secret-digest", PolicyVersion: 1,
+ },
+ }
+ data, err := json.Marshal(schedule)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(data), "secret-grant") || strings.Contains(string(data), "secret-digest") {
+ t.Fatalf("private authority leaked: %s", data)
+ }
+}
diff --git a/server/internal/workflow/scheduler.go b/server/internal/workflow/scheduler.go
index 03b3fa1..a3dccd3 100644
--- a/server/internal/workflow/scheduler.go
+++ b/server/internal/workflow/scheduler.go
@@ -2,13 +2,35 @@ package workflow
import (
"context"
+ "crypto/sha256"
+ "encoding/hex"
"encoding/json"
+ "errors"
+ "strconv"
"time"
"github.com/robfig/cron/v3"
"github.com/rs/zerolog/log"
+ pb "github.com/scitrera/aether/api/proto"
)
+const (
+ maxScheduleCatchUpPerPoll = 100
+
+ scheduleMetadataID = "aether.schedule.id"
+ scheduleMetadataScheduledFor = "aether.schedule.scheduled_for"
+ scheduleMetadataDispatchedAt = "aether.schedule.dispatched_at"
+ scheduleMetadataMissPolicy = "aether.schedule.miss_policy"
+ scheduleMetadataDisposition = "aether.schedule.disposition"
+ scheduleMetadataBacklogCount = "aether.schedule.backlog_count"
+ scheduleMetadataBacklogTruncated = "aether.schedule.backlog_truncated"
+ scheduleMetadataBacklogIndex = "aether.schedule.backlog_index"
+)
+
+type scheduleActionDispatcher interface {
+ DispatchScheduledAction(ctx context.Context, action *ActionDef, scheduleID string, authorization *pb.AuthorizationContext) error
+}
+
// joinDeadlineHandler fires the timeout path for an open join whose deadline
// has elapsed. *JoinEngine satisfies it.
type joinDeadlineHandler interface {
@@ -18,15 +40,16 @@ type joinDeadlineHandler interface {
// Scheduler handles recurring and one-time scheduled tasks.
type Scheduler struct {
store WorkflowStore
- executor *Executor
+ executor scheduleActionDispatcher
dagEng *DAGEngine
leader LeaderElector
joins joinDeadlineHandler
parser cron.Parser
interval time.Duration
+ now func() time.Time
}
-func NewScheduler(store WorkflowStore, executor *Executor, dagEng *DAGEngine, leader LeaderElector, joins joinDeadlineHandler, pollInterval time.Duration) *Scheduler {
+func NewScheduler(store WorkflowStore, executor scheduleActionDispatcher, dagEng *DAGEngine, leader LeaderElector, joins joinDeadlineHandler, pollInterval time.Duration) *Scheduler {
return &Scheduler{
store: store,
executor: executor,
@@ -35,6 +58,7 @@ func NewScheduler(store WorkflowStore, executor *Executor, dagEng *DAGEngine, le
joins: joins,
parser: cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor),
interval: pollInterval,
+ now: time.Now,
}
}
@@ -62,13 +86,19 @@ func (s *Scheduler) Run(ctx context.Context) {
}
func (s *Scheduler) poll(ctx context.Context) error {
- now := time.Now()
+ now := s.now()
schedules, err := s.store.GetDueSchedules(ctx, now)
if err != nil {
return err
}
for _, sc := range schedules {
+ dueAt := now
+ if sc.NextFireAt != nil {
+ dueAt = *sc.NextFireAt
+ }
+ backlogCount, backlogTruncated := s.measureBacklog(sc, dueAt, now)
+
// Concurrency control: if max_concurrent=1 and a task is active, check staleness
if sc.MaxConcurrent == 1 && sc.ActiveTaskID != "" {
if markerTime, err := time.Parse(time.RFC3339, sc.ActiveTaskID); err == nil {
@@ -80,7 +110,7 @@ func (s *Scheduler) poll(ctx context.Context) error {
} else {
log.Debug().Str("schedule_id", sc.ID).Msg("skipping schedule: previous task still active")
nextFire := s.advanceToFuture(sc, now)
- if err := s.store.UpdateScheduleAfterFire(ctx, sc.ID, now, nextFire); err != nil {
+ if err := s.recordSkipped(ctx, sc, dueAt, nextFire, ScheduleSkipReasonMaxConcurrent, backlogCount, backlogTruncated); err != nil {
log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to advance skipped schedule")
}
continue
@@ -89,38 +119,58 @@ func (s *Scheduler) poll(ctx context.Context) error {
// Non-timestamp marker; skip
log.Debug().Str("schedule_id", sc.ID).Msg("skipping schedule: active task marker set")
nextFire := s.advanceToFuture(sc, now)
- if err := s.store.UpdateScheduleAfterFire(ctx, sc.ID, now, nextFire); err != nil {
+ if err := s.recordSkipped(ctx, sc, dueAt, nextFire, ScheduleSkipReasonMaxConcurrent, backlogCount, backlogTruncated); err != nil {
log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to advance skipped schedule")
}
continue
}
}
- // Apply miss_policy
+ // Apply miss_policy. A single due occurrence is an ordinary on-time fire;
+ // "skip" only discards a backlog containing more than one occurrence.
+ // This distinction matters because every scheduler poll necessarily sees
+ // an occurrence after its exact due timestamp.
switch sc.MissPolicy {
- case "skip":
- // If multiple fires were missed, advance to next future time without firing
- nextFire := s.advanceToFuture(sc, now)
- if err := s.store.UpdateScheduleAfterFire(ctx, sc.ID, now, nextFire); err != nil {
- log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to advance schedule")
+ case ScheduleMissPolicySkip:
+ if backlogCount > 1 {
+ nextFire := s.advanceToFuture(sc, now)
+ if err := s.recordSkipped(ctx, sc, dueAt, nextFire, ScheduleSkipReasonMissPolicy, backlogCount, backlogTruncated); err != nil {
+ log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to advance skipped schedule backlog")
+ }
+ continue
}
- continue
- case "fire_all":
- // Fire once per missed interval, capped at 100
- count := s.countMissedFires(sc, now)
- if count > 100 {
- count = 100
+ case ScheduleMissPolicyFireAll:
+ // Advance the durable cursor after every emitted occurrence. The batch
+ // cap bounds one poll without discarding older backlog; a still-due
+ // cursor is picked up by the next poll. Per-occurrence idempotency makes
+ // a retry safe when dispatch succeeds but cursor persistence does not.
+ occurrence := dueAt
+ disposition := ScheduleDispositionOrdinary
+ if backlogCount > 1 {
+ disposition = ScheduleDispositionCatchUp
}
- for i := 0; i < count; i++ {
- if err := s.fire(ctx, sc, now); err != nil {
+ for i := 0; i < maxScheduleCatchUpPerPoll && !occurrence.After(now); i++ {
+ decision := ScheduleOccurrence{
+ ScheduledFor: occurrence, DispatchedAt: timePointer(now), Disposition: disposition,
+ BacklogCount: backlogCount, BacklogTruncated: backlogTruncated, BacklogIndex: i + 1,
+ }
+ if err := s.fire(ctx, sc, decision); err != nil {
+ if s.blockOnPermanentAuthorityError(ctx, sc, decision, err, now) {
+ break
+ }
log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to fire schedule (fire_all)")
break
}
- }
- nextFire := s.advanceToFuture(sc, now)
- if err := s.store.UpdateScheduleAfterFire(ctx, sc.ID, now, nextFire); err != nil {
- log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to update schedule after fire_all")
+ nextFire := s.calculateNextFire(sc, occurrence)
+ if err := s.store.RecordScheduleOccurrence(ctx, sc.ID, decision, nextFire); err != nil {
+ log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to update schedule during fire_all")
+ break
+ }
+ if nextFire == nil {
+ break
+ }
+ occurrence = *nextFire
}
continue
@@ -128,7 +178,18 @@ func (s *Scheduler) poll(ctx context.Context) error {
// Fire exactly once, then advance to next future time
}
- if err := s.fire(ctx, sc, now); err != nil {
+ disposition := ScheduleDispositionOrdinary
+ if backlogCount > 1 {
+ disposition = ScheduleDispositionCoalesced
+ }
+ decision := ScheduleOccurrence{
+ ScheduledFor: dueAt, DispatchedAt: timePointer(now), Disposition: disposition,
+ BacklogCount: backlogCount, BacklogTruncated: backlogTruncated, BacklogIndex: 1,
+ }
+ if err := s.fire(ctx, sc, decision); err != nil {
+ if s.blockOnPermanentAuthorityError(ctx, sc, decision, err, now) {
+ continue
+ }
log.Error().Err(err).
Str("schedule_id", sc.ID).
Str("name", sc.Name).
@@ -137,7 +198,7 @@ func (s *Scheduler) poll(ctx context.Context) error {
}
nextFire := s.advanceToFuture(sc, now)
- if err := s.store.UpdateScheduleAfterFire(ctx, sc.ID, now, nextFire); err != nil {
+ if err := s.store.RecordScheduleOccurrence(ctx, sc.ID, decision, nextFire); err != nil {
log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to update schedule after fire")
}
}
@@ -161,7 +222,66 @@ func (s *Scheduler) poll(ctx context.Context) error {
return nil
}
-func (s *Scheduler) fire(ctx context.Context, sc Schedule, now time.Time) error {
+func (s *Scheduler) blockOnPermanentAuthorityError(ctx context.Context, sc Schedule, occurrence ScheduleOccurrence, dispatchErr error, now time.Time) bool {
+ var authorityErr *ScheduleAuthorityInvalidError
+ if !errors.As(dispatchErr, &authorityErr) {
+ return false
+ }
+ reason := authorityErr.Error()
+ if err := s.store.SetScheduleAuthorityBlocked(ctx, sc.ID, reason); err != nil {
+ log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to block schedule with invalid authority")
+ return true
+ }
+ nextFire := s.advanceToFuture(sc, now)
+ if err := s.recordSkipped(ctx, sc, occurrence.ScheduledFor, nextFire,
+ ScheduleSkipReasonAuthorityInvalid, occurrence.BacklogCount, occurrence.BacklogTruncated); err != nil {
+ log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to record schedule authority skip")
+ }
+ log.Warn().Err(dispatchErr).Str("schedule_id", sc.ID).Msg("blocked schedule after permanent authority failure")
+ return true
+}
+
+// measureBacklog returns the bounded number of occurrences due at this poll.
+// The cap is one beyond the per-poll fire_all dispatch limit, which is enough to
+// say whether a completed batch still leaves work without walking an unbounded
+// outage gap.
+func (s *Scheduler) measureBacklog(sc Schedule, firstDue, now time.Time) (int, bool) {
+ const detailLimit = maxScheduleCatchUpPerPoll + 1
+ count := 1
+ current := firstDue
+ for count < detailLimit {
+ next := s.calculateNextFire(sc, current)
+ if next == nil || next.After(now) {
+ return count, false
+ }
+ current = *next
+ count++
+ }
+ next := s.calculateNextFire(sc, current)
+ return count, next != nil && !next.After(now)
+}
+
+func (s *Scheduler) recordSkipped(
+ ctx context.Context,
+ sc Schedule,
+ scheduledFor time.Time,
+ nextFire *time.Time,
+ reason string,
+ backlogCount int,
+ backlogTruncated bool,
+) error {
+ return s.store.RecordScheduleOccurrence(ctx, sc.ID, ScheduleOccurrence{
+ ScheduledFor: scheduledFor, Disposition: ScheduleDispositionSkipped, Reason: reason,
+ BacklogCount: backlogCount, BacklogTruncated: backlogTruncated,
+ }, nextFire)
+}
+
+func timePointer(value time.Time) *time.Time {
+ copy := value
+ return ©
+}
+
+func (s *Scheduler) fire(ctx context.Context, sc Schedule, occurrence ScheduleOccurrence) error {
log.Info().
Str("schedule_id", sc.ID).
Str("name", sc.Name).
@@ -171,9 +291,14 @@ func (s *Scheduler) fire(ctx context.Context, sc Schedule, now time.Time) error
// If schedule triggers a DAG, start the DAG execution
if sc.WorkflowID != "" {
triggerData, _ := json.Marshal(map[string]any{
- "schedule_id": sc.ID,
- "schedule_name": sc.Name,
- "fired_at": now.Format(time.RFC3339),
+ "schedule_id": sc.ID,
+ "schedule_name": sc.Name,
+ "scheduled_for": occurrence.ScheduledFor.UTC().Format(time.RFC3339Nano),
+ "fired_at": occurrence.DispatchedAt.UTC().Format(time.RFC3339Nano),
+ "disposition": occurrence.Disposition,
+ "backlog_count": occurrence.BacklogCount,
+ "backlog_truncated": occurrence.BacklogTruncated,
+ "backlog_index": occurrence.BacklogIndex,
})
_, err := s.dagEng.StartExecution(ctx, sc.WorkflowID, sc.Workspace, triggerData)
return err
@@ -187,14 +312,33 @@ func (s *Scheduler) fire(ctx context.Context, sc Schedule, now time.Time) error
if action.Workspace == "" {
action.Workspace = sc.Workspace
}
+ action.Metadata = cloneStringMap(action.Metadata)
+ action.Metadata[scheduleMetadataID] = sc.ID
+ action.Metadata[scheduleMetadataScheduledFor] = occurrence.ScheduledFor.UTC().Format(time.RFC3339Nano)
+ action.Metadata[scheduleMetadataDispatchedAt] = occurrence.DispatchedAt.UTC().Format(time.RFC3339Nano)
+ action.Metadata[scheduleMetadataMissPolicy] = normalizedMissPolicy(sc.MissPolicy)
+ action.Metadata[scheduleMetadataDisposition] = occurrence.Disposition
+ action.Metadata[scheduleMetadataBacklogCount] = strconv.Itoa(occurrence.BacklogCount)
+ action.Metadata[scheduleMetadataBacklogTruncated] = strconv.FormatBool(occurrence.BacklogTruncated)
+ action.Metadata[scheduleMetadataBacklogIndex] = strconv.Itoa(occurrence.BacklogIndex)
+ if action.Type == "create_task" && action.IdempotencyKey == "" {
+ action.IdempotencyKey = scheduleOccurrenceIdempotencyKey(sc, occurrence.ScheduledFor)
+ }
- if err := s.executor.DispatchAction(&action); err != nil {
+ var authorization *pb.AuthorizationContext
+ if sc.Authority != nil {
+ if !sc.Authority.ExpiresAt.After(s.now()) {
+ return &ScheduleAuthorityInvalidError{Code: "ERR_AUTHORITY_INVALID", Message: "workflow schedule authority expired"}
+ }
+ authorization = sc.Authority.Authorization
+ }
+ if err := s.executor.DispatchScheduledAction(ctx, &action, sc.ID, authorization); err != nil {
return err
}
// Track active task for concurrency control
if sc.MaxConcurrent == 1 {
- marker := now.Format(time.RFC3339)
+ marker := occurrence.DispatchedAt.Format(time.RFC3339)
if err := s.store.SetScheduleActiveTask(ctx, sc.ID, marker); err != nil {
log.Error().Err(err).Str("schedule_id", sc.ID).Msg("failed to set active task marker")
}
@@ -203,6 +347,28 @@ func (s *Scheduler) fire(ctx context.Context, sc Schedule, now time.Time) error
return nil
}
+func normalizedMissPolicy(policy string) string {
+ switch policy {
+ case ScheduleMissPolicySkip, ScheduleMissPolicyFireAll:
+ return policy
+ default:
+ return ScheduleMissPolicyFireOnce
+ }
+}
+
+func scheduleOccurrenceIdempotencyKey(sc Schedule, scheduledFor time.Time) string {
+ sum := sha256.Sum256([]byte(sc.Workspace + "\x00" + sc.ID + "\x00" + scheduledFor.UTC().Format(time.RFC3339Nano)))
+ return "schedule:" + hex.EncodeToString(sum[:])
+}
+
+func cloneStringMap(source map[string]string) map[string]string {
+ cloned := make(map[string]string, len(source)+8)
+ for key, value := range source {
+ cloned[key] = value
+ }
+ return cloned
+}
+
func (s *Scheduler) calculateNextFire(sc Schedule, now time.Time) *time.Time {
switch sc.ScheduleType {
case ScheduleTypeCron:
@@ -270,43 +436,6 @@ func (s *Scheduler) advanceToFuture(sc Schedule, now time.Time) *time.Time {
}
}
-// countMissedFires returns how many interval fires were missed between
-// the scheduled next_fire_at and now.
-func (s *Scheduler) countMissedFires(sc Schedule, now time.Time) int {
- if sc.NextFireAt == nil {
- return 1
- }
- switch sc.ScheduleType {
- case ScheduleTypeInterval:
- d, err := time.ParseDuration(sc.ScheduleExpr)
- if err != nil || d <= 0 {
- return 1
- }
- missed := int(now.Sub(*sc.NextFireAt)/d) + 1
- if missed < 1 {
- return 1
- }
- return missed
- case ScheduleTypeCron:
- schedule, err := s.parser.Parse(sc.ScheduleExpr)
- if err != nil {
- return 1
- }
- count := 0
- t := *sc.NextFireAt
- for t.Before(now) && count < 101 {
- count++
- t = schedule.Next(t)
- }
- if count < 1 {
- return 1
- }
- return count
- default:
- return 1
- }
-}
-
// ComputeInitialNextFire calculates the first fire time for a new schedule.
func (s *Scheduler) ComputeInitialNextFire(scheduleType, scheduleExpr string) (*time.Time, error) {
now := time.Now()
diff --git a/server/internal/workflow/scheduler_test.go b/server/internal/workflow/scheduler_test.go
index 69cd72c..01a9b23 100644
--- a/server/internal/workflow/scheduler_test.go
+++ b/server/internal/workflow/scheduler_test.go
@@ -1,12 +1,129 @@
package workflow
import (
+ "context"
+ "encoding/json"
+ "errors"
+ "strconv"
"testing"
"time"
"github.com/robfig/cron/v3"
+ pb "github.com/scitrera/aether/api/proto"
)
+type scheduleCursorUpdate struct {
+ occurrence ScheduleOccurrence
+ nextFire *time.Time
+}
+
+type schedulePollStore struct {
+ WorkflowStore
+ due []Schedule
+ updates []scheduleCursorUpdate
+ blockedReason string
+}
+
+func (s *schedulePollStore) GetDueSchedules(context.Context, time.Time) ([]Schedule, error) {
+ return append([]Schedule(nil), s.due...), nil
+}
+
+func (s *schedulePollStore) RecordScheduleOccurrence(_ context.Context, _ string, occurrence ScheduleOccurrence, nextFire *time.Time) error {
+ var nextCopy *time.Time
+ if nextFire != nil {
+ value := *nextFire
+ nextCopy = &value
+ }
+ s.updates = append(s.updates, scheduleCursorUpdate{occurrence: occurrence, nextFire: nextCopy})
+ return nil
+}
+
+func (s *schedulePollStore) GetDueJoinDeadlines(context.Context, time.Time) ([]Join, error) {
+ return nil, nil
+}
+
+func (s *schedulePollStore) SetScheduleAuthorityBlocked(_ context.Context, _ string, reason string) error {
+ s.blockedReason = reason
+ return nil
+}
+
+type recordingScheduleDispatcher struct {
+ actions []*ActionDef
+ failAt int
+ err error
+}
+
+func (d *recordingScheduleDispatcher) DispatchScheduledAction(_ context.Context, action *ActionDef, _ string, _ *pb.AuthorizationContext) error {
+ if d.failAt > 0 && len(d.actions)+1 == d.failAt {
+ if d.err != nil {
+ return d.err
+ }
+ return errors.New("injected dispatch failure")
+ }
+ copy := *action
+ copy.Metadata = cloneStringMap(action.Metadata)
+ d.actions = append(d.actions, ©)
+ return nil
+}
+
+func TestSchedulerBlocksPermanentAuthorityFailureAndRecordsNoTaskSkip(t *testing.T) {
+ now := time.Date(2026, time.August, 12, 12, 0, 0, 0, time.UTC)
+ schedule := dueCreateTaskSchedule(now, 0, ScheduleMissPolicyFireOnce)
+ var action ActionDef
+ if err := json.Unmarshal(schedule.Action, &action); err != nil {
+ t.Fatal(err)
+ }
+ action.RequireTaskAuthority = true
+ schedule.Action, _ = json.Marshal(action)
+
+ scheduler, store, dispatcher := newSchedulePollHarness(now, schedule)
+ dispatcher.failAt = 1
+ dispatcher.err = &ScheduleAuthorityInvalidError{Code: "ERR_AUTHORITY_INVALID", Message: "revoked"}
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatalf("poll: %v", err)
+ }
+ if store.blockedReason == "" || len(store.updates) != 1 {
+ t.Fatalf("blockedReason=%q updates=%+v", store.blockedReason, store.updates)
+ }
+ if got := store.updates[0].occurrence; got.Disposition != ScheduleDispositionSkipped ||
+ got.Reason != ScheduleSkipReasonAuthorityInvalid || got.DispatchedAt != nil {
+ t.Fatalf("authority skip occurrence = %+v", got)
+ }
+}
+
+func TestSchedulerLeavesTransientFailureDue(t *testing.T) {
+ now := time.Date(2026, time.August, 12, 12, 0, 0, 0, time.UTC)
+ scheduler, store, dispatcher := newSchedulePollHarness(now, dueCreateTaskSchedule(now, 0, ScheduleMissPolicyFireOnce))
+ dispatcher.failAt = 1
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatalf("poll: %v", err)
+ }
+ if store.blockedReason != "" || len(store.updates) != 0 {
+ t.Fatalf("transient failure advanced or blocked schedule: reason=%q updates=%+v", store.blockedReason, store.updates)
+ }
+}
+
+func newSchedulePollHarness(now time.Time, schedule Schedule) (*Scheduler, *schedulePollStore, *recordingScheduleDispatcher) {
+ store := &schedulePollStore{due: []Schedule{schedule}}
+ dispatcher := &recordingScheduleDispatcher{}
+ scheduler := NewScheduler(store, dispatcher, nil, nil, nil, time.Second)
+ scheduler.now = func() time.Time { return now }
+ return scheduler, store, dispatcher
+}
+
+func dueCreateTaskSchedule(now time.Time, overdue time.Duration, policy string) Schedule {
+ dueAt := now.Add(-overdue)
+ action, _ := json.Marshal(ActionDef{
+ Type: "create_task", TaskType: "test.schedule", Workspace: "workspace-a",
+ Metadata: map[string]string{"caller": "preserved"},
+ })
+ return Schedule{
+ ID: "schedule-a", Name: "Schedule A", Workspace: "workspace-a",
+ ScheduleType: ScheduleTypeInterval, ScheduleExpr: "1m", Action: action,
+ Enabled: true, NextFireAt: &dueAt, MissPolicy: policy,
+ }
+}
+
// newTestScheduler builds a Scheduler with nil store/executor/dagEng/leader — safe
// for unit tests that only exercise pure computation methods.
func newTestScheduler() *Scheduler {
@@ -16,6 +133,177 @@ func newTestScheduler() *Scheduler {
}
}
+func TestScheduleMissPolicyValidation(t *testing.T) {
+ for _, policy := range []string{ScheduleMissPolicySkip, ScheduleMissPolicyFireOnce, ScheduleMissPolicyFireAll} {
+ if !validScheduleMissPolicy(policy) {
+ t.Fatalf("supported policy %q was rejected", policy)
+ }
+ }
+ if validScheduleMissPolicy("") || validScheduleMissPolicy("eventually") {
+ t.Fatal("empty or unknown missed-fire policy was accepted")
+ }
+}
+
+func TestSchedulerPollSkipFiresSingleDueOccurrenceButDropsBacklog(t *testing.T) {
+ now := time.Date(2026, 8, 10, 18, 0, 0, 0, time.UTC)
+ for name, overdue := range map[string]time.Duration{
+ "single due occurrence": 30 * time.Second,
+ "multiple due occurrences": 90 * time.Second,
+ } {
+ t.Run(name, func(t *testing.T) {
+ scheduler, store, dispatcher := newSchedulePollHarness(now, dueCreateTaskSchedule(now, overdue, "skip"))
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ wantFires := 1
+ if overdue > time.Minute {
+ wantFires = 0
+ }
+ if len(dispatcher.actions) != wantFires {
+ t.Fatalf("dispatched actions = %d, want %d", len(dispatcher.actions), wantFires)
+ }
+ if len(store.updates) != 1 || store.updates[0].nextFire == nil || !store.updates[0].nextFire.After(now) {
+ t.Fatalf("cursor updates = %+v, want one future cursor", store.updates)
+ }
+ wantDisposition := ScheduleDispositionOrdinary
+ wantReason := ""
+ wantBacklog := 1
+ if wantFires == 0 {
+ wantDisposition = ScheduleDispositionSkipped
+ wantReason = ScheduleSkipReasonMissPolicy
+ wantBacklog = 2
+ }
+ got := store.updates[0].occurrence
+ if got.Disposition != wantDisposition || got.Reason != wantReason || got.BacklogCount != wantBacklog || got.BacklogTruncated {
+ t.Fatalf("occurrence = %+v", got)
+ }
+ })
+ }
+}
+
+func TestSchedulerPollFireOnceCoalescesWithDeterministicOccurrenceIdentity(t *testing.T) {
+ now := time.Date(2026, 8, 10, 18, 0, 0, 0, time.UTC)
+ schedule := dueCreateTaskSchedule(now, 150*time.Second, "fire_once")
+ scheduler, store, dispatcher := newSchedulePollHarness(now, schedule)
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(dispatcher.actions) != 1 {
+ t.Fatalf("dispatched actions = %d, want 1", len(dispatcher.actions))
+ }
+ action := dispatcher.actions[0]
+ wantScheduledFor := schedule.NextFireAt.UTC().Format(time.RFC3339Nano)
+ if action.Metadata["caller"] != "preserved" ||
+ action.Metadata[scheduleMetadataID] != schedule.ID ||
+ action.Metadata[scheduleMetadataScheduledFor] != wantScheduledFor ||
+ action.Metadata[scheduleMetadataDispatchedAt] != now.Format(time.RFC3339Nano) ||
+ action.Metadata[scheduleMetadataMissPolicy] != "fire_once" ||
+ action.Metadata[scheduleMetadataDisposition] != ScheduleDispositionCoalesced ||
+ action.Metadata[scheduleMetadataBacklogCount] != "3" ||
+ action.Metadata[scheduleMetadataBacklogTruncated] != "false" ||
+ action.Metadata[scheduleMetadataBacklogIndex] != "1" {
+ t.Fatalf("scheduled action metadata = %#v", action.Metadata)
+ }
+ wantKey := scheduleOccurrenceIdempotencyKey(schedule, *schedule.NextFireAt)
+ if action.IdempotencyKey != wantKey || action.IdempotencyKey == "" {
+ t.Fatalf("idempotency key = %q, want %q", action.IdempotencyKey, wantKey)
+ }
+ if len(store.updates) != 1 || store.updates[0].nextFire == nil || !store.updates[0].nextFire.After(now) {
+ t.Fatalf("cursor updates = %+v, want one future cursor", store.updates)
+ }
+ if got := store.updates[0].occurrence; got.Disposition != ScheduleDispositionCoalesced || got.BacklogCount != 3 || got.BacklogIndex != 1 {
+ t.Fatalf("coalesced occurrence = %+v", got)
+ }
+
+ // A response-loss retry of the same durable cursor produces the same key,
+ // allowing the gateway idempotency ledger to suppress a duplicate task.
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(dispatcher.actions) != 2 || dispatcher.actions[1].IdempotencyKey != wantKey {
+ t.Fatalf("retry idempotency keys = %q, %q", dispatcher.actions[0].IdempotencyKey, dispatcher.actions[1].IdempotencyKey)
+ }
+}
+
+func TestSchedulerPollFireAllAdvancesEveryOccurrenceWithoutDiscardingCappedBacklog(t *testing.T) {
+ now := time.Date(2026, 8, 10, 18, 0, 0, 0, time.UTC)
+ schedule := dueCreateTaskSchedule(now, 150*time.Second, "fire_all")
+ scheduler, store, dispatcher := newSchedulePollHarness(now, schedule)
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(dispatcher.actions) != 3 || len(store.updates) != 3 {
+ t.Fatalf("actions/updates = %d/%d, want 3/3", len(dispatcher.actions), len(store.updates))
+ }
+ seenKeys := map[string]struct{}{}
+ for i, action := range dispatcher.actions {
+ if _, duplicate := seenKeys[action.IdempotencyKey]; duplicate || action.IdempotencyKey == "" {
+ t.Fatalf("occurrence %d idempotency key = %q", i, action.IdempotencyKey)
+ }
+ seenKeys[action.IdempotencyKey] = struct{}{}
+ if action.Metadata[scheduleMetadataDisposition] != ScheduleDispositionCatchUp ||
+ action.Metadata[scheduleMetadataBacklogCount] != "3" ||
+ action.Metadata[scheduleMetadataBacklogIndex] != strconv.Itoa(i+1) {
+ t.Fatalf("catch-up occurrence %d metadata = %#v", i, action.Metadata)
+ }
+ }
+ if last := store.updates[len(store.updates)-1].nextFire; last == nil || !last.After(now) {
+ t.Fatalf("final cursor = %v, want future", last)
+ }
+
+ large := dueCreateTaskSchedule(now, (maxScheduleCatchUpPerPoll+2)*time.Minute+30*time.Second, "fire_all")
+ largeScheduler, largeStore, largeDispatcher := newSchedulePollHarness(now, large)
+ if err := largeScheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(largeDispatcher.actions) != maxScheduleCatchUpPerPoll || len(largeStore.updates) != maxScheduleCatchUpPerPoll {
+ t.Fatalf("capped actions/updates = %d/%d", len(largeDispatcher.actions), len(largeStore.updates))
+ }
+ if last := largeStore.updates[len(largeStore.updates)-1].nextFire; last == nil || last.After(now) {
+ t.Fatalf("capped cursor = %v, want retained due backlog", last)
+ }
+ if got := largeStore.updates[len(largeStore.updates)-1].occurrence; got.Disposition != ScheduleDispositionCatchUp ||
+ got.BacklogCount != maxScheduleCatchUpPerPoll+1 || !got.BacklogTruncated || got.BacklogIndex != maxScheduleCatchUpPerPoll {
+ t.Fatalf("bounded catch-up occurrence = %+v", got)
+ }
+}
+
+func TestSchedulerPollRecordsMaxConcurrentSkipWithoutOverwritingARealFire(t *testing.T) {
+ now := time.Date(2026, 8, 10, 18, 0, 0, 0, time.UTC)
+ schedule := dueCreateTaskSchedule(now, 90*time.Second, ScheduleMissPolicyFireAll)
+ schedule.MaxConcurrent = 1
+ schedule.ActiveTaskID = now.Add(-10 * time.Second).Format(time.RFC3339)
+ scheduler, store, dispatcher := newSchedulePollHarness(now, schedule)
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(dispatcher.actions) != 0 || len(store.updates) != 1 {
+ t.Fatalf("actions/updates = %d/%d", len(dispatcher.actions), len(store.updates))
+ }
+ got := store.updates[0].occurrence
+ if got.DispatchedAt != nil || got.Disposition != ScheduleDispositionSkipped ||
+ got.Reason != ScheduleSkipReasonMaxConcurrent || got.BacklogCount != 2 {
+ t.Fatalf("max-concurrent occurrence = %+v", got)
+ }
+}
+
+func TestSchedulerPollFireAllLeavesFailedOccurrenceAtDurableCursor(t *testing.T) {
+ now := time.Date(2026, 8, 10, 18, 0, 0, 0, time.UTC)
+ schedule := dueCreateTaskSchedule(now, 150*time.Second, "fire_all")
+ scheduler, store, dispatcher := newSchedulePollHarness(now, schedule)
+ dispatcher.failAt = 2
+ if err := scheduler.poll(context.Background()); err != nil {
+ t.Fatal(err)
+ }
+ if len(dispatcher.actions) != 1 || len(store.updates) != 1 {
+ t.Fatalf("actions/updates = %d/%d, want first occurrence only", len(dispatcher.actions), len(store.updates))
+ }
+ wantRetryCursor := schedule.NextFireAt.Add(time.Minute)
+ if store.updates[0].nextFire == nil || !store.updates[0].nextFire.Equal(wantRetryCursor) {
+ t.Fatalf("retry cursor = %v, want %v", store.updates[0].nextFire, wantRetryCursor)
+ }
+}
+
// ---- calculateNextFire ----
func TestScheduler_calculateNextFire_cronScheduleReturnsNextTime(t *testing.T) {
@@ -148,87 +436,6 @@ func TestScheduler_advanceToFuture_intervalReturnsFutureTime(t *testing.T) {
}
}
-// ---- countMissedFires ----
-
-func TestScheduler_countMissedFires_returnsOneWhenNoNextFireAt(t *testing.T) {
- s := newTestScheduler()
- sc := Schedule{
- ScheduleType: ScheduleTypeInterval,
- ScheduleExpr: "1m",
- NextFireAt: nil,
- }
-
- count := s.countMissedFires(sc, time.Now())
- if count != 1 {
- t.Errorf("countMissedFires() = %d, want 1 when NextFireAt is nil", count)
- }
-}
-
-func TestScheduler_countMissedFires_intervalCountsMissedPeriods(t *testing.T) {
- s := newTestScheduler()
- base := time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC)
- // NextFireAt is 3 minutes ago, interval is 1m → should count 3 misses
- nextFire := base.Add(-3 * time.Minute)
- sc := Schedule{
- ScheduleType: ScheduleTypeInterval,
- ScheduleExpr: "1m",
- NextFireAt: &nextFire,
- }
-
- count := s.countMissedFires(sc, base)
- if count < 3 {
- t.Errorf("countMissedFires() = %d, want ≥3 for 3-minute gap with 1m interval", count)
- }
-}
-
-func TestScheduler_countMissedFires_cronCountsMissedSlots(t *testing.T) {
- s := newTestScheduler()
- // nextFire was 3 hours ago, cron fires every hour → 3 missed
- base := time.Date(2025, 1, 15, 15, 0, 0, 0, time.UTC)
- nextFire := time.Date(2025, 1, 15, 12, 0, 0, 0, time.UTC)
- sc := Schedule{
- ScheduleType: ScheduleTypeCron,
- ScheduleExpr: "0 * * * *", // top of hour
- NextFireAt: &nextFire,
- }
-
- count := s.countMissedFires(sc, base)
- if count < 3 {
- t.Errorf("countMissedFires() = %d, want ≥3 for 3-hour gap with hourly cron", count)
- }
-}
-
-func TestScheduler_countMissedFires_invalidIntervalReturnsOne(t *testing.T) {
- s := newTestScheduler()
- now := time.Now()
- nextFire := now.Add(-5 * time.Minute)
- sc := Schedule{
- ScheduleType: ScheduleTypeInterval,
- ScheduleExpr: "bad-duration",
- NextFireAt: &nextFire,
- }
-
- count := s.countMissedFires(sc, now)
- if count != 1 {
- t.Errorf("countMissedFires() = %d with invalid interval, want 1", count)
- }
-}
-
-func TestScheduler_countMissedFires_nonIntervalNonCronReturnsOne(t *testing.T) {
- s := newTestScheduler()
- now := time.Now()
- nextFire := now.Add(-1 * time.Minute)
- sc := Schedule{
- ScheduleType: ScheduleTypeOnce,
- NextFireAt: &nextFire,
- }
-
- count := s.countMissedFires(sc, now)
- if count != 1 {
- t.Errorf("countMissedFires() = %d for once schedule, want 1", count)
- }
-}
-
// ---- ComputeInitialNextFire ----
func TestScheduler_ComputeInitialNextFire_cronReturnsFutureTime(t *testing.T) {
diff --git a/server/internal/workflow/store.go b/server/internal/workflow/store.go
index 551f293..9340135 100644
--- a/server/internal/workflow/store.go
+++ b/server/internal/workflow/store.go
@@ -6,6 +6,8 @@ import (
"encoding/json"
"fmt"
"time"
+
+ pb "github.com/scitrera/aether/api/proto"
)
// Store provides database operations for all workflow tables.
@@ -445,21 +447,53 @@ func (s *Store) GetStepByTaskID(ctx context.Context, taskID string) (*StepState,
// =============================================================================
type Schedule struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Workspace string `json:"workspace"`
- ScheduleType string `json:"schedule_type"`
- ScheduleExpr string `json:"schedule_expr"`
- Action json.RawMessage `json:"action"`
- WorkflowID string `json:"workflow_id"`
- Enabled bool `json:"enabled"`
- NextFireAt *time.Time `json:"next_fire_at,omitempty"`
- LastFiredAt *time.Time `json:"last_fired_at,omitempty"`
- MissPolicy string `json:"miss_policy"`
- MaxConcurrent int `json:"max_concurrent"` // 0 = unlimited; 1 = don't fire if previous still running
- ActiveTaskID string `json:"active_task_id"` // Tracks currently running task (for max_concurrent=1)
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Workspace string `json:"workspace"`
+ ScheduleType string `json:"schedule_type"`
+ ScheduleExpr string `json:"schedule_expr"`
+ Action json.RawMessage `json:"action"`
+ WorkflowID string `json:"workflow_id"`
+ Enabled bool `json:"enabled"`
+ NextFireAt *time.Time `json:"next_fire_at,omitempty"`
+ LastFiredAt *time.Time `json:"last_fired_at,omitempty"`
+ LastOccurrence *ScheduleOccurrence `json:"last_occurrence,omitempty"`
+ MissPolicy string `json:"miss_policy"`
+ MaxConcurrent int `json:"max_concurrent"` // 0 = unlimited; 1 = don't fire if previous still running
+ ActiveTaskID string `json:"active_task_id"` // Tracks currently running task (for max_concurrent=1)
+ Authority *ScheduleAuthority `json:"-"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+}
+
+// ScheduleAuthority is the private, gateway-minted authority used when a
+// schedule creates a task. It is intentionally excluded from schedule JSON and
+// workflow responses: callers replace it only through WorkflowOperation's
+// trusted request context.
+type ScheduleAuthority struct {
+ Authorization *pb.AuthorizationContext
+ RootGrantID string
+ SourceGrantID string
+ ExpiresAt time.Time
+ PolicyDigest string
+ PolicyVersion uint32
+ LifetimeMode pb.WorkflowAuthorityLifetimeMode
+ Blocked bool
+ BlockedReason string
+}
+
+// ScheduleOccurrence is the bounded authoritative summary of the latest
+// scheduler decision. DispatchedAt is nil when the occurrence was skipped and
+// no action/task exists. BacklogCount is capped; BacklogTruncated says more due
+// occurrences existed beyond the reported count.
+type ScheduleOccurrence struct {
+ ScheduledFor time.Time `json:"scheduled_for"`
+ DispatchedAt *time.Time `json:"dispatched_at,omitempty"`
+ Disposition string `json:"disposition"`
+ Reason string `json:"reason,omitempty"`
+ BacklogCount int `json:"backlog_count"`
+ BacklogTruncated bool `json:"backlog_truncated"`
+ BacklogIndex int `json:"backlog_index"`
}
const (
@@ -467,16 +501,40 @@ const (
ScheduleTypeInterval = "interval"
ScheduleTypeOnce = "once"
ScheduleTypeEventDelayed = "event_delayed"
+
+ ScheduleMissPolicySkip = "skip"
+ ScheduleMissPolicyFireOnce = "fire_once"
+ ScheduleMissPolicyFireAll = "fire_all"
+
+ ScheduleDispositionOrdinary = "ordinary"
+ ScheduleDispositionSkipped = "skipped"
+ ScheduleDispositionCoalesced = "coalesced"
+ ScheduleDispositionCatchUp = "catch_up"
+
+ ScheduleSkipReasonMissPolicy = "miss_policy"
+ ScheduleSkipReasonMaxConcurrent = "max_concurrent"
+ ScheduleSkipReasonAuthorityInvalid = "authority_invalid"
)
+func validScheduleMissPolicy(policy string) bool {
+ return policy == ScheduleMissPolicySkip || policy == ScheduleMissPolicyFireOnce || policy == ScheduleMissPolicyFireAll
+}
+
func (s *Store) GetDueSchedules(ctx context.Context, now time.Time) ([]Schedule, error) {
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
- WHERE enabled = true AND next_fire_at IS NOT NULL AND next_fire_at <= $1
+ WHERE enabled = true AND authority_blocked = false
+ AND next_fire_at IS NOT NULL AND next_fire_at <= $1
ORDER BY next_fire_at ASC
`
if !s.isSQLite {
@@ -490,13 +548,8 @@ func (s *Store) GetDueSchedules(ctx context.Context, now time.Time) ([]Schedule,
var schedules []Schedule
for rows.Next() {
- var sc Schedule
- if err := rows.Scan(
- &sc.ID, &sc.Name, &sc.Workspace, &sc.ScheduleType, &sc.ScheduleExpr, &sc.Action,
- &sc.WorkflowID, &sc.Enabled, &sc.NextFireAt, &sc.LastFiredAt, &sc.MissPolicy,
- &sc.MaxConcurrent, &sc.ActiveTaskID,
- &sc.CreatedAt, &sc.UpdatedAt,
- ); err != nil {
+ sc, err := scanSchedule(rows)
+ if err != nil {
return nil, fmt.Errorf("scan schedule: %w", err)
}
schedules = append(schedules, sc)
@@ -504,22 +557,37 @@ func (s *Store) GetDueSchedules(ctx context.Context, now time.Time) ([]Schedule,
return schedules, rows.Err()
}
-func (s *Store) UpdateScheduleAfterFire(ctx context.Context, id string, lastFired time.Time, nextFire *time.Time) error {
- query := `UPDATE workflow_schedules SET last_fired_at = $2, next_fire_at = $3 WHERE id = $1`
- _, err := s.db.ExecContext(ctx, query, id, lastFired, nextFire)
+func (s *Store) RecordScheduleOccurrence(ctx context.Context, id string, occurrence ScheduleOccurrence, nextFire *time.Time) error {
+ query := `UPDATE workflow_schedules SET
+ last_fired_at = COALESCE($2, last_fired_at), next_fire_at = $3,
+ last_occurrence_at = $4, last_occurrence_disposition = $5,
+ last_occurrence_reason = $6, last_backlog_count = $7,
+ last_backlog_truncated = $8, last_backlog_index = $9
+ WHERE id = $1`
+ _, err := s.db.ExecContext(ctx, query, id, occurrence.DispatchedAt, nextFire,
+ occurrence.ScheduledFor, occurrence.Disposition, occurrence.Reason,
+ occurrence.BacklogCount, occurrence.BacklogTruncated, occurrence.BacklogIndex)
return err
}
func (s *Store) CreateSchedule(ctx context.Context, sc *Schedule) error {
query := `
INSERT INTO workflow_schedules (id, name, workspace, schedule_type, schedule_expr, action,
- workflow_id, enabled, next_fire_at, miss_policy, max_concurrent)
- VALUES ($1, $2, $3, $4, $5, $6, NULLIF($7, ''), $8, $9, $10, $11)
+ workflow_id, enabled, next_fire_at, miss_policy, max_concurrent,
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason)
+ VALUES ($1, $2, $3, $4, $5, $6, NULLIF($7, ''), $8, $9, $10, $11,
+ NULLIF($12, ''), NULLIF($13, ''), NULLIF($14, ''), NULLIF($15, ''), NULLIF($16, ''),
+ $17, NULLIF($18, ''), $19, $20, $21, $22)
RETURNING created_at, updated_at
`
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason := scheduleAuthoritySQLValues(sc.Authority)
return s.db.QueryRowContext(ctx, query,
sc.ID, sc.Name, sc.Workspace, sc.ScheduleType, sc.ScheduleExpr, sc.Action,
sc.WorkflowID, sc.Enabled, sc.NextFireAt, sc.MissPolicy, sc.MaxConcurrent,
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason,
).Scan(&sc.CreatedAt, &sc.UpdatedAt)
}
@@ -531,8 +599,14 @@ func (s *Store) DeleteSchedule(ctx context.Context, id string) error {
func (s *Store) ListSchedules(ctx context.Context, workspace string) ([]Schedule, error) {
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
WHERE workspace = $1 OR workspace = '*'
@@ -546,13 +620,8 @@ func (s *Store) ListSchedules(ctx context.Context, workspace string) ([]Schedule
var schedules []Schedule
for rows.Next() {
- var sc Schedule
- if err := rows.Scan(
- &sc.ID, &sc.Name, &sc.Workspace, &sc.ScheduleType, &sc.ScheduleExpr, &sc.Action,
- &sc.WorkflowID, &sc.Enabled, &sc.NextFireAt, &sc.LastFiredAt, &sc.MissPolicy,
- &sc.MaxConcurrent, &sc.ActiveTaskID,
- &sc.CreatedAt, &sc.UpdatedAt,
- ); err != nil {
+ sc, err := scanSchedule(rows)
+ if err != nil {
return nil, fmt.Errorf("scan schedule: %w", err)
}
schedules = append(schedules, sc)
@@ -563,19 +632,19 @@ func (s *Store) ListSchedules(ctx context.Context, workspace string) ([]Schedule
func (s *Store) GetSchedule(ctx context.Context, id string) (*Schedule, error) {
query := `
SELECT id, name, workspace, schedule_type, schedule_expr, action,
- COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at, miss_policy,
+ COALESCE(workflow_id, ''), enabled, next_fire_at, last_fired_at,
+ last_occurrence_at, last_occurrence_disposition, last_occurrence_reason,
+ last_backlog_count, last_backlog_truncated, last_backlog_index, miss_policy,
max_concurrent, COALESCE(active_task_id, ''),
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason,
created_at, updated_at
FROM workflow_schedules
WHERE id = $1
`
- var sc Schedule
- err := s.db.QueryRowContext(ctx, query, id).Scan(
- &sc.ID, &sc.Name, &sc.Workspace, &sc.ScheduleType, &sc.ScheduleExpr, &sc.Action,
- &sc.WorkflowID, &sc.Enabled, &sc.NextFireAt, &sc.LastFiredAt, &sc.MissPolicy,
- &sc.MaxConcurrent, &sc.ActiveTaskID,
- &sc.CreatedAt, &sc.UpdatedAt,
- )
+ sc, err := scanSchedule(s.db.QueryRowContext(ctx, query, id))
if err == sql.ErrNoRows {
return nil, nil
}
@@ -585,26 +654,105 @@ func (s *Store) GetSchedule(ctx context.Context, id string) (*Schedule, error) {
return &sc, nil
}
+type scheduleScanner interface {
+ Scan(dest ...any) error
+}
+
+func scanSchedule(scanner scheduleScanner) (Schedule, error) {
+ var sc Schedule
+ var occurrenceAt sql.NullTime
+ var disposition, reason string
+ var backlogCount, backlogIndex int
+ var backlogTruncated bool
+ var grantID, subjectType, subjectID, rootGrantID, sourceGrantID sql.NullString
+ var authorityExpiresAt sql.NullTime
+ var policyDigest, blockedReason sql.NullString
+ var policyVersion, lifetimeMode sql.NullInt64
+ var authorityBlocked bool
+ err := scanner.Scan(
+ &sc.ID, &sc.Name, &sc.Workspace, &sc.ScheduleType, &sc.ScheduleExpr, &sc.Action,
+ &sc.WorkflowID, &sc.Enabled, &sc.NextFireAt, &sc.LastFiredAt,
+ &occurrenceAt, &disposition, &reason, &backlogCount, &backlogTruncated, &backlogIndex, &sc.MissPolicy,
+ &sc.MaxConcurrent, &sc.ActiveTaskID,
+ &grantID, &subjectType, &subjectID, &rootGrantID, &sourceGrantID, &authorityExpiresAt,
+ &policyDigest, &policyVersion, &lifetimeMode, &authorityBlocked, &blockedReason,
+ &sc.CreatedAt, &sc.UpdatedAt,
+ )
+ if err != nil {
+ return Schedule{}, err
+ }
+ if occurrenceAt.Valid {
+ sc.LastOccurrence = &ScheduleOccurrence{
+ ScheduledFor: occurrenceAt.Time, Disposition: disposition, Reason: reason,
+ BacklogCount: backlogCount, BacklogTruncated: backlogTruncated, BacklogIndex: backlogIndex,
+ }
+ if disposition != ScheduleDispositionSkipped && sc.LastFiredAt != nil {
+ dispatchedAt := *sc.LastFiredAt
+ sc.LastOccurrence.DispatchedAt = &dispatchedAt
+ }
+ }
+ if grantID.Valid {
+ sc.Authority = &ScheduleAuthority{
+ Authorization: &pb.AuthorizationContext{
+ AuthorityMode: "on_behalf_of",
+ Subject: &pb.PrincipalRef{PrincipalType: subjectType.String, PrincipalId: subjectID.String},
+ GrantId: grantID.String,
+ },
+ RootGrantID: rootGrantID.String, SourceGrantID: sourceGrantID.String,
+ ExpiresAt: authorityExpiresAt.Time, PolicyDigest: policyDigest.String,
+ PolicyVersion: uint32(policyVersion.Int64),
+ LifetimeMode: pb.WorkflowAuthorityLifetimeMode(lifetimeMode.Int64),
+ Blocked: authorityBlocked, BlockedReason: blockedReason.String,
+ }
+ }
+ return sc, nil
+}
+
func (s *Store) UpsertSchedule(ctx context.Context, sc *Schedule) error {
query := `
INSERT INTO workflow_schedules (id, name, workspace, schedule_type, schedule_expr, action,
- workflow_id, enabled, next_fire_at, miss_policy, max_concurrent)
- VALUES ($1, $2, $3, $4, $5, $6, NULLIF($7, ''), $8, $9, $10, $11)
+ workflow_id, enabled, next_fire_at, miss_policy, max_concurrent,
+ authority_grant_id, authority_subject_type, authority_subject_id,
+ authority_root_grant_id, authority_source_grant_id, authority_expires_at,
+ authority_policy_digest, authority_policy_version, authority_lifetime_mode,
+ authority_blocked, authority_blocked_reason)
+ VALUES ($1, $2, $3, $4, $5, $6, NULLIF($7, ''), $8, $9, $10, $11,
+ NULLIF($12, ''), NULLIF($13, ''), NULLIF($14, ''), NULLIF($15, ''), NULLIF($16, ''),
+ $17, NULLIF($18, ''), $19, $20, $21, $22)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
workspace = EXCLUDED.workspace,
+ next_fire_at = CASE
+ WHEN workflow_schedules.schedule_type <> EXCLUDED.schedule_type
+ OR workflow_schedules.schedule_expr <> EXCLUDED.schedule_expr
+ THEN EXCLUDED.next_fire_at
+ ELSE workflow_schedules.next_fire_at
+ END,
schedule_type = EXCLUDED.schedule_type,
schedule_expr = EXCLUDED.schedule_expr,
action = EXCLUDED.action,
workflow_id = EXCLUDED.workflow_id,
enabled = EXCLUDED.enabled,
miss_policy = EXCLUDED.miss_policy,
- max_concurrent = EXCLUDED.max_concurrent
+ max_concurrent = EXCLUDED.max_concurrent,
+ authority_grant_id = EXCLUDED.authority_grant_id,
+ authority_subject_type = EXCLUDED.authority_subject_type,
+ authority_subject_id = EXCLUDED.authority_subject_id,
+ authority_root_grant_id = EXCLUDED.authority_root_grant_id,
+ authority_source_grant_id = EXCLUDED.authority_source_grant_id,
+ authority_expires_at = EXCLUDED.authority_expires_at,
+ authority_policy_digest = EXCLUDED.authority_policy_digest,
+ authority_policy_version = EXCLUDED.authority_policy_version,
+ authority_lifetime_mode = EXCLUDED.authority_lifetime_mode,
+ authority_blocked = EXCLUDED.authority_blocked,
+ authority_blocked_reason = EXCLUDED.authority_blocked_reason
RETURNING created_at, updated_at
`
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason := scheduleAuthoritySQLValues(sc.Authority)
return s.db.QueryRowContext(ctx, query,
sc.ID, sc.Name, sc.Workspace, sc.ScheduleType, sc.ScheduleExpr, sc.Action,
sc.WorkflowID, sc.Enabled, sc.NextFireAt, sc.MissPolicy, sc.MaxConcurrent,
+ grantID, subjectType, subjectID, rootGrantID, sourceGrantID, expiresAt, digest, policyVersion, lifetime, blocked, blockedReason,
).Scan(&sc.CreatedAt, &sc.UpdatedAt)
}
@@ -614,6 +762,22 @@ func (s *Store) SetScheduleActiveTask(ctx context.Context, scheduleID, taskID st
return err
}
+func (s *Store) SetScheduleAuthorityBlocked(ctx context.Context, scheduleID, reason string) error {
+ query := `UPDATE workflow_schedules SET authority_blocked = true, authority_blocked_reason = $2 WHERE id = $1`
+ _, err := s.db.ExecContext(ctx, query, scheduleID, reason)
+ return err
+}
+
+func scheduleAuthoritySQLValues(authority *ScheduleAuthority) (grantID, subjectType, subjectID, rootGrantID, sourceGrantID string, expiresAt any, digest string, policyVersion uint32, lifetime any, blocked bool, blockedReason string) {
+ if authority == nil || authority.Authorization == nil {
+ return "", "", "", "", "", nil, "", 0, nil, false, ""
+ }
+ subject := authority.Authorization.GetSubject()
+ return authority.Authorization.GetGrantId(), subject.GetPrincipalType(), subject.GetPrincipalId(),
+ authority.RootGrantID, authority.SourceGrantID, authority.ExpiresAt, authority.PolicyDigest, authority.PolicyVersion,
+ int32(authority.LifetimeMode), authority.Blocked, authority.BlockedReason
+}
+
// =============================================================================
// Join types and operations
// =============================================================================
@@ -638,9 +802,9 @@ type Join struct {
OnTimeout string
OnPartialFailure string
DeadlineAt *time.Time
- LingerUntil *time.Time
- CreatedAt time.Time
- UpdatedAt time.Time
+ LingerUntil *time.Time
+ CreatedAt time.Time
+ UpdatedAt time.Time
}
const (
@@ -655,7 +819,7 @@ const (
)
// nullableText stores an empty string as SQL NULL so optional TEXT columns
-// (on_complete/on_timeout/on_partial_failure) stay NULL rather than '' when unset.
+// (on_complete/on_timeout/on_partial_failure) stay NULL rather than ” when unset.
func nullableText(s string) interface{} {
if s == "" {
return nil
diff --git a/server/internal/workflow/store_iface.go b/server/internal/workflow/store_iface.go
index 9267d39..131005e 100644
--- a/server/internal/workflow/store_iface.go
+++ b/server/internal/workflow/store_iface.go
@@ -68,8 +68,9 @@ type WorkflowStore interface {
ListSchedules(ctx context.Context, workspace string) ([]Schedule, error)
GetSchedule(ctx context.Context, id string) (*Schedule, error)
UpsertSchedule(ctx context.Context, sc *Schedule) error
- UpdateScheduleAfterFire(ctx context.Context, id string, lastFired time.Time, nextFire *time.Time) error
+ RecordScheduleOccurrence(ctx context.Context, id string, occurrence ScheduleOccurrence, nextFire *time.Time) error
SetScheduleActiveTask(ctx context.Context, scheduleID, taskID string) error
+ SetScheduleAuthorityBlocked(ctx context.Context, scheduleID, reason string) error
// Joins
EnsureJoin(ctx context.Context, j *Join) (*Join, error)
diff --git a/server/internal/workflow/templates.go b/server/internal/workflow/templates.go
index 80636e5..5e3eb50 100644
--- a/server/internal/workflow/templates.go
+++ b/server/internal/workflow/templates.go
@@ -46,6 +46,8 @@ type TransformResult struct {
Type string `yaml:"type" json:"type"`
TaskType string `yaml:"task_type" json:"task_type"`
TargetImplementation string `yaml:"target_implementation" json:"target_implementation"`
+ TargetAgentID string `yaml:"target_agent_id" json:"target_agent_id"`
+ PayloadEncoding string `yaml:"payload_encoding" json:"payload_encoding"`
Payload any `yaml:"payload" json:"payload"`
// Fan-out tagging for spawned tasks: a correlation id (the join's barrier
// key) and an optional feed-B completion-event opt-in.
diff --git a/server/internal/workflow/workflow_handler.go b/server/internal/workflow/workflow_handler.go
index 6427bb4..bf81f28 100644
--- a/server/internal/workflow/workflow_handler.go
+++ b/server/internal/workflow/workflow_handler.go
@@ -3,6 +3,7 @@ package workflow
import (
"context"
"encoding/json"
+ "fmt"
"strconv"
"time"
@@ -312,8 +313,14 @@ func (s *Server) handleCreateSchedule(ctx context.Context, op *pb.WorkflowOperat
if sc.Workspace == "" {
sc.Workspace = "*"
}
+ if err := validateScheduleOperationIdentity(op, &sc); err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
if sc.MissPolicy == "" {
- sc.MissPolicy = "skip"
+ sc.MissPolicy = ScheduleMissPolicySkip
+ }
+ if !validScheduleMissPolicy(sc.MissPolicy) {
+ return errResponse(op.RequestId, "miss_policy must be skip, fire_once, or fire_all"), nil
}
sc.Enabled = true
@@ -322,6 +329,11 @@ func (s *Server) handleCreateSchedule(ctx context.Context, op *pb.WorkflowOperat
return errResponse(op.RequestId, "invalid schedule expression: "+err.Error()), nil
}
sc.NextFireAt = nextFire
+ authority, err := scheduleAuthorityFromOperation(op, &sc)
+ if err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
+ sc.Authority = authority
if err := s.store.CreateSchedule(ctx, &sc); err != nil {
return errResponse(op.RequestId, err.Error()), nil
@@ -330,6 +342,16 @@ func (s *Server) handleCreateSchedule(ctx context.Context, op *pb.WorkflowOperat
}
func (s *Server) handleDeleteSchedule(ctx context.Context, op *pb.WorkflowOperation) (*pb.WorkflowResponse, error) {
+ existing, err := s.store.GetSchedule(ctx, op.Id)
+ if err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
+ if existing != nil && existing.Workspace != op.GetWorkspace() {
+ return errResponse(op.RequestId, "workflow schedule operation identity does not match stored schedule"), nil
+ }
+ if err := s.revokeScheduleAuthority(ctx, existing); err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
if err := s.store.DeleteSchedule(ctx, op.Id); err != nil {
return errResponse(op.RequestId, err.Error()), nil
}
@@ -350,8 +372,14 @@ func (s *Server) handleUpsertSchedule(ctx context.Context, op *pb.WorkflowOperat
if sc.Workspace == "" {
sc.Workspace = "*"
}
+ if err := validateScheduleOperationIdentity(op, &sc); err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
if sc.MissPolicy == "" {
- sc.MissPolicy = "skip"
+ sc.MissPolicy = ScheduleMissPolicySkip
+ }
+ if !validScheduleMissPolicy(sc.MissPolicy) {
+ return errResponse(op.RequestId, "miss_policy must be skip, fire_once, or fire_all"), nil
}
sc.Enabled = true
@@ -360,13 +388,109 @@ func (s *Server) handleUpsertSchedule(ctx context.Context, op *pb.WorkflowOperat
return errResponse(op.RequestId, "invalid schedule expression: "+err.Error()), nil
}
sc.NextFireAt = nextFire
+ authority, err := scheduleAuthorityFromOperation(op, &sc)
+ if err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
+ existing, err := s.store.GetSchedule(ctx, sc.ID)
+ if err != nil {
+ return errResponse(op.RequestId, err.Error()), nil
+ }
+ if existing != nil && existing.Workspace != sc.Workspace {
+ return errResponse(op.RequestId, "cannot move a schedule between workspaces"), nil
+ }
+ sc.Authority = authority
if err := s.store.UpsertSchedule(ctx, &sc); err != nil {
return errResponse(op.RequestId, err.Error()), nil
}
+ if err := s.revokeScheduleAuthority(ctx, existing); err != nil {
+ // Restore the complete prior row before reporting failure. The gateway
+ // will revoke the provisional replacement grant when it sees the failed
+ // response, leaving the previously-authorized schedule intact.
+ if existing != nil {
+ if restoreErr := s.store.UpsertSchedule(ctx, existing); restoreErr != nil {
+ _ = s.store.SetScheduleAuthorityBlocked(ctx, sc.ID, "schedule authority replacement rollback failed")
+ return errResponse(op.RequestId, fmt.Sprintf("%v; rollback failed: %v", err, restoreErr)), nil
+ }
+ }
+ return errResponse(op.RequestId, err.Error()), nil
+ }
return jsonResponse(op.RequestId, sc)
}
+func validateScheduleOperationIdentity(op *pb.WorkflowOperation, sc *Schedule) error {
+ if op == nil || sc == nil || op.GetId() == "" || op.GetWorkspace() == "" ||
+ op.GetId() != sc.ID || op.GetWorkspace() != sc.Workspace {
+ return fmt.Errorf("workflow schedule operation identity does not match JSON definition")
+ }
+ return nil
+}
+
+func scheduleAuthorityFromOperation(op *pb.WorkflowOperation, sc *Schedule) (*ScheduleAuthority, error) {
+ var action ActionDef
+ requireAuthority := false
+ if len(sc.Action) > 0 {
+ if err := json.Unmarshal(sc.Action, &action); err != nil {
+ return nil, fmt.Errorf("invalid schedule action: %w", err)
+ }
+ requireAuthority = action.RequireTaskAuthority
+ if action.RequiredDownstreamAuthorityHops > 1 {
+ return nil, fmt.Errorf("required_downstream_authority_hops currently supports only 0 or 1")
+ }
+ }
+ requestContext := op.GetRequestContext()
+ if requestContext == nil || requestContext.GetScheduleAuthorization() == nil {
+ if requireAuthority {
+ return nil, fmt.Errorf("schedule action requires task authority")
+ }
+ return nil, nil
+ }
+ if action.Type != "create_task" {
+ return nil, fmt.Errorf("private schedule authority is valid only for create_task actions")
+ }
+ authorization := requestContext.GetScheduleAuthorization()
+ if authorization.GetAuthorityMode() != "on_behalf_of" || authorization.GetSubject() == nil ||
+ authorization.GetGrantId() == "" || requestContext.GetPolicyDigest() == "" ||
+ requestContext.GetExpiresAtMs() <= time.Now().UnixMilli() {
+ return nil, fmt.Errorf("invalid private schedule authority context")
+ }
+ if requestContext.GetPolicyVersion() != 1 {
+ return nil, fmt.Errorf("unsupported private schedule authority policy version %d", requestContext.GetPolicyVersion())
+ }
+ if action.RequiredDownstreamAuthorityHops > op.GetScheduleAuthorityScope().GetRequiredTaskAuthorityHops() {
+ return nil, fmt.Errorf("schedule action downstream authority requirement exceeds granted scope")
+ }
+ return &ScheduleAuthority{
+ Authorization: authorization,
+ RootGrantID: requestContext.GetRootGrantId(), SourceGrantID: requestContext.GetSourceGrantId(),
+ ExpiresAt: time.UnixMilli(requestContext.GetExpiresAtMs()).UTC(),
+ PolicyDigest: requestContext.GetPolicyDigest(), PolicyVersion: requestContext.GetPolicyVersion(),
+ LifetimeMode: requestContext.GetLifetimeMode(),
+ }, nil
+}
+
+func (s *Server) revokeScheduleAuthority(ctx context.Context, sc *Schedule) error {
+ if sc == nil || sc.Authority == nil || sc.Authority.Authorization == nil ||
+ sc.Authority.Authorization.GetGrantId() == "" || s.client == nil {
+ return nil
+ }
+ response, err := s.client.AuthorityGrants().RevokeForWorkflowSchedule(
+ ctx, sc.Authority.Authorization.GetGrantId(), sc.ID,
+ )
+ if err != nil {
+ return fmt.Errorf("revoke prior workflow schedule authority: %w", err)
+ }
+ if response == nil || !response.GetSuccess() {
+ message := "no response"
+ if response != nil && response.GetError() != "" {
+ message = response.GetError()
+ }
+ return fmt.Errorf("revoke prior workflow schedule authority: %s", message)
+ }
+ return nil
+}
+
// =============================================================================
// Executions
// =============================================================================
diff --git a/server/migrations/sqlite_workflow/003_schedule_occurrence_state.sql b/server/migrations/sqlite_workflow/003_schedule_occurrence_state.sql
new file mode 100644
index 0000000..0c1ef8e
--- /dev/null
+++ b/server/migrations/sqlite_workflow/003_schedule_occurrence_state.sql
@@ -0,0 +1,9 @@
+-- Bounded observability for the latest scheduler decision. Fired occurrences
+-- also carry this descriptor in task metadata; skipped occurrences have no task
+-- and therefore remain visible only on the authoritative schedule row.
+ALTER TABLE workflow_schedules ADD COLUMN last_occurrence_at TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN last_occurrence_disposition TEXT NOT NULL DEFAULT '';
+ALTER TABLE workflow_schedules ADD COLUMN last_occurrence_reason TEXT NOT NULL DEFAULT '';
+ALTER TABLE workflow_schedules ADD COLUMN last_backlog_count INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE workflow_schedules ADD COLUMN last_backlog_truncated INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE workflow_schedules ADD COLUMN last_backlog_index INTEGER NOT NULL DEFAULT 0;
diff --git a/server/migrations/sqlite_workflow/004_schedule_authority.sql b/server/migrations/sqlite_workflow/004_schedule_authority.sql
new file mode 100644
index 0000000..ae332d6
--- /dev/null
+++ b/server/migrations/sqlite_workflow/004_schedule_authority.sql
@@ -0,0 +1,13 @@
+-- Private authorization for scheduled task creation. These columns are never
+-- projected into schedule JSON or task payload/metadata.
+ALTER TABLE workflow_schedules ADD COLUMN authority_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_subject_type TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_subject_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_root_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_source_grant_id TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_expires_at TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_policy_digest TEXT;
+ALTER TABLE workflow_schedules ADD COLUMN authority_policy_version INTEGER;
+ALTER TABLE workflow_schedules ADD COLUMN authority_lifetime_mode INTEGER;
+ALTER TABLE workflow_schedules ADD COLUMN authority_blocked INTEGER NOT NULL DEFAULT 0;
+ALTER TABLE workflow_schedules ADD COLUMN authority_blocked_reason TEXT NOT NULL DEFAULT '';
diff --git a/server/pkg/authproxy/run.go b/server/pkg/authproxy/run.go
index fb1e124..34cb8e0 100644
--- a/server/pkg/authproxy/run.go
+++ b/server/pkg/authproxy/run.go
@@ -26,7 +26,8 @@ type Option func(*runOptions)
// runOptions captures the configurable behaviour of Run. Held internal so
// the option set can be extended without breaking callers.
type runOptions struct {
- identityResolver IdentityResolver
+ identityResolver IdentityResolver
+ handlerMiddleware func(http.Handler) http.Handler
}
// WithIdentityResolver overrides the default IdentityResolver used by Run.
@@ -41,6 +42,25 @@ func WithIdentityResolver(r IdentityResolver) Option {
}
}
+// WithHandlerMiddleware wraps the internal plane's HTTP handler.
+//
+// Exists so a caller can instrument this plane without this package taking on
+// the dependency: the middleware is supplied as a plain func, so tracing
+// libraries stay in the binary that wants them.
+//
+// This plane serves /auth/verify — the ext_authz call on the path of EVERY
+// request through the gateway — so it is the highest-value thing to trace and
+// was previously invisible.
+//
+// Applied to the outermost handler AFTER routes are registered. The wrapped
+// handler delegates to the mux by pointer, so routes attached later (notably
+// AttachLogin) are covered too.
+func WithHandlerMiddleware(mw func(http.Handler) http.Handler) Option {
+ return func(o *runOptions) {
+ o.handlerMiddleware = mw
+ }
+}
+
// Run wires up the auth-proxy from cfg, applies opts, and blocks until the
// process receives SIGINT/SIGTERM (or the supplied context is cancelled).
// It performs a graceful shutdown on signal and returns nil for clean exits
@@ -138,6 +158,10 @@ func Run(ctx context.Context, cfg *Config, opts ...Option) error {
}
defer loginCleanup()
+ // Applied after AttachLogin so the login routes are covered too. No-op when
+ // the option was not supplied.
+ server.WrapHandler(o.handlerMiddleware)
+
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
diff --git a/server/pkg/authproxy/server.go b/server/pkg/authproxy/server.go
index 5a9c907..0ce4629 100644
--- a/server/pkg/authproxy/server.go
+++ b/server/pkg/authproxy/server.go
@@ -27,6 +27,19 @@ type Server struct {
// construction.
func (s *Server) Mux() *http.ServeMux { return s.mux }
+// WrapHandler wraps the server's outermost HTTP handler with mw.
+//
+// Safe to call after routes are registered: the existing handler chain ends at
+// the mux POINTER, so handlers attached later (e.g. AttachLogin) are still
+// served through mw. No-op when mw is nil, so callers can pass an unset option
+// straight through.
+func (s *Server) WrapHandler(mw func(http.Handler) http.Handler) {
+ if mw == nil || s.httpServer == nil {
+ return
+ }
+ s.httpServer.Handler = mw(s.httpServer.Handler)
+}
+
// NewServer creates a new auth-proxy server. In proxy mode it also
// initialises a reverse proxy to the configured backend URL.
func NewServer(cfg *Config, middleware *AuthMiddleware) (*Server, error) {
diff --git a/server/pkg/models/resource_types.go b/server/pkg/models/resource_types.go
index d379b90..1178001 100644
--- a/server/pkg/models/resource_types.go
+++ b/server/pkg/models/resource_types.go
@@ -34,4 +34,18 @@ const (
// mint per-task tokens for service principals (preventing arbitrary
// agents from forging tokens for impls they don't own).
ResourceTypeServiceImpl = "service_impl"
+ // ResourceTypeWorkspaceExecutionView authorizes a concrete execution view
+ // within a logical MemoryLayer workspace. The resource ID is the stable
+ // view ID, not a client-local filesystem path.
+ ResourceTypeWorkspaceExecutionView = "workspace-execution/view"
+ // ResourceTypeToolCatalogProvider authorizes catalog lifecycle operations
+ // for one provider identity.
+ ResourceTypeToolCatalogProvider = "tool-catalog/provider"
+ // ResourceTypeToolCatalogEntry authorizes discovery and invocation of one
+ // provider-qualified catalog entry.
+ ResourceTypeToolCatalogEntry = "tool-catalog/entry"
+ // ResourceTypeWorkflowSchedule authorizes exact WorkflowEngine schedule
+ // definitions. Resource IDs are canonical
+ // workspaces/{workspace}/schedules/{schedule} paths.
+ ResourceTypeWorkflowSchedule = "workflow/schedule"
)
diff --git a/versions.yaml b/versions.yaml
index 2d14de8..fe593de 100644
--- a/versions.yaml
+++ b/versions.yaml
@@ -9,23 +9,24 @@
# directives in each go.mod handle local builds; the `require` pins handle downstream
# `go get` from pkg.go.dev. The gomod_require rules below keep those pins in sync.
-aether-gateway: 0.2.2
+aether-gateway: 0.2.3
-aether-sdk-go: 0.2.2 # github.com/scitrera/aether/sdk/go (also drives github.com/scitrera/aether/api require pins)
+aether-sdk-go: 0.2.3 # github.com/scitrera/aether/sdk/go (also drives github.com/scitrera/aether/api require pins)
-aether-sdk-typescript: 0.2.2 # @scitrera/aether-client
+aether-sdk-typescript: 0.2.3 # @scitrera/aether-client
-aether-sdk-python: 0.2.2 # scitrera-aether-client
+aether-sdk-python: 0.2.3 # scitrera-aether-client
aether-sdk-python-ag2: 0.0.2 # scitrera-aether-ag2
go_toolchain:
- go: "1.25.12"
+ go: "1.25.14"
# set explicit versions where possible to protect against supply chain attacks
preferred_versions:
go:
- "google.golang.org/grpc": "1.81.1"
+ "google.golang.org/grpc": "1.82.1"
"google.golang.org/protobuf": "1.36.11"
+ "golang.org/x/net": "0.55.0"
"github.com/scitrera/go-backpressure": "v0.1.1"
python:
"grpcio": 1.81.1
@@ -112,27 +113,27 @@ ci:
# Preserves the go-coverage artifact that the hand-written test.yml uploaded.
coverage: true
# Accepted risk, scoped to the SDK because that is the only module that
- # reaches them: all four are in github.com/docker/docker <= v28.5.2 via
- # sdk/go/orchestrators/docker, and none has an upstream fix. Documented in
- # SECURITY.md; drop these entries when docker ships fixed releases — CI
- # warns once an entry stops matching.
+ # imports the legacy github.com/docker/docker client. These advisories
+ # describe Docker Engine plugin behavior, while Aether only uses client
+ # packages; the Go vulnerability records nevertheless have no symbol-level
+ # data or fixed release for this legacy module path. Documented in
+ # SECURITY.md; CI warns once an entry stops matching.
govulncheck_ignore:
- id: GO-2026-4887
- reason: "docker/docker <= v28.5.2; no upstream fix; see SECURITY.md"
+ reason: "legacy docker client module; Engine-only advisory; see SECURITY.md"
projects: [ aether-sdk-go ]
- id: GO-2026-4883
- reason: "docker/docker <= v28.5.2; no upstream fix; see SECURITY.md"
+ reason: "legacy docker client module; Engine-only advisory; see SECURITY.md"
projects: [ aether-sdk-go ]
- - id: GO-2026-5617
- reason: "docker cp bind-mount redirection race; no upstream fix; see SECURITY.md"
- projects: [ aether-sdk-go ]
- - id: GO-2026-5668
- reason: "docker cp symlink-swap empty-file race; no upstream fix; see SECURITY.md"
- projects: [ aether-sdk-go ]
- # Verify that scripts/tag-release.sh actually cut api/, sdk/go/ and server/
- # tags for the release. Flip to `push` — and drop tag-release.sh — once a
- # real release has proven the wiring.
- module_tags: verify
+ # The root `vX.Y.Z` tag is the whole release action: this job creates and
+ # pushes the api/, sdk/go/ and server/ module tags Go resolves against.
+ # Proven in `verify` mode on v0.2.2, where all four tags were cut by hand.
+ #
+ # Pushing only the root tag also sidesteps a GitHub limit that bit that
+ # release: pushing more than three tags at once produces no push event, so
+ # the four-tag push fired no workflows at all until the root tag was
+ # re-pushed on its own.
+ module_tags: push
docker:
# Empty to preserve build.yml's behavior: images build on a tag that was
# already tested on its branch, with no inlined test gate.
@@ -151,7 +152,7 @@ docker:
version_from: aether-gateway
aetherlite:
context: server
- dockerfile: server/Dockerfile.aetherlite-dev
+ dockerfile: server/Dockerfile.aetherlite
needs: aether
version_from: aether-gateway
aetherlite-dev: