b77f537dc6
- neuron-runtime, neuron-store, neuron-migrate: fix path deps (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/) - neuron-rs workspace: fix axon-events/axon-protocol paths (../../../platform/ → ../../platform/, paths were off by one level) - neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
163 lines
5.4 KiB
EmacsLisp
163 lines
5.4 KiB
EmacsLisp
// artifact.el — Versioned deliverables subsystem.
|
|
//
|
|
// Artifacts are the tangible outputs of Neuron-assisted work: plans,
|
|
// specifications, architecture docs, reports, design documents.
|
|
// Unlike raw memories (ephemeral) or knowledge (reference), artifacts are
|
|
// polished, versioned deliverables that external stakeholders consume.
|
|
//
|
|
// WHY explicit versioning?
|
|
// A plan that gets revised without version tracking looks like a single
|
|
// authoritative document. With version tracking, you can see when a plan
|
|
// changed and compare the reasoning at each revision. This is especially
|
|
// important for architectural decisions that need an audit trail.
|
|
//
|
|
// Status machine:
|
|
// draft → review → approved → archived
|
|
//
|
|
// Artifacts are never deleted — only archived. A deleted plan loses its
|
|
// history. An archived plan retains it.
|
|
|
|
from types import {
|
|
Artifact,
|
|
NeuronError,
|
|
}
|
|
|
|
// ── Public API ────────────────────────────────────────────────────────────────
|
|
|
|
// draft_artifact — create a new artifact in draft status.
|
|
//
|
|
// artifact_types is a list because a document can be multiple things at once:
|
|
// ["plan", "spec"] for a planning spec, ["report", "analysis"] for an
|
|
// analysis report.
|
|
@manager
|
|
fn draft_artifact(
|
|
title: String,
|
|
content: String,
|
|
artifact_types: [String],
|
|
project: String,
|
|
) -> Result<Artifact, NeuronError> {
|
|
let id = native_uuid()
|
|
let now = native_now()
|
|
// Serialize artifact_types to a comma-joined string for the store.
|
|
// The Axon response formatter re-splits on display.
|
|
let artifact_type = artifact_types[0]
|
|
let artifact = Artifact {
|
|
id: id,
|
|
title: title,
|
|
content: content,
|
|
artifact_type: artifact_type,
|
|
status: "draft",
|
|
project: project,
|
|
version: 1,
|
|
created_at: now,
|
|
updated_at: now,
|
|
}
|
|
native_store_artifact(artifact)?
|
|
native_emit("artifact.drafted", {"id": id, "project": project, "type": artifact_type})
|
|
Ok(artifact)
|
|
}
|
|
|
|
// find_artifacts — list or search artifacts for a project.
|
|
//
|
|
// When query is provided, uses semantic search (activate) to find artifacts
|
|
// by content similarity. When query is empty, returns all project artifacts.
|
|
@accessor
|
|
fn find_artifacts(project: String, query: String) -> Result<[Artifact], NeuronError> {
|
|
if query == "" {
|
|
let artifacts = native_list_artifacts(project)?
|
|
return Ok(artifacts)
|
|
}
|
|
let results = activate Artifact where "{query} project:{project}"
|
|
Ok(results)
|
|
}
|
|
|
|
// retrieve_artifact — fetch a single artifact by ID.
|
|
@accessor
|
|
fn retrieve_artifact(id: String) -> Result<Artifact, NeuronError> {
|
|
let artifact = native_get_artifact(id)?
|
|
Ok(artifact)
|
|
}
|
|
|
|
// revise_artifact — update an artifact's content and increment its version.
|
|
//
|
|
// Every revision is a distinct event in the graph. The version number is
|
|
// monotonically increasing — no rollbacks, no overwriting. If you need to
|
|
// revert, draft a new artifact that references the old version.
|
|
@manager
|
|
fn revise_artifact(
|
|
id: String,
|
|
content: String,
|
|
change_summary: String,
|
|
) -> Result<Artifact, NeuronError> {
|
|
let artifact = native_get_artifact(id)?
|
|
let now = native_now()
|
|
// Increment version — this is the only place version numbers are assigned.
|
|
let new_version = artifact.version + 1
|
|
let revised = Artifact {
|
|
id: artifact.id,
|
|
title: artifact.title,
|
|
content: content,
|
|
artifact_type: artifact.artifact_type,
|
|
status: artifact.status,
|
|
project: artifact.project,
|
|
version: new_version,
|
|
created_at: artifact.created_at,
|
|
updated_at: now,
|
|
}
|
|
native_update_artifact(revised)?
|
|
native_emit("artifact.revised", {
|
|
"id": id,
|
|
"version": "new_version",
|
|
"summary": change_summary,
|
|
})
|
|
Ok(revised)
|
|
}
|
|
|
|
// manage_artifact — transition an artifact's status.
|
|
//
|
|
// action: "review" | "approve" | "archive"
|
|
// Valid transitions:
|
|
// review: draft → review
|
|
// approve: review → approved
|
|
// archive: * → archived
|
|
@manager
|
|
fn manage_artifact(id: String, action: String) -> Result<Artifact, NeuronError> {
|
|
let artifact = native_get_artifact(id)?
|
|
let new_status = if action == "review" {
|
|
if artifact.status == "draft" {
|
|
"review"
|
|
} else {
|
|
return Err(NeuronError::InvalidInput("can only send a draft artifact for review"))
|
|
}
|
|
} else {
|
|
if action == "approve" {
|
|
if artifact.status == "review" {
|
|
"approved"
|
|
} else {
|
|
return Err(NeuronError::InvalidInput("can only approve an artifact under review"))
|
|
}
|
|
} else {
|
|
if action == "archive" {
|
|
"archived"
|
|
} else {
|
|
return Err(NeuronError::InvalidInput("unknown artifact action"))
|
|
}
|
|
}
|
|
}
|
|
let now = native_now()
|
|
let updated = Artifact {
|
|
id: artifact.id,
|
|
title: artifact.title,
|
|
content: artifact.content,
|
|
artifact_type: artifact.artifact_type,
|
|
status: new_status,
|
|
project: artifact.project,
|
|
version: artifact.version,
|
|
created_at: artifact.created_at,
|
|
updated_at: now,
|
|
}
|
|
native_update_artifact(updated)?
|
|
native_emit("artifact.status_changed", {"id": id, "status": new_status, "action": action})
|
|
Ok(updated)
|
|
}
|