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
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3379,3 +3379,17 @@ Core Session or container creation. One conversation/Agent binding freezes the
request on first execution; later messages and observer retries reuse it. Separate
conversations get independent Sessions and environments. Edits affect future
Sessions only. Keep the existing product navigation and direct empty-chat composer.

### API-key write provenance

Public resource writes carry authenticated key provenance separately from the
execution principal. Persist their operation record and genuine creation ownership
in the same business transaction; no best-effort response middleware or async audit
queue. A failed audit must roll back the write. Internal lifecycle/refresh work does
not acquire public provenance. Retries never replace ownership. Environment uploads
persist safe request origin before dispatch and record success with the confirmed
receipt, not the native filesystem call. Never put payloads, paths or secrets in
audit metadata. Read models are deployment-authenticated `/core/v1` extensions;
keep `/v1` wire contracts unchanged. See
[write-audit.md](contracts/agents-api/write-audit.md) for coverage, retention and
console integration. Do not confuse key identity with Session creator identity.
4 changes: 4 additions & 0 deletions contracts/agents-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,3 +905,7 @@ Session or Environment observations and is not a readiness endpoint.
Model endpoints and credentials may be supplied at Session creation through the
[write-only execution extension](model-execution.md). Provider catalogs and their
business permissions remain client/product responsibilities.

API-key ownership and write history are documented in
[write-audit.md](write-audit.md). These are deployment-authenticated Core console
extensions; the pinned public `/v1` schema is unchanged.
171 changes: 171 additions & 0 deletions contracts/agents-api/sandbox-manager.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ definitions:
name:
type: string
type: object
api.ResourceOwnerList:
properties:
data:
items:
$ref: '#/definitions/store.ResourceOwner'
type: array
type: object
api.SandboxAllocationList:
properties:
data:
Expand Down Expand Up @@ -82,6 +89,19 @@ definitions:
$ref: '#/definitions/store.RuntimeNode'
type: array
type: object
store.AuditAPIKey:
properties:
id:
type: string
kind:
type: string
name:
type: string
prefix:
type: string
revoked_at:
type: string
type: object
store.IssuedExecutorCredential:
properties:
environment_id:
Expand Down Expand Up @@ -119,6 +139,13 @@ definitions:
revoked_at:
type: string
type: object
store.ResourceOwner:
properties:
api_key:
$ref: '#/definitions/store.AuditAPIKey'
resource_id:
type: string
type: object
store.RuntimeDeploymentView:
properties:
core_url:
Expand Down Expand Up @@ -273,6 +300,38 @@ definitions:
maintenance:
type: boolean
type: object
store.WriteOperation:
properties:
action:
type: string
api_key:
$ref: '#/definitions/store.AuditAPIKey'
created_at:
type: string
id:
type: string
parent_id:
type: string
request_id:
type: string
resource_id:
type: string
resource_type:
type: string
trace_id:
type: string
type: object
store.WriteOperationPage:
properties:
data:
items:
$ref: '#/definitions/store.WriteOperation'
type: array
has_more:
type: boolean
next_cursor:
type: string
type: object
v1.APIError:
properties:
code:
Expand Down Expand Up @@ -519,6 +578,53 @@ paths:
summary: Revoke a project API key
tags:
- Project API Keys
/core/v1/resource-owners:
get:
description: Deployment administrator only. A configured binding selects the project. Returns null for resources without recorded creation provenance, including historical and foreign resources. No key secret is returned.
parameters:
- description: Configured caller SHA-256 binding selector
in: query
name: binding_digest
required: true
type: string
- description: Resource type
in: query
name: resource_type
required: true
type: string
- description: Comma-separated public resource IDs, maximum 100
in: query
name: resource_ids
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.ResourceOwnerList'
"400":
description: Bad Request
schema:
$ref: '#/definitions/v1.ErrorResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/v1.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/v1.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/v1.ErrorResponse'
security:
- DeploymentAdminAuth: []
summary: Batch lookup resource creation keys
tags:
- Write Audit
/core/v1/sandbox/deployment:
get:
description: Core deployment extension. Does not grant project resource access. Responses contain only explicit safe fields.
Expand Down Expand Up @@ -1010,6 +1116,71 @@ paths:
summary: List retained allocations on a sandbox node
tags:
- Sandbox Manager
/core/v1/write-operations:
get:
description: Deployment administrator only. Reverse chronological keyset pagination over committed writes. Creation records remain; other records follow configured retention. A configured binding selects the project, never a caller-supplied tenant.
parameters:
- description: Configured caller SHA-256 binding selector
in: query
name: binding_digest
required: true
type: string
- description: Issued UUID or static key identifier
in: query
name: key_id
type: string
- description: Resource type
in: query
name: resource_type
type: string
- description: Public resource ID
in: query
name: resource_id
type: string
- description: Inclusive RFC3339 timestamp
in: query
name: created_after
type: string
- description: Exclusive RFC3339 timestamp
in: query
name: created_before
type: string
- description: Page size, 1-100, default 50
in: query
name: limit
type: integer
- description: Opaque next_cursor from the preceding page
in: query
name: after
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/store.WriteOperationPage'
"400":
description: Bad Request
schema:
$ref: '#/definitions/v1.ErrorResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/v1.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/v1.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/v1.ErrorResponse'
security:
- DeploymentAdminAuth: []
summary: Query API-key write operations
tags:
- Write Audit
schemes:
- http
- https
Expand Down
131 changes: 131 additions & 0 deletions contracts/agents-api/write-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# API-key write provenance

