Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 76 additions & 8 deletions cmd/veer-reference-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package main
import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"flag"
"fmt"
Expand All @@ -21,9 +22,14 @@ import (

"github.com/ArdurAI/veer/internal/adapters/referenceaccess"
"github.com/ArdurAI/veer/internal/adapters/store/memory"
"github.com/ArdurAI/veer/internal/core/domain/authorization"
"github.com/ArdurAI/veer/internal/core/domain/hierarchy"
"github.com/ArdurAI/veer/internal/core/domain/identity"
"github.com/ArdurAI/veer/internal/core/domain/model/v1alpha1"
"github.com/ArdurAI/veer/internal/core/domain/resource"
"github.com/ArdurAI/veer/internal/core/ports"
"github.com/ArdurAI/veer/internal/core/service/reference"
"github.com/ArdurAI/veer/internal/core/service/referenceauthorization"
httptransport "github.com/ArdurAI/veer/internal/transport/http"
)

Expand Down Expand Up @@ -67,22 +73,26 @@ func run(ctx context.Context, args []string, output io.Writer, listen listenFunc
if err != nil {
return fmt.Errorf("construct reference principal: %w", err)
}
access, err := referenceaccess.New(credential, principal, httptransport.ReferenceActions())
access, err := referenceaccess.New(credential, principal)
if err != nil {
return fmt.Errorf("construct reference access boundary: %w", err)
}
pageTokenKey := make([]byte, 32)
if _, err := io.ReadFull(entropy, pageTokenKey); err != nil {
return errors.New("read reference page-token entropy")
}
service, err := reference.New(reference.Config{
rawService, err := reference.New(reference.Config{
Store: memory.NewStore(), Clock: reference.ClockFunc(func() time.Time { return time.Now().UTC() }),
Issuer: &reference.SequentialIssuer{}, PageTokenKey: pageTokenKey,
})
if err != nil {
return fmt.Errorf("construct reference service: %w", err)
}
handler, err := httptransport.NewReferenceHandler(service, access, access)
service, err := seedAuthorizationRuntime(ctx, rawService, principal)
if err != nil {
return fmt.Errorf("seed reference authorization runtime: %w", err)
}
handler, err := httptransport.NewReferenceHandler(service, access)
if err != nil {
return fmt.Errorf("construct reference handler: %w", err)
}
Expand Down Expand Up @@ -166,12 +176,70 @@ func loadCredential(path string) (ports.BearerCredential, error) {
}

func referencePrincipal() (identity.Principal, error) {
workload, err := identity.NewWorkloadIdentity("veer-reference-server")
return identity.NewPrincipal(identity.PrincipalInput{
Kind: identity.KindHuman, Issuer: "https://reference.veer.invalid",
Subject: "local-reference-operator", Audiences: []string{"veer-api"},
})
}

func seedAuthorizationRuntime(
ctx context.Context,
service *reference.Service,
principal identity.Principal,
) (*referenceauthorization.Service, error) {
workspaceBody, err := json.Marshal(v1alpha1.WorkspaceWrite{
APIVersion: v1alpha1.APIVersion,
Kind: hierarchy.KindWorkspace.String(),
Metadata: v1alpha1.WriteMetadata{DisplayName: "Reference Workspace"},
Spec: v1alpha1.WorkspaceWriteSpec{},
})
if err != nil {
return identity.Principal{}, err
return nil, err
}
return identity.NewPrincipal(identity.PrincipalInput{
Kind: identity.KindWorkload, Issuer: "https://reference.veer.invalid",
Subject: "local-reference-harness", Audiences: []string{"veer-api"}, WorkloadIdentity: &workload,
workspace, err := service.Create(ctx, reference.CreateCommand{
Principal: principal, Kind: hierarchy.KindWorkspace,
CanonicalTarget: "reference:bootstrap:workspace", IdempotencyKey: "reference-bootstrap-workspace-v1",
Body: workspaceBody,
})
if err != nil {
return nil, err
}
memberID, err := resource.ParseID("mem_reference_admin_0001")
if err != nil {
return nil, err
}
member, err := authorization.NewMemberRecord(authorization.MemberInput{
ID: memberID, WorkspaceID: workspace.ResourceID, Kind: principal.Kind(),
LogicalIdentity: principal.LogicalIdentity(),
})
if err != nil {
return nil, err
}
directory, err := authorization.NewMemberDirectory(workspace.ResourceID, []authorization.MemberRecord{member})
if err != nil {
return nil, err
}
policyBody, err := json.Marshal(v1alpha1.PolicyWrite{
APIVersion: v1alpha1.APIVersion,
Kind: hierarchy.KindPolicy.String(),
Metadata: v1alpha1.WriteMetadata{DisplayName: "Reference Workspace Administrator"},
Spec: authorization.PolicySpec{Bindings: []authorization.RoleBinding{{
MemberID: memberID, Role: authorization.RoleWorkspaceAdministrator,
Scope: authorization.Scope{Kind: authorization.ScopeKindWorkspace},
}}},
})
if err != nil {
return nil, err
}
parentID := workspace.ResourceID
if _, err := service.Create(ctx, reference.CreateCommand{
Principal: principal, Kind: hierarchy.KindPolicy, WorkspaceID: workspace.ResourceID, ParentID: &parentID,
CanonicalTarget: "reference:bootstrap:policy", IdempotencyKey: "reference-bootstrap-policy-v1",
Body: policyBody, Members: directory,
}); err != nil {
return nil, err
}
return referenceauthorization.New(referenceauthorization.Config{
Reference: service, MemberDirectories: []authorization.MemberDirectory{directory},
})
}
34 changes: 30 additions & 4 deletions cmd/veer-reference-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,32 @@ func TestRunServesAuthenticatedRequestOnRealListenerAndShutsDown(t *testing.T) {
t.Fatal(err)
}

listRequest, err := http.NewRequest(http.MethodGet, baseURL+"/api/v1alpha1/workspaces", nil)
if err != nil {
t.Fatal(err)
}
listRequest.Header.Set("Authorization", "Bearer "+token)
listResponse, err := client.Do(listRequest)
if err != nil {
t.Fatal(err)
}
listBody, err := io.ReadAll(listResponse.Body)
if err != nil {
t.Fatal(err)
}
if err := listResponse.Body.Close(); err != nil {
t.Fatal(err)
}
var page struct {
Items []json.RawMessage `json:"items"`
}
if err := json.Unmarshal(listBody, &page); err != nil {
t.Fatal(err)
}
if listResponse.StatusCode != http.StatusOK || len(page.Items) != 1 {
t.Fatalf("authorized list response = %d, %s", listResponse.StatusCode, listBody)
}

body := strings.NewReader(`{"apiVersion":"v1alpha1","kind":"Workspace","metadata":{"displayName":"socket"},"spec":{}}`)
request, err := http.NewRequest(http.MethodPost, baseURL+"/api/v1alpha1/workspaces", body)
if err != nil {
Expand All @@ -81,12 +107,12 @@ func TestRunServesAuthenticatedRequestOnRealListenerAndShutsDown(t *testing.T) {
if err := response.Body.Close(); err != nil {
t.Fatal(err)
}
var receipt map[string]any
if err := json.Unmarshal(responseBody, &receipt); err != nil {
var problem map[string]any
if err := json.Unmarshal(responseBody, &problem); err != nil {
t.Fatal(err)
}
if response.StatusCode != http.StatusAccepted || receipt["resourceId"] == "" || receipt["operationId"] == "" {
t.Fatalf("authenticated response = %d, %#v", response.StatusCode, receipt)
if response.StatusCode != http.StatusForbidden || problem["code"] != "authorization-denied" {
t.Fatalf("reserved create response = %d, %#v", response.StatusCode, problem)
}

transport.CloseIdleConnections()
Expand Down
12 changes: 7 additions & 5 deletions docs/architecture/0003-http-api-and-resource-evolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ implementation.

Issue [#21](https://github.com/ArdurAI/veer/issues/21) subsequently implements
the four published paths and seven operations in a loopback-only in-memory
reference server. It uses the shared bearer parser, a fixed local credential
adapter, and an injected closed action gate. That executable harness is not
OIDC validation, tenant PolicySet enforcement, durable persistence, atomic
audit/outbox acceptance, or a production deployment. Those boundaries remain
owned by the issues below.
reference server. Issue [#24](https://github.com/ArdurAI/veer/issues/24) binds
that handler to retained PolicySet enforcement and a process-local
execution-time reauthorization callback. The harness uses the shared bearer
parser and a fixed local credential adapter; it is not production OIDC,
durable persistence or membership, atomic audit/outbox acceptance, a real
worker/provider dispatch path, or a production deployment. Those boundaries
remain owned by the issues below.

- Issue [#17](https://github.com/ArdurAI/veer/issues/17) implements the common
resource envelope, stable identity, serialization, and property tests.
Expand Down
43 changes: 28 additions & 15 deletions docs/architecture/0009-deterministic-hierarchical-authorization.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ Each of the seven existing operations carries one scalar
`x-veer-authorization-action`. The OpenAPI document remains at four paths and
seven operations.

This is a pure reference contract, not runtime request enforcement. No API
server or worker currently invokes the evaluator. Route integration,
execution-time reauthorization, persistence, and audit emission remain work for
their owning issues. In particular, the documented `createWorkspace` operation
At acceptance, this was a pure reference contract rather than runtime request
enforcement. Issue #24 subsequently bound the loopback reference handler to
retained PolicySet evaluation and added process-local execution-time
reauthorization. Production OIDC, durable persistence, worker/provider
dispatch, and audit emission remain work for their owning issues. In
particular, the documented `createWorkspace` operation
maps to `resource.create`, but Workspace creation is reserved and default
denied to every tenant role. Platform provisioning and bootstrap authority are
deferred to a separate governance decision.
Expand Down Expand Up @@ -69,6 +71,13 @@ admission reports missing references as `reference-not-found` and wrong or
incompatible kinds as `reference-kind-mismatch`; neither is an `invalid-spec`
failure.

Membership revocation makes existing bindings to the removed member inactive
at evaluation time without invalidating the retained Policy resource or its
generation. New and replaced Policy documents still reject references that
are absent from the active private member directory. This lets revocation take
effect immediately without allowing one removed member to make unrelated,
still-valid administrator bindings unevaluable.

### Roles and inheritance

Inheritance is an explicit star, not a transitive job hierarchy. `Developer`,
Expand Down Expand Up @@ -97,8 +106,8 @@ implementation must seal and evaluate each candidate retained row against that
row's Workspace policy state before including it in a response; a parent target
never authorizes its children, and an empty result does not create a synthetic
collection target. Pagination, filters, and counts cannot turn a denied row into
response data. Retained Plan, Operation, and resource-anchored Audit rows remain
unavailable until issue #24 adds their authoritative binding resolver.
response data. Retained Plan and resource-anchored Audit rows remain unavailable
until their owning runtimes add authoritative binding resolvers.

| Actions | Object | Direct role and resource-kind grants |
| --- | --- | --- |
Expand Down Expand Up @@ -138,15 +147,19 @@ The current transport projection is exact:
### Evaluation and canonical decisions

Evaluation accepts only a validated principal, a registered action, and a
hierarchy-sealed target. Public resolvers currently seal retained hierarchy
resources, server-derived create placements, and Workspace-scoped Membership or
Audit objects; callers cannot assert their Workspace or Environment ownership.
No public resolver for a retained Plan, Operation, or resource-anchored Audit
exists in this issue because a hierarchy snapshot cannot prove the separate
object-ID-to-resource binding. Those action-matrix entries remain fail closed.
Issue #24 must load the object by ID from authoritative persistence, derive its
immutable binding, and add the corresponding target-construction boundary before
runtime enforcement.
hierarchy-sealed target. At acceptance, public resolvers sealed retained
hierarchy resources, server-derived create placements, and Workspace-scoped
Membership or Audit objects; callers cannot assert their Workspace or
Environment ownership. Issue #24 added an Operation resolver that accepts only
an Operation loaded by the runtime and independently cross-checks its immutable
resource, Workspace, Environment, and ProviderConnection bindings against the
retained hierarchy. A successful mutation seals that derived target in its
bounded process-local admission record, so a retained delete Operation can
still be read, planned, and reauthorized after its resource is tombstoned; the
caller never supplies that fallback target. No public resolver for a retained
Plan or resource-anchored Audit exists; those action-matrix entries remain fail
closed until their owning runtime can prove the separate object-ID-to-resource
binding.

The immutable PolicySet binds the member directory and at most 2,500 ordered
Policy revisions, each with a desired-state generation. Its domain-separated
Expand Down
15 changes: 9 additions & 6 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ bounded decision representation. Its OpenAPI projection is a pure reference
contract; no API route or worker enforcement is implemented by that document.
Issue #21 adds a loopback-only reference server around the published
Workspace/Operation routes plus process-local lifecycle semantics for all six
resource kinds. It requires an injected bearer authenticator and closed action
gate, but does not invoke the PolicySet evaluator, persist state, emit durable
audit/outbox records, enqueue work, or execute a provider. Issue #24 owns
authoritative route authorization and issue #30 owns durable acceptance.
resource kinds. Issue #24 binds that handler to retained hierarchy and
PolicySet evaluation, per-row list filtering, admission decision/actor Plan
binding, and a process-local execution-time reauthorization callback. The
harness still does not validate production OIDC, persist durable state, emit
durable audit/outbox records, enqueue work, or execute a provider. Issue #30
owns durable acceptance.
[ADR 0010](0010-provider-neutral-credential-broker.md) fixes a provider-neutral,
process-local credential broker with separate secret-resolution and
session-issuance ports, immutable operation/target/recipient bindings, exact
Expand Down Expand Up @@ -160,8 +162,9 @@ not imply that asynchronous provider work has already completed.
- Tenant roles cannot grant worker, controller, provider-adapter, approval,
export, or redrive actions. Workspace creation/bootstrap remains default
denied until a platform provisioning decision exists.
- The current authorization package and OpenAPI manifest are reference
contracts; production API and worker wiring remains deferred.
- The loopback reference runtime enforces the current authorization package on
its published routes and process-local execution callback; production API,
durable membership/policy state, and worker/provider wiring remain deferred.
- Short-lived provider credentials wherever the provider supports them.

### Desired-state store
Expand Down
54 changes: 40 additions & 14 deletions docs/reference-server.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# In-memory reference server

Veer's first executable control-plane slice is the loopback-only
`veer-reference-server`. It is a deterministic contract harness for issue #21,
not the production `veer-api` service.
`veer-reference-server`. It is a deterministic contract and authorization
harness for issues #21 and #24, not the production `veer-api` service.

## Proven boundary

Expand Down Expand Up @@ -30,15 +30,30 @@ uses bounded header and connection timeouts, and requires a bearer credential
from a regular non-symlink token file with no group or world permissions.
Bearer syntax is parsed by Veer's fuzz-tested HTTP boundary; the reference
adapter reduces the configured credential to a per-process, randomly keyed
HMAC-SHA-256 digest immediately and compares fixed-size digests. A closed
seven-action gate is invoked after authentication on every published
operation.

That gate is deliberately not Veer's tenant authorization evaluator. The
fixed local Workload principal, bearer file, and action allow-list make the
harness controllable on a trusted developer host, but do not validate OIDC,
load Workspace membership or Policy resources, resolve a per-row list target,
or enforce production authorization. Issue #24 owns that integration.
HMAC-SHA-256 digest immediately and compares fixed-size digests.

Before listening, the command creates one process-local Workspace, private
Human member directory, and WorkspaceAdministrator Policy. The HTTP handler is
bound to the policy-enforcing runtime rather than the raw lifecycle service.
That runtime reloads retained hierarchy and Policy resources, evaluates a
sealed target for get/replace/delete and Operation get, and evaluates every
retained list row before it can influence page size or a cursor. Workspace
create and status replacement remain reserved and return `403` without a
resource or Operation mutation.

Accepted mutations retain a bounded process-local admission record. Plan
construction replaces caller-supplied actor, decision, and Operation fields
with that record and current retained Operation. Immediately before a
process-local execution callback, the runtime reloads current membership,
PolicySet, and Operation, then requires exact actor, policy-version, and
authorization-input bindings. Non-delete execution also reloads the current
resource generation. Delete replay and execution use only the server-sealed
pre-delete targets retained with the admission because the resource has
already been tombstoned. Revocation, policy drift, or applicable generation
drift prevents the callback. Expired idempotency epochs replace their prior
admission instead of leaving the new Operation unplannable. This is executable
reference evidence, not a queue, worker, provider adapter, production OIDC
path, or cross-process authorization guarantee.

## Run locally

Expand All @@ -65,13 +80,15 @@ Run the focused evidence set with:

```sh
go test ./internal/core/service/reference \
./internal/core/service/referenceauthorization \
./internal/adapters/store/memory \
./internal/adapters/referenceaccess \
./internal/transport/http \
./cmd/veer-reference-server \
./test/contract

go test -race ./internal/core/service/reference \
./internal/core/service/referenceauthorization \
./internal/adapters/store/memory \
./internal/adapters/referenceaccess \
./internal/transport/http \
Expand All @@ -84,7 +101,10 @@ SHA-256 digest, exercises deterministic black-box HTTP vectors, proves zero
database, queue, and provider calls, and compares the complete report with
[`reference-server-v1alpha1.golden.json`](../test/contract/testdata/reference-server-v1alpha1.golden.json).
The command test uses a real ephemeral TCP listener and proves authentication,
request handling, and graceful shutdown.
policy-filtered request handling, reserved-action denial, and graceful
shutdown. Authorization tests prove denied-mutation atomicity, per-row list
filtering, actor/decision Plan binding, policy drift, generation drift,
admission/revocation races, and effect/revocation exclusion.

## Explicit limitations

Expand All @@ -96,8 +116,14 @@ request handling, and graceful shutdown.
required for a production `202` response.
- The command is loopback-only and does not terminate TLS. Exposing it through
a proxy, container port, tunnel, or public listener is unsupported.
- The bearer adapter is a fixed local verifier, not OIDC. The action gate is
not Workspace membership or PolicySet authorization.
- The bearer adapter is a fixed local verifier, not OIDC. The preseeded private
member directory is process-local configuration rather than a membership
API or durable identity store. Removing a member makes its retained Policy
bindings inactive; new Policy writes still require every referenced member
to exist.
- The execution callback serializes only this process's configured membership,
policy mutations, and effect. It is not a distributed transaction or proof
that an external provider call can be cancelled after dispatch.
- There is no provider execution, queue, worker, audit sink, telemetry export,
persistent secret, cloud resource, or paid API call.

Expand Down
Loading