amduat-api/docs/amduatd-api-v2-design.md

197 lines
5.6 KiB
Markdown
Raw Normal View History

# amduatd API v2 Design (PEL-only writes)
## Status
Draft proposal for discussion.
First server slice implemented: `POST /v2/pel/execute` with `inputs.refs`,
`inputs.inline_artifacts[{body_hex,type_tag?}]`, and required `receipt`.
## Goals
- Keep `amduatd` thin: transport, parsing, auth, and mapping to core calls.
- Make **PEL the only write primitive** in HTTP v2.
- Enforce artifact provenance for writes: adding new artifacts must go through:
1. store inputs,
2. run a PEL program,
3. persist and return receipt.
- Keep `/v1/*` behavior unchanged while v2 is introduced.
## Non-goals
- Re-implementing PEL semantics in `amduat-api`.
- Creating new substrate semantics in daemon code.
- Removing v1 immediately.
## High-level model
v2 separates read and write concerns:
- Read paths remain direct and deterministic (`GET /v2/meta`, `GET /v2/artifacts/{ref}`, federation/space read endpoints).
- All state-changing operations are executed as PEL runs.
In v2, `POST /v1/artifacts` equivalent is replaced by a mutation endpoint that always drives a PEL run.
For simplified clients, v2 also exposes async operation endpoints:
- `POST /v2/ops/put`
- `POST /v2/ops/concat`
- `POST /v2/ops/slice`
- `GET /v2/jobs/{id}`
These enqueue work and execute PEL in the daemon background loop.
Graph-oriented endpoints can be added as thin wrappers over concepts/relations:
- `POST /v2/graph/nodes` (maps to concept create/publish)
- `POST /v2/graph/edges` (maps to relation edge append)
- `GET /v2/graph/nodes/{name}` (node + versions + incoming/outgoing edges)
- `GET /v2/graph/history/{name}` (version + edge timeline)
## Versioning and contract id
- Base path: `/v2`
- Contract id: `AMDUATD/API/2`
- Keep the same store-seeded contract model used in v1.
## Endpoint shape
### Read-only endpoints (v2)
- `GET /v2/meta`
- `GET /v2/contract`
- `GET /v2/artifacts/{ref}`
- `HEAD /v2/artifacts/{ref}`
- `GET /v2/artifacts/{ref}?format=info`
- Space/federation read endpoints can be mirrored under `/v2/*` unchanged where possible.
### Write endpoints (v2)
- `POST /v2/pel/execute`
No direct artifact write endpoint in v2 (`POST /v2/artifacts` is intentionally absent).
## `POST /v2/pel/execute`
Single mutation primitive for v2.
### Request
```json
{
"program_ref": "<hex-ref-or-name>",
"scheme_ref": "dag",
"params_ref": "<optional-ref-or-name>",
"inputs": {
"refs": ["<hex-ref-or-name>"],
"inline_artifacts": [
{
"content_type": "application/octet-stream",
"type_tag": "0x00000000",
"body_hex": "48656c6c6f"
}
]
},
"receipt": {
"input_manifest_ref": "<ref-or-name>",
"environment_ref": "<ref-or-name>",
"evaluator_id": "local-amduatd",
"executor_ref": "<ref-or-name>",
"started_at": 1731000000,
"completed_at": 1731000005
},
"effects": {
"publish_outputs": true,
"append_fed_log": true
}
}
```
### Processing contract
Server behavior is deterministic and ordered:
1. Resolve `program_ref`, `scheme_ref`, `params_ref`, and `inputs.refs` to refs.
2. Decode and store `inputs.inline_artifacts` into ASL store.
3. Build effective `input_refs = refs + stored_inline_refs`.
4. Execute PEL using core `pel_surf_run_with_result`.
5. Build FER receipt from run result + receipt fields.
6. Store receipt artifact and return `receipt_ref`.
7. Apply requested side effects (`publish_outputs`, `append_fed_log`) after successful run.
If any step fails:
- return non-2xx,
- do not publish outputs,
- do not append federation publish records.
## Response
```json
{
"run_ref": "<pel1-result-ref>",
"trace_ref": "<optional-trace-ref>",
"receipt_ref": "<fer1-receipt-ref>",
"stored_input_refs": ["<ref>"],
"output_refs": ["<ref>"],
"status": "OK"
}
```
Notes:
- `stored_input_refs` are refs created from `inline_artifacts`.
- `receipt_ref` is required for successful writes in v2.
## Error model
- `400` invalid request or schema violation.
- `404` referenced input/program/params not found.
- `409` conflict in post-run side effects (e.g. cursor/update conflicts).
- `422` run completed but business policy failed (reserved for policy checks).
- `500` internal failure.
Error payload:
```json
{
"error": {
"code": "invalid_input_ref",
"message": "input ref not found",
"retryable": false
}
}
```
## Behavioral differences from v1
- Removed write path: direct `POST /artifacts`.
- New requirement: every write returns a receipt ref tied to a PEL run.
- `pel/run` semantics become the write gateway rather than an optional compute endpoint.
## Compatibility and rollout
1. Add `/v2/pel/execute` while keeping `/v1/*` unchanged.
2. Mirror core read endpoints under `/v2/*`.
3. Mark `POST /v1/artifacts` as deprecated in docs.
4. Migrate clients to v2 execute flow.
5. Remove v1 write endpoints in a later major release.
## Open questions
- Should v2 require exactly one output for write operations, or allow multiple outputs with one receipt?
- Should inline artifact body support both `body_hex` and `body_base64`, or move to multipart for large payloads?
- Should `publish_outputs` default to `true` or be explicit-only?
- Do we need async execution (`202 + run status endpoint`) for long-running programs in v2?
## Minimal implementation plan
1. Add v2 contract bytes (`registry/amduatd-api-contract.v2.json`).
2. Add `/v2/pel/execute` handler by adapting existing `POST /v1/pel/run` + artifact ingest path.
3. Factor shared receipt parsing/building from current v1 pel handler.
4. Add tests:
- inline artifact -> stored -> run -> receipt success,
- failure before run,
- failure during run,
- side effect gating (no publish on failure).
5. Document deprecation notice for `POST /v1/artifacts`.