This Core extension does not change the pinned public `/v1` protocol. It serves
administrator consoles; it is not an HTTP access log, execution event stream or
replacement for a Session's creator identity.

## Authentication and ownership

Authentication carries the actual issued key UUID, independently of its inherited
principal. Static configured keys use `static:<lowercase SHA-256 digest>` as a stable,
non-secret identifier and the digest's first eight characters as the display prefix.
Optional `name` labels a static key; optional `kind: "console"` identifies a trusted
console credential (`static` is the default). Newly generated installer bindings use
`name: "Console", kind: "console"`. These fields describe the credential, not the
HTTP client: using that same key in an SDK retains its console label. Client headers
cannot assert a key identity or console origin. Display renaming does not change ID.

True creation stores ownership in the same transaction as the resource and operation.
Updates, retries and successful no-ops never replace it. Initial Skill versions and
new Session Environments share their parent creation operation's provenance. Runtime
produced Artifacts have no API-key creation anchor; deleting one is still recorded.
Missing historical or foreign ownership is `null`. No historical resources are backfilled.

An issued key's current `revoked_at` comes from its retained key row. Revocation
prevents new authentication but does not invalidate already admitted work or erase
history. Removing a static key from configuration stops authentication; there is no
static-key revocation timestamp, so `revoked_at` remains null. Recorded static key
name/prefix/kind are immutable snapshots. Deleting a resource does not delete its
operation history or creation anchor.

## Transaction and success boundary

A successful write means a durable business commit, not successful HTTP delivery or
successful subsequent model execution. Each request has a server-generated
`request_id` for deduplication and the request's diagnostic `trace_id`. A shared
trace across requests is not an idempotency key. Business rollback also rolls back
the operation and ownership. Failure to persist the audit fails the transaction.
Reads, failed validation/authorization and internal maintenance writes are not logged.

Session input is recorded when durably admitted, including prepared-Environment
reservations. Later execution failure or response disconnection does not undo that
accepted write. Explicit successful empty-input and creation/deletion retries get
one operation without changing ownership. Automatic promotion/refresh/cleanup does
not create another public operation.

Environment file bytes are written by the existing Runtime, outside PostgreSQL.
Preparation durably stores only the safe key/request/trace origin. The confirmed
success receipt and audit commit together; uncertain/failed uploads are not reported
as successful operations. Recovery uses the saved origin. This is not a claim of
atomic transactions across the filesystem and PostgreSQL.

Recorded fields are ID, timestamp, safe key metadata, action, resource type/ID,
parent ID, request ID and trace ID. Never store request/response bodies, bearer
secrets, model credentials, tokens, file paths or file contents in these tables.

| Public write | Action | Resource / parent |
| --- | --- | --- |
| Agent create/update/delete | create/update/delete | agent |
| Session create/update/delete | create/update/delete | session |
| Session events | send_events | session |
| Session Artifact delete | delete | artifact / session |
| Environment file upload | upload_file | environment / session |
| Environment Template create/update/delete | create/update/delete | environment_template |
| Skill create/default/delete | create/update_default_version/delete | skill |
| Skill version upload/delete | upload_version/delete | skill_version / skill |
| Source File upload/delete | create/delete | file |
| Vault create/delete | create/delete | vault |
| Credential create/replace/delete | create/update/delete | credential / vault |

