2026-08-24 00:20:06 -03:00
""" HUX-04 artifact workspace: typed, versioned, diffable outputs.
Every artifact is one ` ` hux . artifact . v1 ` ` document under the caller ' s tenant
subtree . Content lives in the content - addressed blob store ; the document only
carries ` ` content_ref ` ` hashes , so a version can never be rewritten once it
has been appended ( SO - 30 ) . Blobs are served inside a JSON envelope with
` ` nosniff ` ` and never as an executable response type ( SO - 32 ) . Every
referenced id is resolved under the caller ' s own subtree (SO-33); a foreign
or unknown id is ` ` 404 ` ` so ids cannot be probed . Sharing is out of scope for
this increment : ` ` access . mode ` ` is always ` ` owner ` ` ( SO - 34 ) .
"""
from __future__ import annotations
import base64
import hashlib
from typing import Any
from hux import diffs
from hux . contracts import load_all , validate_record
from hux . errors import Conflict , Invalid , NotFound , TooLarge
from hux . http import Request , Response , Router , page
from hux . identity import Identity
from hux . store import TenantStore , check_id , new_id , now_iso
2026-08-24 00:27:38 -03:00
MAX_UPLOAD_BODY = 34 * 1024 * 1024 # 25 MiB of content plus base64 overhead (SO-31, SO-54)
def _project_exists ( store , project_id : str ) - > bool :
""" Ask the organization family when it is present; otherwise only the id shape is known. """
try :
from hux . organization import project_exists
except ModuleNotFoundError : # pragma: no cover - organization is always shipped with artifacts
return True
return project_exists ( store , project_id )
2026-08-24 00:20:06 -03:00
FAMILY = " artifacts "
MAX_VERSION_BYTES = 25 * 1024 * 1024
MAX_VERSIONS = 200
MAX_ARTIFACTS = 2000
PAGE_SIZE = 50
ARTIFACT_TYPES = ( " markdown " , " code " , " html " , " svg " , " image " , " json " , " csv " , " document " , " audio " )
SENSITIVITIES = ( " public " , " personal " , " sensitive " , " restricted " )
_SCHEMAS : dict [ str , dict [ str , Any ] ] = { }
def _schemas ( ) - > dict [ str , dict [ str , Any ] ] :
if not _SCHEMAS :
_SCHEMAS . update ( load_all ( ) )
return _SCHEMAS
# -- helpers shared with other lanes -------------------------------------------
def artifact_exists ( store : TenantStore , artifact_id : str ) - > bool :
""" True when ``artifact_id`` is a well-formed id owned by the store ' s tenant. """
try :
return store . exists ( FAMILY , artifact_id )
except Invalid :
return False
def artifact_titles ( store : TenantStore , ids : list [ str ] ) - > dict [ str , str ] :
""" Map each resolvable artifact id to its title; unknown ids are omitted. """
titles : dict [ str , str ] = { }
for artifact_id in ids :
if artifact_exists ( store , artifact_id ) :
titles [ artifact_id ] = store . get ( FAMILY , artifact_id ) [ " title " ]
return titles
def emit_event ( store : TenantStore , identity : Identity , artifact : dict [ str , Any ] , kind : str , summary : str , detail : dict [ str , Any ] ) - > None :
""" Record an activity event through the events lane when it is present.
Imported lazily so module import order never matters ; a missing events
module ( earlier build ) is not an error because the artifact write itself
is the source of truth .
"""
conversation_id = artifact . get ( " conversation_id " )
if conversation_id is None :
return
try :
from hux . events import emit
except ModuleNotFoundError :
return
version = artifact [ " current_version " ]
evidence = [ { " kind " : " artifact_version " , " id " : f " { artifact [ ' id ' ] } @ { version } " , " hash " : artifact [ " versions " ] [ - 1 ] [ " content_ref " ] [ " hash " ] } ]
emit ( store , identity , conversation_id , kind , summary , detail = detail , evidence = evidence , sensitivity = artifact [ " sensitivity " ] )
def replay ( store : TenantStore , family : str , key : str ) - > str | None :
""" Record id previously created under an Idempotency-Key, if any. """
if not key :
return None
for row in store . read ( family , " idempotency " ) :
if row . get ( " key " ) == key :
return row [ " id " ]
return None
def remember ( store : TenantStore , family : str , key : str , record_id : str ) - > None :
""" Persist an Idempotency-Key to record id mapping. """
if key :
store . append ( family , " idempotency " , { " key " : key , " id " : record_id , " at " : now_iso ( ) } )
# -- record shaping ------------------------------------------------------------
def _actor ( identity : Identity ) - > dict [ str , str ] :
if identity . trust == " worker " :
return { " type " : " system " , " id " : " worker " }
return { " type " : " user " , " id " : identity . subject }
def _body ( request : Request ) - > dict [ str , Any ] :
if not isinstance ( request . body , dict ) :
raise Invalid ( " body must be a JSON object " )
return request . body
def _string ( body : dict [ str , Any ] , key : str , limit : int , required : bool = True ) - > str | None :
value = body . get ( key )
if value is None :
if required :
raise Invalid ( f " { key } is required " )
return None
if not isinstance ( value , str ) or not value . strip ( ) or len ( value ) > limit :
raise Invalid ( f " { key } must be a non-empty string of at most { limit } characters " )
return value
2026-08-24 00:48:19 -03:00
def secret_hits ( data : bytes ) - > list [ str ] :
""" Secret-pattern classes found in text content; binary content is not scanned (SO-12). """
from hux . redaction import scrub_text
try :
text = data . decode ( " utf-8 " )
except UnicodeDecodeError :
return [ ]
return scrub_text ( text ) [ 1 ]
2026-08-24 00:20:06 -03:00
def _content ( body : dict [ str , Any ] ) - > tuple [ bytes , str ] :
""" Decode the submitted content and return ``(bytes, mime)``. """
text , encoded = body . get ( " content " ) , body . get ( " content_base64 " )
if isinstance ( text , str ) and encoded is None :
data = text . encode ( " utf-8 " )
elif isinstance ( encoded , str ) and text is None :
try :
data = base64 . b64decode ( encoded , validate = True )
except ( ValueError , TypeError ) as error :
raise Invalid ( " content_base64 is not valid base64 " ) from error
else :
raise Invalid ( " exactly one of content (utf-8 text) or content_base64 is required " )
if len ( data ) > MAX_VERSION_BYTES :
raise TooLarge ( " version exceeds 25 MiB " )
mime = _string ( body , " mime " , 120 , required = False ) or " application/octet-stream "
return data , mime
def _store_content ( store : TenantStore , body : dict [ str , Any ] ) - > dict [ str , Any ] :
""" Hash, verify against any client-supplied hash, store the blob, return ``content_ref``. """
data , mime = _content ( body )
digest = hashlib . sha256 ( data ) . hexdigest ( )
claimed = body . get ( " hash " )
if claimed is not None and claimed != f " sha256: { digest } " :
raise Invalid ( " hash does not match the submitted content " )
store . put_blob ( digest , data )
return { " hash " : f " sha256: { digest } " , " bytes " : len ( data ) , " mime " : mime }
def _optional_id ( body : dict [ str , Any ] , key : str ) - > str | None :
value = body . get ( key )
return None if value is None else check_id ( value )
def _load_owned ( request : Request ) - > dict [ str , Any ] :
""" The artifact named in the path; unknown, malformed or foreign is 404. """
try :
artifact = request . store . get ( FAMILY , request . params [ " id " ] )
except Invalid as error :
raise NotFound ( " artifact not found " ) from error
if artifact . get ( " owner " ) != request . identity . subject :
raise NotFound ( " artifact not found " )
return artifact
def _version ( artifact : dict [ str , Any ] , number : Any ) - > dict [ str , Any ] :
if not isinstance ( number , int ) or isinstance ( number , bool ) :
raise Invalid ( " version must be an integer " )
for entry in artifact [ " versions " ] :
if entry [ " version " ] == number :
return entry
raise NotFound ( f " version { number } not found " )
def _lineage ( request : Request , body : dict [ str , Any ] ) - > dict [ str , Any ] | None :
""" Resolve ``lineage`` under the caller ' s subtree; anything else is 404 (SO-33). """
lineage = body . get ( " lineage " )
if lineage is None :
return None
if not isinstance ( lineage , dict ) :
raise Invalid ( " lineage must be an object " )
try :
parent = request . store . get ( FAMILY , check_id ( lineage . get ( " artifact_id " ) ) )
except ( Invalid , NotFound ) as error :
raise NotFound ( " lineage artifact not found " ) from error
if parent . get ( " owner " ) != request . identity . subject :
raise NotFound ( " lineage artifact not found " )
version = _version ( parent , lineage . get ( " version " ) )
return { " artifact_id " : parent [ " id " ] , " version " : version [ " version " ] }
def append_version ( store : TenantStore , artifact : dict [ str , Any ] , entry : dict [ str , Any ] , expected_revision : int | None ) - > dict [ str , Any ] :
""" Append an immutable version entry and persist the document.
The version number must be exactly ` ` current_version + 1 ` ` ; any attempt to
write an existing number is a Conflict so history is never rewritten .
"""
if any ( v [ " version " ] == entry [ " version " ] for v in artifact [ " versions " ] ) :
raise Conflict ( f " version { entry [ ' version ' ] } already exists " )
if entry [ " version " ] != artifact [ " current_version " ] + 1 :
raise Conflict ( " versions are appended in order " )
if len ( artifact [ " versions " ] ) > = MAX_VERSIONS :
raise TooLarge ( " artifact has reached 200 versions " )
updated = { * * artifact , " versions " : [ * artifact [ " versions " ] , entry ] , " current_version " : entry [ " version " ] , " updated_at " : now_iso ( ) }
_check ( updated )
return store . put ( FAMILY , updated , expected_revision = expected_revision )
def _check ( artifact : dict [ str , Any ] ) - > None :
problems = validate_record ( artifact , _schemas ( ) )
if problems :
raise Invalid ( " artifact does not satisfy hux.artifact.v1 " , problems )
# -- handlers ------------------------------------------------------------------
def create ( request : Request ) - > Response :
""" ``POST /hux/v1/artifacts``: create an artifact with version 1. """
body = _body ( request )
key = request . idempotency_key ( )
with request . store . lock ( FAMILY ) :
existing = replay ( request . store , FAMILY , key )
if existing is not None :
request . audit ( " artifacts.create " , existing , reason = " idempotent_replay " )
return Response ( 200 , request . store . get ( FAMILY , existing ) , { " HUX-Replayed " : " true " } )
if request . store . count ( FAMILY ) > = MAX_ARTIFACTS :
raise TooLarge ( " tenant has reached 2000 artifacts " )
artifact_type = _string ( body , " type " , 40 )
if artifact_type not in ARTIFACT_TYPES :
raise Invalid ( " unknown artifact type " )
sensitivity = body . get ( " sensitivity " , " personal " )
if sensitivity not in SENSITIVITIES :
raise Invalid ( " unknown sensitivity " )
2026-08-24 00:48:19 -03:00
hits = secret_hits ( _content ( body ) [ 0 ] )
if hits :
sensitivity = " restricted "
2026-08-24 00:20:06 -03:00
stamp = now_iso ( )
version : dict [ str , Any ] = { " version " : 1 , " created_at " : stamp , " created_by " : _actor ( request . identity ) , " content_ref " : _store_content ( request . store , body ) }
for field , limit in ( ( " message_id " , 120 ) , ( " note " , 200 ) ) :
if _string ( body , field , limit , required = False ) is not None :
version [ field ] = body [ field ]
lineage = _lineage ( request , body )
if lineage is not None :
version [ " lineage " ] = lineage
artifact : dict [ str , Any ] = {
" schema " : " hux.artifact.v1 " ,
" id " : new_id ( " art " ) ,
" owner " : request . identity . subject ,
" type " : artifact_type ,
" title " : _string ( body , " title " , 200 ) ,
" current_version " : 1 ,
" versions " : [ version ] ,
" sensitivity " : sensitivity ,
" created_at " : stamp ,
" updated_at " : stamp ,
" revision " : 1 ,
" access " : { " mode " : " owner " } ,
}
for field in ( " conversation_id " , " project_id " ) :
if _optional_id ( body , field ) is not None :
artifact [ field ] = body [ field ]
if _string ( body , " language " , 40 , required = False ) is not None :
artifact [ " language " ] = body [ " language " ]
_check ( artifact )
stored = request . store . put ( FAMILY , artifact )
remember ( request . store , FAMILY , key , stored [ " id " ] )
2026-08-24 00:48:19 -03:00
request . audit ( " artifacts.create " , stored [ " id " ] , reason = " secret_pattern_restricted " if hits else " " )
2026-08-24 00:20:06 -03:00
emit_event ( request . store , request . identity , stored , " artifact.created " , f " Created { stored [ ' type ' ] } artifact " , { " artifact_id " : stored [ " id " ] , " version " : 1 } )
return Response ( 201 , stored , { " ETag " : str ( stored [ " revision " ] ) } )
def list_artifacts ( request : Request ) - > Response :
""" ``GET /hux/v1/artifacts?conversation_id=&project_id=&cursor=``: owned artifacts, oldest first. """
filters = { k : request . query [ k ] for k in ( " conversation_id " , " project_id " ) if request . query . get ( k ) }
cursor = request . query . get ( " cursor " , " 0 " )
if not cursor . isdigit ( ) :
raise Invalid ( " cursor must be a non-negative integer " )
items = [
artifact
for artifact in request . store . scan ( FAMILY )
if artifact . get ( " owner " ) == request . identity . subject and all ( artifact . get ( k ) == v for k , v in filters . items ( ) )
]
start = int ( cursor )
window = items [ start : start + PAGE_SIZE ]
request . audit ( " artifacts.list " , " artifacts " )
return page ( window , str ( start + PAGE_SIZE ) if len ( items ) > start + PAGE_SIZE else None )
def get ( request : Request ) - > Response :
""" ``GET /hux/v1/artifacts/ {id} ``: one artifact document. """
artifact = _load_owned ( request )
request . audit ( " artifacts.get " , artifact [ " id " ] )
return Response ( 200 , artifact , { " ETag " : str ( artifact [ " revision " ] ) } )
def add_version ( request : Request ) - > Response :
""" ``POST /hux/v1/artifacts/ {id} /versions``: append a new immutable version (If-Match required). """
body = _body ( request )
expected = request . if_match ( )
if expected is None :
raise Invalid ( " If-Match is required to append a version " )
key = request . idempotency_key ( )
with request . store . lock ( FAMILY ) :
artifact = _load_owned ( request )
existing = replay ( request . store , FAMILY , key )
if existing is not None and existing . split ( " @ " ) [ 0 ] == artifact [ " id " ] :
request . audit ( " artifacts.version " , existing , reason = " idempotent_replay " )
return Response ( 200 , artifact , { " HUX-Replayed " : " true " , " ETag " : str ( artifact [ " revision " ] ) } )
if expected != artifact [ " revision " ] :
raise Conflict ( f " revision { expected } does not match current revision { artifact [ ' revision ' ] } " , [ str ( artifact [ " revision " ] ) ] )
number = artifact [ " current_version " ] + 1
2026-08-24 00:48:19 -03:00
hits = secret_hits ( _content ( body ) [ 0 ] )
if hits :
artifact = { * * artifact , " sensitivity " : " restricted " }
2026-08-24 00:20:06 -03:00
entry : dict [ str , Any ] = { " version " : number , " created_at " : now_iso ( ) , " created_by " : _actor ( request . identity ) , " content_ref " : _store_content ( request . store , body ) , " diff_from " : artifact [ " current_version " ] }
if body . get ( " diff_from " ) is not None :
entry [ " diff_from " ] = _version ( artifact , body [ " diff_from " ] ) [ " version " ]
for field , limit in ( ( " message_id " , 120 ) , ( " note " , 200 ) ) :
if _string ( body , field , limit , required = False ) is not None :
entry [ field ] = body [ field ]
lineage = _lineage ( request , body )
if lineage is not None :
entry [ " lineage " ] = lineage
stored = append_version ( request . store , artifact , entry , expected )
remember ( request . store , FAMILY , key , f " { stored [ ' id ' ] } @ { number } " )
2026-08-24 00:48:19 -03:00
request . audit ( " artifacts.version " , f " { stored [ ' id ' ] } @ { number } " , reason = " secret_pattern_restricted " if hits else " " )
2026-08-24 00:20:06 -03:00
emit_event ( request . store , request . identity , stored , " artifact.version " , f " New version { number } " , { " artifact_id " : stored [ " id " ] , " version " : number } )
return Response ( 201 , stored , { " ETag " : str ( stored [ " revision " ] ) } )
def _version_number ( request : Request ) - > int :
raw = request . params [ " n " ]
if not raw . isdigit ( ) or int ( raw ) < 1 :
raise Invalid ( " version must be a positive integer " )
return int ( raw )
def get_version ( request : Request ) - > Response :
""" ``GET /hux/v1/artifacts/ {id} /versions/ {n} ``: version metadata plus content. """
artifact = _load_owned ( request )
entry = _version ( artifact , _version_number ( request ) )
data = request . store . get_blob ( entry [ " content_ref " ] [ " hash " ] . split ( " : " , 1 ) [ 1 ] )
text = diffs . decode_text ( data ) if diffs . is_text_type ( artifact [ " type " ] ) else None
body : dict [ str , Any ] = { " artifact_id " : artifact [ " id " ] , " type " : artifact [ " type " ] , " version " : entry }
if text is None :
body [ " content_base64 " ] = base64 . b64encode ( data ) . decode ( " ascii " )
else :
body [ " content " ] = text
request . audit ( " artifacts.get_version " , f " { artifact [ ' id ' ] } @ { entry [ ' version ' ] } " )
headers = { " X-Content-Type-Options " : " nosniff " , " Content-Disposition " : " attachment " , " ETag " : str ( artifact [ " revision " ] ) }
return Response ( 200 , body , headers )
def diff ( request : Request ) - > Response :
""" ``GET /hux/v1/artifacts/ {id} /versions/ {n} /diff?from=``: unified diff for text, sizes and hashes otherwise. """
artifact = _load_owned ( request )
to_entry = _version ( artifact , _version_number ( request ) )
raw_from = request . query . get ( " from " , " " )
if raw_from and not raw_from . isdigit ( ) :
raise Invalid ( " from must be a version number " )
from_number = int ( raw_from ) if raw_from else to_entry . get ( " diff_from " , to_entry [ " version " ] )
from_entry = _version ( artifact , from_number )
older = request . store . get_blob ( from_entry [ " content_ref " ] [ " hash " ] . split ( " : " , 1 ) [ 1 ] )
newer = request . store . get_blob ( to_entry [ " content_ref " ] [ " hash " ] . split ( " : " , 1 ) [ 1 ] )
request . audit ( " artifacts.diff " , f " { artifact [ ' id ' ] } @ { from_number } .. { to_entry [ ' version ' ] } " )
return Response ( 200 , diffs . unified ( artifact [ " type " ] , older , newer , from_number , to_entry [ " version " ] ) , { " X-Content-Type-Options " : " nosniff " } )
def promote ( request : Request ) - > Response :
""" ``POST /hux/v1/artifacts/ {id} /promote``: mark the current version as the project ' s copy. """
body = _body ( request )
project_id = check_id ( body . get ( " project_id " ) )
2026-08-24 00:27:38 -03:00
if not _project_exists ( request . store , project_id ) :
raise NotFound ( " project not found " )
2026-08-24 00:20:06 -03:00
expected = request . if_match ( )
with request . store . lock ( FAMILY ) :
artifact = _load_owned ( request )
if expected is not None and expected != artifact [ " revision " ] :
raise Conflict ( f " revision { expected } does not match current revision { artifact [ ' revision ' ] } " , [ str ( artifact [ " revision " ] ) ] )
version = artifact [ " current_version " ]
if body . get ( " version " ) is not None :
version = _version ( artifact , body [ " version " ] ) [ " version " ]
stamp = now_iso ( )
updated = { * * artifact , " project_id " : project_id , " promotion " : { " project_id " : project_id , " version " : version , " at " : stamp } , " updated_at " : stamp }
_check ( updated )
stored = request . store . put ( FAMILY , updated , expected_revision = artifact [ " revision " ] )
request . audit ( " artifacts.promote " , f " { stored [ ' id ' ] } @ { version } " , reason = " " if expected is not None else " unconditional_write " )
emit_event ( request . store , request . identity , stored , " artifact.promoted " , f " Promoted version { version } to project " , { " artifact_id " : stored [ " id " ] , " version " : version , " project_id " : project_id } )
return Response ( 200 , stored , { " ETag " : str ( stored [ " revision " ] ) } )
def register ( router : Router ) - > None :
""" Attach HUX-04 routes. """
card = " HUX-04 "
2026-08-24 00:27:38 -03:00
router . add ( " POST " , " /hux/v1/artifacts " , card , " artifacts.create " , create , max_body = MAX_UPLOAD_BODY )
2026-08-24 00:20:06 -03:00
router . add ( " GET " , " /hux/v1/artifacts " , card , " artifacts.list " , list_artifacts )
router . add ( " GET " , " /hux/v1/artifacts/ {id} " , card , " artifacts.get " , get )
2026-08-24 00:27:38 -03:00
router . add ( " POST " , " /hux/v1/artifacts/ {id} /versions " , card , " artifacts.version " , add_version , max_body = MAX_UPLOAD_BODY )
2026-08-24 00:20:06 -03:00
router . add ( " GET " , " /hux/v1/artifacts/ {id} /versions/ {n} " , card , " artifacts.get_version " , get_version )
router . add ( " GET " , " /hux/v1/artifacts/ {id} /versions/ {n} /diff " , card , " artifacts.diff " , diff )
router . add ( " POST " , " /hux/v1/artifacts/ {id} /promote " , card , " artifacts.promote " , promote )