Skip to content

ARVIS Access Control Specification v1

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals, as shown here.

Status

  • Version: v1
  • Scope: Normative
  • Component: Kernel Core, Authorization Layer
  • Position: Level 5 (Execution Model), subordinate to Gate (Level 2) and IR (Level 3)

1. Purpose

This specification defines the authorization layer of the ARVIS kernel: the mechanism by which the syscall boundary decides whether a given identity is permitted to perform a given syscall on a given resource.

It realizes the obligation already stated in ARVIS_SYSCALLS_SPEC_V1 (Security Model): policy-enforced authorization via kernel services. It promotes the "permission system" and "capability-based access control" items from that spec's Future Extensions into a normative, versioned contract.

This layer governs access, not cognition. It MUST NOT influence decision semantics, the Gate verdict, or the cognitive pipeline. It operates strictly at the execution boundary.


2. Position in the Hierarchy

  • Authorization is an execution policy (Level 5). It is evaluated at the syscall boundary, post-decision, consistent with the Execution Boundary Contract (no syscalls during cognition).
  • Authorization MUST NOT override the Gate. A denied access surfaces as a normative reason code (Level 3) consistent with the Gate verdict, never as a silent mutation of cognition.
  • Authorization MUST be deterministic and replay-safe: identical context yields an identical verdict, and verdicts are journaled like any syscall outcome.

3. Model

3.1 Principal

A Principal is the identity on whose behalf a syscall is executed.

Principal:
    user_id: str
    organization_id: str | None
    grants: frozenset[str]

A bare principal (user_id only, no organization, no grants) denotes the resource owner. This reproduces the pre-authorization behaviour, where access was scoped solely by user_id.

For a production effect, a bare Principal is insufficient. The host MUST stamp an AuthenticatedPrincipal, which extends the same owner/grant model with non-secret governance material:

AuthenticatedPrincipal:
    user_id: str
    organization_id: str | None
    grants: frozenset[str]
    authentication_source: str
    authentication_strength: str
    service_id: str | None
    session_id_hash: str | None

ARVIS does not validate credentials. Construction of this object is the host's attestation that authentication already occurred.

3.2 AccessContext

An AccessContext is the tuple evaluated by a policy.

AccessContext:
    principal: Principal
    effect: SyscallEffect          # READ or EFFECT (per ARVIS_SYSCALLS_SPEC)
    resource_owner_id: str
    resource_organization_id: str | None   # owning organization, None when personal
    resource_id: str | None
    resource_scope: str | None     # opaque narrower-than-organization scope, None when unscoped
    syscall_name: str | None

resource_scope is an opaque token naming an area narrower than the organization (a matter, a project, whatever the realization layer names it). This layer never parses it. None means the resource is not sub-scoped, the behaviour every resource had before scoped grants existed. A resource resolver copies the value verbatim from the resource into the context; the coverage rule (Section 4) decides whether the principal's grants reach that scope.

3.3 AccessDecision and AccessVerdict

AccessDecision := ALLOW | DENY

AccessVerdict:
    decision: AccessDecision
    reason_code: str | None        # canonical access-layer code on DENY

4. Authorization Policy Contract

An AuthorizationPolicy exposes a single operation:

decide(context: AccessContext) -> AccessVerdict

Requirements (normative):

  • Deterministic: an identical context MUST yield an identical verdict.
  • Side-effect free: decide MUST NOT perform I/O or mutate state.
  • Fail-closed: when the answer is uncertain, the policy MUST return DENY.
  • On DENY, the verdict MUST carry a canonical access-layer reason code.

4.1 Scope coverage

When a policy honours resource_scope, coverage is decided by an injectable rule of the shape:

scope_covers(principal: Principal, resource_scope: str | None) -> bool

Normative properties of this rule:

  • it operates on opaque tokens; this layer never parses a scope;
  • resource_scope is None MUST be covered (an unscoped resource keeps its pre-scope behaviour), so injecting a rule is behaviour-neutral for resources that carry no scope;
  • the default rule is verbatim membership: the scope token must be present in principal.grants.

A resource that belongs to an organization AND carries a scope is ALLOW only if the principal belongs to the organization, holds the required capability, AND the scope is covered. The three conditions are cumulative.

4.2 Resolved lookup outcomes

A policy decides on resolved metadata. The resource resolver that populates resource_owner_id, resource_organization_id and resource_scope MUST distinguish three cases:

  • the resource reference is absent (for example creation at the root): not an error, the resource is genuinely unscoped, resource_scope is None;
  • the lookup produces an expected terminal condition, such as VFSItemNotFoundError: no resource has been authorized. The resolver MAY carry that condition with a neutral context solely so a caller otherwise entitled to the addressed owner scope receives the precise domain code. The syscall body MUST consume the carried condition WITHOUT reading or mutating the service. It MUST NOT treat the neutral context as authority over a resource that appears later;
  • the lookup fails unexpectedly (infrastructure, proxy, cache or other indeterminate failure): the resolver MUST propagate the failure. The SyscallHandler converts the authorization-machinery exception into an authorization_failure refusal and MUST NOT dispatch the syscall body.

