Files
aios/docs/superpowers/specs/2026-08-19-durable-owner-authorization.md
T

6.2 KiB

Durable Owner Authorization and capability projection

Problem

AIOS already supports unattended worker execution after a Task is executable, but Owner intent was not durable at the capability layer. The same Owner repeatedly had to authorize or repair:

  • governed-task:project;
  • release:candidate.

This caused valid one-time Owner intent to be lost between conversations and forced repeated human interaction.

Design

Durable authorization is represented by a root-owned mode 0600 policy outside Git. The policy is persistent until explicitly revoked.

The first persistable capabilities are deliberately narrow:

governed-task:project
release:candidate

They cover preparation work only.

The following capabilities can never be inherited from this durable grant:

submitOwnerApproval
startDeployment
tag_publish
baseline_registry_finalize
runtime_switch
main_write
production_deploy
force_push
credential_admin

Those remain separate high-risk release/Owner-signature gates.

Policy

Example schema:

{
  "schema": "aios.owner-durable-authorization.v1",
  "grant_id": "owner-permanent-preparation-20260819",
  "owner_identity": "owner@example",
  "account": "chatgpt-governed-implementer-singlehost",
  "capabilities": [
    "governed-task:project",
    "release:candidate"
  ],
  "issued_at": "2026-08-19T05:00:00Z",
  "not_before": "2026-08-19T05:00:00Z",
  "persistent_until_revoked": true,
  "constraints": {
    "repository": "aios/aios",
    "allowed_target_prefix": "agent/",
    "release_version_prefix": "V2R2C",
    "max_task_risk": "engineering"
  }
}

The repository never stores the live Owner policy or release credential.

Revocation

A root-owned mode 0600 revocation file lists revoked_grant_ids.

Revocation is checked on every operation, not only at service startup. A running broker therefore fails closed immediately after revocation without needing a new conversation.

Broker boundary

The broker exposes only an AF_UNIX socket:

/run/aios-owner-authorization-broker/broker.sock

The socket is mode 0660 and owned by the service's aios-executor-exchange group. This avoids introducing another long-lived bearer secret solely for local broker access.

It exposes only:

GET  /v1/capabilities
POST /v1/project-governed-task
POST /v1/authorize-release-candidate

project-governed-task calls only the canonical Bridge.

authorize-release-candidate persists an Owner authorization decision only. It deliberately does not invent a local trusted-builder endpoint. The external release connector remains responsible for projecting the account's real release:candidate scope. Until that external projection exists, the decision state is AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION.

The broker holds only the existing host control credential needed for canonical governed-task projection. It never holds an Owner signing key or a release/deployment credential. The caller also supplies the expected account identifier over the Unix transport; the OS socket permission is the local access boundary.

Canonical Online Task projection

A durable governed-task:project grant may create a complete canonical Online Task rather than a bare Task. It requires:

require_online_task_projection=true
generation=1
branch_mode=CREATE_NEW
max_attempts<=3
risk<=engineering
initial action=source_capture

Projected task capabilities are limited to:

git.read
git.test
governed.write.branch
governed.write.file

A write-capable task still begins as source_capture. Existing placeholder-deferral prevents the generic worker from claiming it until a deterministic plan is registered. Repository mutation therefore still requires the normal deterministic plan, WriterLease and governed commit path.

This fixes the previous split-brain behavior where a bare Task could exist without the canonical governed execution projection.

External capability projection outbox

A release:candidate authorization now creates a durable machine-readable outbox record:

schema=aios.external-capability-projection.request.v1
state=PENDING_EXTERNAL_CONNECTOR
capability=release:candidate

The projection identifier is deterministic from:

grant_id
account
capability
request_sha256

The outbox stores no credentials or Owner signing material. It retains only the already-authorized release-candidate request, policy/request hashes, account, grant id and timestamps.

The broker exposes read-only Unix-socket endpoints:

GET /v1/projections
GET /v1/projections/{projection_id}

These endpoints re-check the current Owner policy on every read. If the durable grant has been revoked, enumeration fails closed with OWNER_GRANT_REVOKED. The historical outbox record is retained for audit and is not silently deleted.

The repository intentionally does not provide a connector acknowledgement/write endpoint yet. No in-repository code can truthfully assert that the external ChatGPT connector account has gained release:candidate. Until the platform consumes the outbox and performs its own identity-scope projection, the only valid state is:

AUTHORIZED_PENDING_EXTERNAL_CAPABILITY_PROJECTION
PENDING_EXTERNAL_CONNECTOR

A future external projector must preserve:

release:candidate
!= submitOwnerApproval
!= startDeployment
!= tag_publish
!= baseline_registry_finalize
!= runtime_switch

Idempotency and audit

Every broker operation requires an idempotency key. A receipt SQLite database stores only:

  • idempotency key;
  • capability;
  • request SHA-256;
  • response SHA-256;
  • sanitized upstream response;
  • timestamp.

An exact retry returns the same receipt. Reusing an idempotency key with different content fails.

Unattended invariant

Once the Owner policy and narrow service credentials are provisioned:

Owner durable grant
→ broker decision
→ canonical governed task projection / release candidate preparation
→ worker / trusted builder

No repeated Owner interaction is required for those preparation capabilities.

Production execution remains:

release candidate
→ immutable approval envelope
→ Owner signature
→ deployment

The durable grant never substitutes for the Owner signature.