Explicit public OAuth Credential writes are covered. Automatic OAuth refresh is not
a separate public write. Public resource IDs retain their existing formats.

## Console queries

Both endpoints require the deployment Bearer credential used by
`/core/v1/project-api-keys`. Project keys, including console project credentials,
cannot call them. Required `binding_digest` selects an existing configured static
binding and its tenant/project; it does not authenticate. There is no arbitrary
tenant selector. Two bindings for the same project see the same project history.

### Batch ownership

`GET /core/v1/resource-owners?binding_digest=...&resource_type=agent&resource_ids=id1,id2`

`resource_type` is one of `agent`, `session`, `environment`,
`environment_template`, `skill`, `skill_version`, `file`, `vault`, `credential`,
`artifact`. Pass 1–100 comma-separated public IDs. Results preserve input order:

```json
{"data":[
{"resource_id":"id1","api_key":{"id":"key-uuid","name":"SDK","prefix":"pc_example","kind":"issued","revoked_at":null}},
{"resource_id":"id2","api_key":null}
]}
```

### Operations

`GET /core/v1/write-operations?binding_digest=...&limit=50`

Optional filters: `key_id`, `resource_type`, `resource_id`, `created_after`
(inclusive RFC3339 timestamp), `created_before` (exclusive RFC3339 timestamp).
`limit` is 1–100, default 50. Pass the previous `next_cursor` as `after`, keeping
filters unchanged. Results sort by descending `(created_at, id)` with keyset
pagination; a cursor is not an authorization token or a snapshot of future writes.
The response is `{ "data": [...], "has_more": false, "next_cursor": "" }`.
Each operation contains `id`, `created_at`, `api_key`, `action`, `resource_type`,
`resource_id`, `parent_id`, `request_id`, `trace_id`. An absent parent is an empty
string. Empty pages contain `data: []`.

Malformed/duplicate/unknown query parameters return 400, missing configured binding
404, invalid deployment authentication 401. These rules belong only to the new
Core routes and do not alter public list parsing or errors. Creation records remain
queryable after resource deletion; expired non-creation records do not.

## Retention

`AGENTS_API_WRITE_AUDIT_RETENTION` accepts a Go duration of at least one hour;
default `2160h` (90 days). Every minute Core removes at most 1,000 expired
non-creation records in a bounded transaction. Creation records and anchors are
retained permanently, which includes the entire lifetime of a resource and its
history after deletion. Key revocation/resource deletion never triggers cleanup.
Retention removes only aged non-creation operations; it does not erase key metadata,
resource rows or ownership. A retention backlog can take multiple passes to drain.

## Validation

The batch's Store tests cover each mutation, induced audit-write failure with
business rollback, tenant isolation, ownership immutability, revocation, cursor
filters and retention. HTTP tests cover authenticated source propagation and
administrator-only query validation. Live evidence and remaining limitations are
recorded separately; passing these tests does not claim full Agents API compatibility.
1 change: 1 addition & 0 deletions deploy/install/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ def initialize(root, args, manifest):
state["device_gid"] = device_gid
write_json(config / "keys.json", [{"tenant_id": str(uuid.uuid4()), "organization_id": "installation",
"project_id": "default", "subject_kind": "service_account", "subject_id": "operator",
"name": "Console", "kind": "console",
"token_sha256": hashlib.sha256(token.encode()).hexdigest()}])
private_write(config / "credential.key", base64.b64encode(secrets.token_bytes(32)).decode())
private_write(config / "database.password", secrets.token_hex(32))
Expand Down
11 changes: 11 additions & 0 deletions docs/getting-started/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,14 @@ Host virtualization, credentials, tenant isolation, durable state and actual
execution are release acceptance requirements. Other missing operational screens
or low-frequency improvements belong in the backlog; they do not turn this batch
into a Web redesign or a complete protocol-compatibility claim.

### API-key write history

Core records committed public resource writes and their key ownership for the
administrator console. Configure `AGENTS_API_WRITE_AUDIT_RETENTION` (Go duration,
minimum `1h`, default `2160h`) to control non-creation history. Creation ownership
remains permanently; removing keys or resources does not cascade-delete records.
Static key bindings may set a safe `name` and `kind: "console"` for console-owned
credentials. Existing resources without recorded provenance return null. See the
[query contract](../../contracts/agents-api/write-audit.md) for deployment-authenticated
batch ownership and cursor history endpoints. These APIs do not log bodies or secrets.
Loading
Loading