For a successful pure read, the resolver SHOULD carry the exact resource it authorized and the body SHOULD return that instance without a second lookup. The resource and expected-error outcomes are mutually exclusive, and their kernel handoff fields MUST NOT be caller-supplied. These rules prevent a time-of-check/time-of-use retry from returning or mutating a resource after authorization was evaluated on an absence.


5. Enforcement

  • Authorization is enforced at the syscall boundary, via a kernel service registered in the KernelServiceRegistry (per ARVIS_KERNEL_SERVICES_SPEC).
  • The SyscallHandler MUST evaluate the active policy before dispatching a governed syscall, and MUST refuse a DENY verdict by returning a failed SyscallResult carrying an ArvisSecurityError (domain = kernel.security, fail-closed).
  • A refused syscall MUST be journaled like any other syscall outcome (full observability, replay-safe).
  • The absence of a configured policy MUST be treated as the default owner-scoped policy (Section 7), never as unconditional allow.

Enforcement wiring is live in the handler: SyscallHandler resolves the access context and calls the authorization service's decide() before dispatch.

5.1 Identity Transport and Trust Boundary

The Principal is ambient identity, carried on the execution context and stamped by the realization layer at the start of a run, not passed as a per-syscall operational argument. This separates the trusted identity channel (the context, set from an authenticated session) from the operational parameter channel (the syscall arguments, which may be influenced by cognition downstream of the Gate). Routing identity through the argument channel would let a caller self-declare its own rights on every call; routing it through the context models identity as process state, established once at authentication.

The identity carried on the context is a trusted input. ARVIS does not authenticate it and MUST NOT derive it from cognition; the authorization guarantee of this layer is conditional on the realization layer establishing that identity from an authenticated session. Local, test and research postures may fall back to a bare user-scoped principal. In production, every EFFECT syscall MUST carry an AuthenticatedPrincipal whose user_id matches the turn owner (or the exact kernel principal for kernel-internal effects); otherwise the syscall is refused before intent persistence and before the effect.


6. Reason Code

A new normative reason code is registered in the access layer:

access_denied   (layer: access, severity: high, normative)

Per ARVIS_REASON_CODE_REGISTRY, a high normative reason code MUST force the verdict to be not ALLOW. This guarantees that an access denial surfaces as a governed refusal and is distinguishable in the IR from a "not found" or "absent" outcome, which prevents information leakage through the difference between the two.


7. Reference Policies (Implementation-Aligned)

The reference implementation provides two generic policies. Both are mechanisms: neither encodes any organization-specific or domain-specific governance. Such governance belongs to the realization layer, which composes or injects policies on this same contract.

7.1 OwnerScopedAuthorization (default)

  • ALLOW iff principal.user_id == context.resource_owner_id.
  • DENY otherwise, with reason code access_denied.

This policy is behaviour-neutral: wiring it as the default reproduces the pre-authorization access model (owner == user_id) exactly. It is the policy assumed by Section 5 when no policy is configured.

7.2 OrganizationScopedAuthorization

A generic, organization-aware policy that subsumes the personal case:

  • A resource with no organization (resource_organization_id is None) is owner-scoped, identical to 7.1.
  • A resource that belongs to an organization is ALLOW iff the principal belongs to that same organization (principal.organization_id == context.resource_organization_id) and holds the required capability (the capability token is a member of principal.grants).
  • DENY otherwise, with reason code access_denied.

The required capability is derived from the context. The reference derivation maps the structural effect to a capability (READ to a read capability, EFFECT to a write capability). This derivation is injectable: a realization layer MAY supply its own mapping from effect or syscall to a domain-specific capability without modifying this layer.

7.3 Grants and scopes are opaque

grants are opaque tokens to this layer. ARVIS only tests membership of a required capability, and of a resource scope, in principal.grants. It never parses a grant or a scope. Assigning meaning to those tokens, constructing the Principal (its organization and grants) from an identity system, labelling resources with their owner, organization and scope, deciding which capability a given access requires, and authoring the coverage rule and any governance beyond these reference mechanisms are responsibilities of the realization layer. This boundary keeps the standard generic and auditable while organization-specific governance, including how a scope is named and which grants cover it, remains private to the realization layer.


8. Compliance

An implementation conforms to this specification if and only if:

  • every governed syscall is evaluated by an authorization policy before dispatch;
  • a DENY verdict fails closed with an ArvisSecurityError and is journaled;
  • the access_denied reason code is emitted on denial and is consistent with the Gate verdict;
  • policy evaluation is deterministic and replay-safe;
  • the absence of a configured policy defaults to owner-scoped authorization;
  • resource_scope is treated as opaque and is never parsed by this layer;
  • a None scope is covered, and belonging to an organization with the required capability does not by itself grant access to a scoped resource whose scope the principal's grants do not cover;
  • an unexpected resource-resolution failure never yields ALLOW;
  • an expected terminal lookup condition may preserve its precise domain code only if the body consumes it without any second lookup or effect;
  • an internal resolved-resource/error handoff is unambiguous and cannot be supplied by a syscall caller.

9. Design Principle

Cognition decides what is wise. The Gate decides what is admissible. Authorization decides what is permitted.

These are distinct authorities. Authorization constrains execution without ever reshaping cognition or overriding the Gate.