research: add /v1/research spec — versioned R&D evidence surface (HIP-0512)

Adds research/openapi.yaml (7 ops: ingest, list experiments, projects, totals,
grants, record artifact, list artifacts) matching the cloud research subsystem,
registers research under the Intelligence domain in capabilities.yaml, and
regenerates hanzo.yaml + CAPABILITIES.md via merge.py. SDK/CLI regen fires via
regenerate-sdks.yml on merge.
This commit is contained in:
2026-07-22 18:12:00 -07:00
parent 68ae2a7534
commit b54469a167
5 changed files with 875 additions and 1 deletions
+1
View File
@@ -31,6 +31,7 @@ One canonical name per capability = one `/v1/<name>` route = one `<name>/openapi
| `engine` | `/v1/engine` | ✓ |
| `agents` | `/v1/agents` | ✓ |
| `evals` | `/v1/evals` | ✓ |
| `research` | `/v1/research` | ✓ |
| `prompts` | `/v1/prompts` | ✓ |
| `functions` | `/v1/functions` | ✓ |
| `exec` | `/v1/exec` | ✓ |
+9
View File
@@ -2,6 +2,15 @@
## v1.0.0
### Add `research/` — the /v1/research versioned R&D evidence surface (HIP-0512)
Adds the `research` service under the Intelligence domain: the R&D evidence plane
(experiments, attempts, diary artifacts) served by `hanzoai/cloud/clients/research`.
Versioned append-only records with canonical-vs-retained counts, first-class provenance
(git + lib_versions), private-by-default visibility + separate consent grants, and
content-hash-addressed diary artifacts. Seven operations: ingest, list experiments,
projects, totals, grants, record artifact, list artifacts.
### Codegen-clean: dart-dio + typescript-axios crashes fixed; SDK regen unblocked
The `Regenerate SDKs` workflow failed on every merge for a week. Root causes,
+1 -1
View File
@@ -34,7 +34,7 @@ domains:
- id: intelligence
title: "Intelligence"
role: "models · agents · evaluation — the mind of the cloud"
services: [ai, ml, engine, agents, evals, prompts, functions, exec, graph]
services: [ai, ml, engine, agents, evals, research, prompts, functions, exec, graph]
- id: data
title: "Data"
+573
View File
@@ -128,6 +128,8 @@ tags:
description: Hanzo Referrals API
- name: registry
description: Hanzo Registry API
- name: research
description: Hanzo Research API
- name: s3
description: Hanzo S3 API
- name: search
@@ -169,6 +171,7 @@ x-tagGroups:
- engine
- agents
- evals
- research
- prompts
- functions
- exec
@@ -53150,6 +53153,231 @@ paths:
type: object
'401':
$ref: '#/components/responses/ai_Unauthorized'
/v1/research/artifacts:
post:
tags:
- Research
summary: Record a diary artifact (idempotent by sha256 content hash)
description: "A raw-artifact-class manifest tied to a run \u2014 a `snapshot` (a PNG of the\ncanonical board) or a `report`\
\ (a generated page). `sha256` is the content-hash\nidentity; a re-POST of the same bytes is a no-op. The bytes live\
\ at `ref`\n(blob store). Private by default.\n"
operationId: research_recordResearchArtifact
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/research_Artifact'
responses:
'200':
description: Recorded
content:
application/json:
schema:
type: object
properties:
sha256:
type: string
created:
type: boolean
rolled_up:
type: boolean
'400':
$ref: '#/components/responses/research_BadRequest'
'403':
$ref: '#/components/responses/research_Forbidden'
'422':
$ref: '#/components/responses/research_Unprocessable'
get:
tags:
- Research
summary: The chronological diary feed (newest-first)
operationId: research_listResearchArtifacts
parameters:
- name: run
in: query
required: false
schema:
type: string
description: Filter to one run/snapshot id.
- name: project
in: query
required: false
schema:
type: string
description: Defaults to the caller's project scope.
- name: since
in: query
required: false
schema:
type: integer
format: int64
description: Unix-seconds lower bound on ts.
responses:
'200':
description: Diary feed
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/research_Artifact'
total:
type: integer
'403':
$ref: '#/components/responses/research_Forbidden'
/v1/research/experiments:
post:
tags:
- Research
summary: Ingest a batch of experiments and attempts (idempotent by content)
description: "Appends versions to the org's durable plane and rolls up to the warehouse\nbest-effort. The org is the\
\ validated principal's; `project` is the validated\n`X-Project-Id` sub-scope, stamped server-side. Re-ingesting identical\
\ content\nis a no-op. A `endpoint` on an experiment is SSRF-gated. Visibility/consent are\nforced private/withheld\
\ \u2014 a grant is separate.\n"
operationId: research_ingestResearch
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/research_IngestRequest'
responses:
'200':
description: Ingest counts
content:
application/json:
schema:
$ref: '#/components/schemas/research_IngestResult'
'400':
$ref: '#/components/responses/research_BadRequest'
'403':
$ref: '#/components/responses/research_Forbidden'
'422':
$ref: '#/components/responses/research_Unprocessable'
get:
tags:
- Research
summary: List canonical experiments (latest answered version per stable id)
operationId: research_listExperiments
parameters:
- name: project
in: query
required: false
schema:
type: string
description: Narrow to one project; omit for the org's whole cross-project view.
- name: kind
in: query
required: false
schema:
type: string
enum:
- benchmark
- kernel-perf
- training
- ablation
- policy-eval
responses:
'200':
description: Experiments
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/research_Experiment'
total:
type: integer
'403':
$ref: '#/components/responses/research_Forbidden'
/v1/research/grants:
post:
tags:
- Research
summary: Set visibility/consent for a run (by id) or an artifact (by sha256)
description: 'The SEPARATE authorization upload never implies. A run grant (`id`) sets
visibility + the training/commons consent flags; an artifact grant (`sha256`)
sets visibility. A nil field is left unchanged.
'
operationId: research_setResearchGrant
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/research_GrantRequest'
responses:
'200':
description: Rows updated
content:
application/json:
schema:
type: object
properties:
updated:
type: integer
'400':
$ref: '#/components/responses/research_BadRequest'
'403':
$ref: '#/components/responses/research_Forbidden'
'404':
description: No records for that target
'422':
$ref: '#/components/responses/research_Unprocessable'
/v1/research/projects:
get:
tags:
- Research
summary: Every project in the org with its real totals (canonical + retained)
operationId: research_listResearchProjects
responses:
'200':
description: Project summaries
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/research_ProjectSummary'
total:
type: integer
'403':
$ref: '#/components/responses/research_Forbidden'
/v1/research/totals:
get:
tags:
- Research
summary: Headline aggregate (canonical + retained) plus per-kind breakdown
operationId: research_getResearchTotals
parameters:
- name: project
in: query
required: false
schema:
type: string
description: Scope the aggregate to one project.
responses:
'200':
description: Totals
content:
application/json:
schema:
$ref: '#/components/schemas/research_Totals'
'403':
$ref: '#/components/responses/research_Forbidden'
/v1/s3:
post:
tags:
@@ -79670,6 +79898,333 @@ components:
type: string
skip_cert_verify:
type: boolean
research_Artifact:
type: object
description: "A research-diary record \u2014 a raw-artifact-class manifest tied to a run."
required:
- sha256
- kind
- ref
properties:
sha256:
type: string
description: content-hash identity; idempotent re-POST
kind:
type: string
enum:
- snapshot
- report
ref:
type: string
description: file/blob ref (the bytes live here)
run_id:
type: string
description: the run/snapshot id it belongs to
project:
type: string
visibility:
type: string
enum:
- private
- org
- public
default: private
description: Read-only on ingest; set via a grant.
retention_class:
type: string
default: raw-artifact
readOnly: true
git_sha:
type: string
git_branch:
type: string
git_dirty:
type: boolean
lib_versions:
type: object
additionalProperties:
type: string
ts:
type: integer
format: int64
research_Attempt:
type: object
description: One versioned measured attempt on one item.
required:
- benchmark
- item
- model
properties:
benchmark:
type: string
item:
type: string
model:
type: string
revision:
type: string
enum:
- original
- corrected
- retracted
default: original
status:
type: string
enum:
- complete
- faulted
- failed
default: complete
description: faulted retains a negative result
gold:
type: string
answer:
type: string
correct:
type: boolean
response:
type: string
description: raw model output (raw-artifact retention class)
source:
type: string
default: hanzo-measured
ts:
type: integer
format: int64
research_Error:
type: object
properties:
error:
type: string
research_Experiment:
type: object
description: One versioned run record. On read, the canonical (latest answered) version.
required:
- id
- kind
- subject
properties:
project:
type: string
description: Server-stamped on ingest; a payload cannot forge it.
id:
type: string
description: "Stable id \u2014 \"<kind>:<subject>:<task>\". Keys supersession."
revision:
type: string
enum:
- original
- corrected
- retracted
default: original
status:
type: string
enum:
- queued
- planning
- running
- scoring
- complete
- partial
- failed
- faulted
- cancelled
default: complete
canonical:
type: boolean
readOnly: true
description: True when this is the current authoritative version.
visibility:
type: string
enum:
- private
- org
- public
default: private
description: Read-only on ingest (forced private); set via a grant.
trainable:
type: boolean
default: false
description: Read-only on ingest; set via a grant.
publishable:
type: boolean
default: false
description: Read-only on ingest; set via a grant.
kind:
type: string
enum:
- benchmark
- kernel-perf
- training
- ablation
- policy-eval
subject:
type: string
description: model / kernel / dataset under test
task:
type: string
description: benchmark id
kernel op: null
training objective: null
metric:
type: string
description: accuracy | tok/s | loss | net-lift
value:
type: number
description: the headline number
n:
type: integer
description: answered sample size / coverage
n_total:
type: integer
description: item target (done vs remaining = n_total - n)
cost_usd:
type: number
meta:
type: object
additionalProperties: true
description: nonpersonal provenance (permanent retention class)
git_sha:
type: string
description: producing repo commit at run time (indexed)
git_branch:
type: string
description: producing repo branch at run time (indexed)
git_dirty:
type: boolean
description: tree was dirty at run time
lib_versions:
type: object
additionalProperties:
type: string
description: '{lib: version} at run time'
ts:
type: integer
format: int64
description: run/observation clock (unix seconds); orders versions
endpoint:
type: string
description: OPTIONAL BYO arm URL; SSRF-gated at ingest (https
no loopback/private/link-local/metadata): null
research_GrantRequest:
type: object
description: A run grant (id) OR an artifact grant (sha256). At least one target required.
properties:
project:
type: string
id:
type: string
description: an experiment (run) stable id
sha256:
type: string
description: OR an artifact content hash
visibility:
type: string
enum:
- private
- org
- public
nullable: true
trainable:
type: boolean
nullable: true
publishable:
type: boolean
nullable: true
research_IngestRequest:
type: object
properties:
experiments:
type: array
items:
$ref: '#/components/schemas/research_Experiment'
attempts:
type: array
items:
$ref: '#/components/schemas/research_Attempt'
research_IngestResult:
type: object
description: "Counts after ingest \u2014 retained is the truth, canonical the deduped answered view."
properties:
project:
type: string
experiments_ingested:
type: integer
description: NEW versions added this batch
attempts_ingested:
type: integer
canonical_experiments:
type: integer
experiments_retained:
type: integer
canonical_attempts:
type: integer
attempts_retained:
type: integer
rolled_up:
type: boolean
description: false when the datastore was absent (SQLite retains the write)
research_KindTotal:
type: object
properties:
kind:
type: string
experiments:
type: integer
cost_usd:
type: number
research_ProjectSummary:
type: object
properties:
project:
type: string
experiments:
type: integer
description: canonical
experiments_retained:
type: integer
attempts:
type: integer
description: canonical
attempts_retained:
type: integer
models:
type: integer
benchmarks:
type: integer
cost_usd:
type: number
kinds:
type: array
items:
type: string
research_Totals:
type: object
properties:
project:
type: string
projects:
type: integer
experiments:
type: integer
description: canonical
experiments_retained:
type: integer
attempts:
type: integer
description: canonical
attempts_retained:
type: integer
models:
type: integer
benchmarks:
type: integer
cost_usd:
type: number
by_kind:
type: array
items:
$ref: '#/components/schemas/research_KindTotal'
s3_Bucket:
type: object
properties:
@@ -82870,6 +83425,24 @@ components:
application/json:
schema:
$ref: '#/components/schemas/registry_Error'
research_BadRequest:
description: Malformed request
content:
application/json:
schema:
$ref: '#/components/schemas/research_Error'
research_Forbidden:
description: Missing validated principal (X-Org-Id required)
content:
application/json:
schema:
$ref: '#/components/schemas/research_Error'
research_Unprocessable:
description: Well-formed but semantically rejected (bad shape, blocked SSRF endpoint, bad enum)
content:
application/json:
schema:
$ref: '#/components/schemas/research_Error'
search_NotFound:
description: Resource not found
content:
+291
View File
@@ -0,0 +1,291 @@
openapi: 3.1.0
info:
title: Hanzo Research API
description: |
The `/v1/research` surface served by the cloud `research` subsystem
(`hanzoai/cloud/clients/research`) — the R&D EVIDENCE plane (HIP-0512
§"Hanzo Research"). Every experiment across every product accrues here as
VERSIONED, append-only evidence under one discriminator,
`kind ∈ benchmark | kernel-perf | training | ablation | policy-eval`.
Two planes (HIP-0512): each org's transactional SQLite (per HIP-0302,
physically file-isolated) is the local source of truth; it rolls up into
`hanzoai/datastore` (the column-oriented OLAP aggregate plane) for the unified
cross-project query surface. Durability today is versioned · append-only; cloud
mirroring is rolling out.
Versioned, not write-once: a correction APPENDS a new version under the same
stable id; the prior is RETAINED (superseded), never mutated. `retained` is the
full history (the truth); `canonical` is the deterministic answered deduped view
over it — so dedup never reads as loss. faulted/failed runs are retained
(negative results are evidence). Ingest is idempotent by content — re-running the
backfill appends nothing.
Provenance (`project`, `git_sha`, `git_branch`, `git_dirty`, `lib_versions`) is
first-class + queryable — the longitudinal "which lib version regressed X" record.
Private by default: an upload records `visibility: private` and grants NO training
or commons-publication rights. Public visibility, training, and commons publication
are each a SEPARATE authorized grant (`POST /v1/research/grants`), never implied by
uploading a run.
version: 1.0.0
contact:
name: Hanzo AI
url: https://hanzo.ai
email: [email protected]
license:
name: Proprietary
servers:
- url: https://api.hanzo.ai
description: Hanzo Gateway
tags:
- name: Research
description: Versioned R&D evidence — experiments, attempts, artifacts
security:
- bearerAuth: []
paths:
/v1/research/experiments:
post:
tags: [Research]
summary: Ingest a batch of experiments and attempts (idempotent by content)
description: |
Appends versions to the org's durable plane and rolls up to the warehouse
best-effort. The org is the validated principal's; `project` is the validated
`X-Project-Id` sub-scope, stamped server-side. Re-ingesting identical content
is a no-op. A `endpoint` on an experiment is SSRF-gated. Visibility/consent are
forced private/withheld — a grant is separate.
operationId: ingestResearch
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/IngestRequest' }
responses:
'200': { description: Ingest counts, content: { application/json: { schema: { $ref: '#/components/schemas/IngestResult' } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'422': { $ref: '#/components/responses/Unprocessable' }
get:
tags: [Research]
summary: List canonical experiments (latest answered version per stable id)
operationId: listExperiments
parameters:
- { name: project, in: query, required: false, schema: { type: string }, description: Narrow to one project; omit for the org's whole cross-project view. }
- { name: kind, in: query, required: false, schema: { type: string, enum: [benchmark, kernel-perf, training, ablation, policy-eval] } }
responses:
'200': { description: Experiments, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Experiment' } }, total: { type: integer } } } } } }
'403': { $ref: '#/components/responses/Forbidden' }
/v1/research/projects:
get:
tags: [Research]
summary: Every project in the org with its real totals (canonical + retained)
operationId: listResearchProjects
responses:
'200': { description: Project summaries, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/ProjectSummary' } }, total: { type: integer } } } } } }
'403': { $ref: '#/components/responses/Forbidden' }
/v1/research/totals:
get:
tags: [Research]
summary: Headline aggregate (canonical + retained) plus per-kind breakdown
operationId: getResearchTotals
parameters:
- { name: project, in: query, required: false, schema: { type: string }, description: Scope the aggregate to one project. }
responses:
'200': { description: Totals, content: { application/json: { schema: { $ref: '#/components/schemas/Totals' } } } }
'403': { $ref: '#/components/responses/Forbidden' }
/v1/research/grants:
post:
tags: [Research]
summary: Set visibility/consent for a run (by id) or an artifact (by sha256)
description: |
The SEPARATE authorization upload never implies. A run grant (`id`) sets
visibility + the training/commons consent flags; an artifact grant (`sha256`)
sets visibility. A nil field is left unchanged.
operationId: setResearchGrant
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/GrantRequest' }
responses:
'200': { description: Rows updated, content: { application/json: { schema: { type: object, properties: { updated: { type: integer } } } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { description: No records for that target }
'422': { $ref: '#/components/responses/Unprocessable' }
/v1/research/artifacts:
post:
tags: [Research]
summary: Record a diary artifact (idempotent by sha256 content hash)
description: |
A raw-artifact-class manifest tied to a run — a `snapshot` (a PNG of the
canonical board) or a `report` (a generated page). `sha256` is the content-hash
identity; a re-POST of the same bytes is a no-op. The bytes live at `ref`
(blob store). Private by default.
operationId: recordResearchArtifact
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/Artifact' }
responses:
'200': { description: Recorded, content: { application/json: { schema: { type: object, properties: { sha256: { type: string }, created: { type: boolean }, rolled_up: { type: boolean } } } } } }
'400': { $ref: '#/components/responses/BadRequest' }
'403': { $ref: '#/components/responses/Forbidden' }
'422': { $ref: '#/components/responses/Unprocessable' }
get:
tags: [Research]
summary: The chronological diary feed (newest-first)
operationId: listResearchArtifacts
parameters:
- { name: run, in: query, required: false, schema: { type: string }, description: Filter to one run/snapshot id. }
- { name: project, in: query, required: false, schema: { type: string }, description: Defaults to the caller's project scope. }
- { name: since, in: query, required: false, schema: { type: integer, format: int64 }, description: Unix-seconds lower bound on ts. }
responses:
'200': { description: Diary feed, content: { application/json: { schema: { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/Artifact' } }, total: { type: integer } } } } } }
'403': { $ref: '#/components/responses/Forbidden' }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: IAM-issued JWT or `hk-` Cloud API key. The org and project sub-scope are the validated principal's, never client fields.
responses:
BadRequest:
description: Malformed request
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
Forbidden:
description: Missing validated principal (X-Org-Id required)
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
Unprocessable:
description: Well-formed but semantically rejected (bad shape, blocked SSRF endpoint, bad enum)
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
schemas:
Error:
type: object
properties:
error: { type: string }
Experiment:
type: object
description: One versioned run record. On read, the canonical (latest answered) version.
required: [id, kind, subject]
properties:
project: { type: string, description: Server-stamped on ingest; a payload cannot forge it. }
id: { type: string, description: Stable id — "<kind>:<subject>:<task>". Keys supersession. }
revision: { type: string, enum: [original, corrected, retracted], default: original }
status: { type: string, enum: [queued, planning, running, scoring, complete, partial, failed, faulted, cancelled], default: complete }
canonical: { type: boolean, readOnly: true, description: True when this is the current authoritative version. }
visibility: { type: string, enum: [private, org, public], default: private, description: Read-only on ingest (forced private); set via a grant. }
trainable: { type: boolean, default: false, description: Read-only on ingest; set via a grant. }
publishable: { type: boolean, default: false, description: Read-only on ingest; set via a grant. }
kind: { type: string, enum: [benchmark, kernel-perf, training, ablation, policy-eval] }
subject: { type: string, description: model / kernel / dataset under test }
task: { type: string, description: benchmark id, kernel op, training objective }
metric: { type: string, description: accuracy | tok/s | loss | net-lift }
value: { type: number, description: the headline number }
n: { type: integer, description: answered sample size / coverage }
n_total: { type: integer, description: item target (done vs remaining = n_total - n) }
cost_usd: { type: number }
meta: { type: object, additionalProperties: true, description: nonpersonal provenance (permanent retention class) }
git_sha: { type: string, description: producing repo commit at run time (indexed) }
git_branch: { type: string, description: producing repo branch at run time (indexed) }
git_dirty: { type: boolean, description: tree was dirty at run time }
lib_versions: { type: object, additionalProperties: { type: string }, description: '{lib: version} at run time' }
ts: { type: integer, format: int64, description: run/observation clock (unix seconds); orders versions }
endpoint: { type: string, description: OPTIONAL BYO arm URL; SSRF-gated at ingest (https, no loopback/private/link-local/metadata) }
Attempt:
type: object
description: One versioned measured attempt on one item.
required: [benchmark, item, model]
properties:
benchmark: { type: string }
item: { type: string }
model: { type: string }
revision: { type: string, enum: [original, corrected, retracted], default: original }
status: { type: string, enum: [complete, faulted, failed], default: complete, description: faulted retains a negative result }
gold: { type: string }
answer: { type: string }
correct: { type: boolean }
response: { type: string, description: raw model output (raw-artifact retention class) }
source: { type: string, default: hanzo-measured }
ts: { type: integer, format: int64 }
IngestRequest:
type: object
properties:
experiments: { type: array, items: { $ref: '#/components/schemas/Experiment' } }
attempts: { type: array, items: { $ref: '#/components/schemas/Attempt' } }
IngestResult:
type: object
description: Counts after ingest — retained is the truth, canonical the deduped answered view.
properties:
project: { type: string }
experiments_ingested: { type: integer, description: NEW versions added this batch }
attempts_ingested: { type: integer }
canonical_experiments: { type: integer }
experiments_retained: { type: integer }
canonical_attempts: { type: integer }
attempts_retained: { type: integer }
rolled_up: { type: boolean, description: false when the datastore was absent (SQLite retains the write) }
ProjectSummary:
type: object
properties:
project: { type: string }
experiments: { type: integer, description: canonical }
experiments_retained: { type: integer }
attempts: { type: integer, description: canonical }
attempts_retained: { type: integer }
models: { type: integer }
benchmarks: { type: integer }
cost_usd: { type: number }
kinds: { type: array, items: { type: string } }
KindTotal:
type: object
properties:
kind: { type: string }
experiments: { type: integer }
cost_usd: { type: number }
Totals:
type: object
properties:
project: { type: string }
projects: { type: integer }
experiments: { type: integer, description: canonical }
experiments_retained: { type: integer }
attempts: { type: integer, description: canonical }
attempts_retained: { type: integer }
models: { type: integer }
benchmarks: { type: integer }
cost_usd: { type: number }
by_kind: { type: array, items: { $ref: '#/components/schemas/KindTotal' } }
GrantRequest:
type: object
description: A run grant (id) OR an artifact grant (sha256). At least one target required.
properties:
project: { type: string }
id: { type: string, description: an experiment (run) stable id }
sha256: { type: string, description: OR an artifact content hash }
visibility: { type: string, enum: [private, org, public], nullable: true }
trainable: { type: boolean, nullable: true }
publishable: { type: boolean, nullable: true }
Artifact:
type: object
description: A research-diary record — a raw-artifact-class manifest tied to a run.
required: [sha256, kind, ref]
properties:
sha256: { type: string, description: content-hash identity; idempotent re-POST }
kind: { type: string, enum: [snapshot, report] }
ref: { type: string, description: file/blob ref (the bytes live here) }
run_id: { type: string, description: the run/snapshot id it belongs to }
project: { type: string }
visibility: { type: string, enum: [private, org, public], default: private, description: Read-only on ingest; set via a grant. }
retention_class: { type: string, default: raw-artifact, readOnly: true }
git_sha: { type: string }
git_branch: { type: string }
git_dirty: { type: boolean }
lib_versions: { type: object, additionalProperties: { type: string } }
ts: { type: integer, format: int64 }