Skip to main content

API Flow - Code

Last updated: August 18, 2026

ASCII Flowchart

#L0 Stage^api
+-- #L1 BuildAttrsRequired^sapiBld
+-- #L1 BuildAttrsCache^sapiBld
+-- #L1 ResolveRecAttrs^apiRslv
|   +-- #L2 ResolveAttr^apiRslv
+-- #L1 StageRecord^apiWFL
|   +-- #L2 ResolveTargetEntity^apiRslv
|   |   +-- #L3 ResolveByEID^apiRslv
|   |   |   +-- #L4 GetEID^apiGet
|   |   |       +-- #L5 IsEID^utils
|   |   |       +-- #L5 IsEntityRegistered^apiVld
|   |   |       +-- #L5 ResolveLiveEID^apiRslv
|   |   +-- #L3 ResolveByNaturalKey^apiRslv
|   |   |   +-- #L4 GetEID^apiGet
|   |   |       +-- #L5 IsEID^utils
|   |   |       +-- #L5 IsEntityRegistered^apiVld
|   |   |       +-- #L5 ResolveLiveEID^apiRslv
|   |   |   +-- #L4 GENID^utils
|   |   |       +-- #L5 EPOCH_RAND^utils
|   |   +-- #L3 ResolveByUnique^apiRslv
|   |       +-- #L4 CountUniqueAttributes^apiRslv
|   |       |   +-- #L5 GetUniqueMode^sapiGet
|   |       +-- #L4 GENID^utils
|   |       |   +-- #L5 EPOCH_RAND^utils
|   |       +-- #L4 ResolveOwnership^apiRslv
|   |       |   +-- #L5 GetUniqueMode^sapiGet
|   |       |   +-- #L5 IsRangeValue^sapiVld
|   |       |   +-- #L5 ResolveLiveEID^apiRslv
|   |       +-- #L4 ResolveDesignatedUnique^apiRslv
|   |           +-- #L5 GetEID^sapiGet
|   |           +-- #L5 GetUniqueMode^sapiGet
|   |           +-- #L5 ResolveOwnership^apiRslv
|   |               +-- #L6 GetUniqueMode^sapiGet
|   |               +-- #L6 IsRangeValue^sapiVld
|   |               +-- #L6 ResolveLiveEID^apiRslv
|   |               +-- #L6 GENID^utils
|   |                   +-- #L7 - EPOCH_RAND^utils
|   +-- #L2 IsEntityRegistered^apiVld
|   +-- #L2 ValidateRecord^apiVld
|   |   +-- #L3 ValidateAttrPredicates^apiVld
|   |       +-- #L4 GetAttrValidFn^sapiGet
|   |       |   +-- #L5 GetDictValue^sapiGet
|   |       +-- #L4 ValidateAttrPredVal^apiVld
|   |           +-- #L5 EscapeQuotes^apiVld
|   +-- #L2 DatomAssert^apiWFL
|   |   +-- #L3 IsMultiValue^sapiVld
|   |   +-- #L3 DatomAssertMulti^apiWFL
|   |   |   +-- #L4 DatomAssertMulti^apiWFL
|   |   |   |   [RECURSIVE]
|   |   |   +-- #L4 DatomStageMulti^apiWFL
|   |   |       +-- #L5 IsRefValType^sapiVld
|   |   |       +-- #L5 ResolveAttrRef^sapiRslv
|   |   |       |   +-- #L6 ResolveIsaRef^sapiRslv
|   |   |       |   +-- #L6 ResolveKeyLabel^sapiRslv
|   |   |       |   |   +-- #L7 - GetEID^apiGet
|   |   |       |   |       +-- #L8 - IsEID^utils
|   |   |       |   |       +-- #L8 - IsEntityRegistered^apiVld
|   |   |       |   |       +-- #L8 - ResolveLiveEID^apiRslv
|   |   |       |   +-- #L6 ResolveRawEid^sapiRslv
|   |   |       |       +-- #L7 - GetEID^apiGet
|   |   |       |           +-- #L8 - IsEID^utils
|   |   |       |           +-- #L8 - IsEntityRegistered^apiVld
|   |   |       |           +-- #L8 - ResolveLiveEID^apiRslv
|   |   |       +-- #L5 RegisterAttrVal^sapiRslv
|   |   |           +-- #L6 IsNewRangeValue^sapiVld
|   |   |           +-- #L6 CreateNewValKey^sapiRslv
|   |   |           +-- #L6 IsRangeValue^sapiVld
|   |   |           +-- #L6 GetValKey^sapiGet
|   |   +-- #L3 DatomAssertSingle^apiWFL
|   +-- #L2 DatomRetract^apiWFL
|       +-- #L3 IsMultiValue^sapiVld
|       +-- #L3 DatomRetractMulti^apiWFL
|       |   +-- #L4 DatomRetractMulti^apiWFL
|       |   |   [RECURSIVE]
|       |   +-- #L4 DatomStageMulti^apiWFL
|       |       +-- #L5 IsRefValType^sapiVld
|       |       +-- #L5 ResolveAttrRef^sapiRslv
|       |       |   +-- #L6 ResolveIsaRef^sapiRslv
|       |       |   +-- #L6 ResolveKeyLabel^sapiRslv
|       |       |   |   +-- #L7 - GetEID^apiGet
|       |       |   |       +-- #L8 - IsEID^utils
|       |       |   |       +-- #L8 - IsEntityRegistered^apiVld
|       |       |   |       +-- #L8 - ResolveLiveEID^apiRslv
|       |       |   +-- #L6 ResolveRawEid^sapiRslv
|       |       |       +-- #L7 - GetEID^apiGet
|       |       |           +-- #L8 - IsEID^utils
|       |       |           +-- #L8 - IsEntityRegistered^apiVld
|       |       |           +-- #L8 - ResolveLiveEID^apiRslv
|       |       +-- #L5 RegisterAttrVal^sapiRslv
|       |           +-- #L6 IsNewRangeValue^sapiVld
|       |           +-- #L6 CreateNewValKey^sapiRslv
|       |           +-- #L6 IsRangeValue^sapiVld
|       |           +-- #L6 GetValKey^sapiGet
|   +-- #L2 AddKeyword^apiWFL
|   +-- #L2 DatomAssertSingle^apiWFL
+-- #L1 DatomRetract^apiWFL
    +-- #L2 IsMultiValue^sapiVld
    +-- #L2 DatomRetractMulti^apiWFL
        +-- #L3 DatomRetractMulti^apiWFL
        |   [RECURSIVE]
        +-- #L3 DatomStageMulti^apiWFL
            +-- #L4 IsRefValType^sapiVld
            +-- #L4 ResolveAttrRef^sapiRslv
            |   +-- #L5 ResolveIsaRef^sapiRslv
            |   +-- #L5 ResolveKeyLabel^sapiRslv
            |   |   +-- #L6 GetEID^apiGet
            |   |       +-- #L7 - IsEID^utils
            |   |       +-- #L7 - IsEntityRegistered^apiVld
            |   |       +-- #L7 - ResolveLiveEID^apiRslv
            |   +-- #L5 ResolveRawEid^sapiRslv
            |       +-- #L6 GetEID^apiGet
            |           +-- #L7 - IsEID^utils
            |           +-- #L7 - IsEntityRegistered^apiVld
            |           +-- #L7 - ResolveLiveEID^apiRslv
            +-- #L4 RegisterAttrVal^sapiRslv
                +-- #L5 IsNewRangeValue^sapiVld
                +-- #L5 CreateNewValKey^sapiRslv
                +-- #L5 IsRangeValue^sapiVld
                +-- #L5 GetValKey^sapiGet


#L0 Transact^api
+-- #L1 TxAdd^sapiWFL
|   +-- #L2 EPOCH^utils
+-- #L1 IsEntityRegistered^apiVld
+-- #L1 IsRefValType^sapiVld


#L0 PrintEntity^api
+-- #L1 GetEID^sapiGet
+-- #L1 BuildKeysCatalog^sapiBld
+-- #L1 GetEID^apiGet
|   +-- #L2 IsEID^utils
|   +-- #L2 IsEntityRegistered^apiVld
|   +-- #L2 ResolveLiveEID^apiRslv
+-- #L1 IsMultiValue^sapiVld
+-- #L1 IsRefValType^sapiVld
+-- #L1 GetKeyForRefEID^apiGet
+-- #L1 GetDictValue^sapiGet


#L0 PrintHistory^api
+-- #L1 GetEID^sapiGet
+-- #L1 GetEID^apiGet
|   +-- #L2 IsEID^utils
|   +-- #L2 IsEntityRegistered^apiVld
|   +-- #L2 ResolveLiveEID^apiRslv

L0 INIT^api

INIT	
;;------------------------------------------------------------------
;; Procedure : INIT  —  Module entry point
;;
;; Call      : DO INIT^api
;;
;; Purpose   : Must be called once per user session before any other entry point in this module.
;;
;; Parameters: None
;;
;; Returns   : Nothing (procedure)
;;
;; Sets      : %ABR = 0  (process-level staging buffer)
;;
;; Notice    : %ABR is initialised to 0 by INIT^api on session
;;             startup and reset to 0 by TransactCLEAR after every
;;             Transact call — success or failure.
;;------------------------------------------------------------------ 
    KILL %ABR
    SET %ABR=0    
        
    QUIT

L0 PE^api

PE(val,attr)
;;------------------------------------------------------------------
;; Routine  : PE — Procedure
;; Call     : DO PE^api(val,attr)
;;
;; Purpose  : Short-form alias for PrintEntity.
;;            Defaults val and attr at the call boundary so that
;;            undefined actuals do not shadow PrintEntity's own
;;            $GET handling with empty strings.
;;
;; Parameters
;;   val     : (IN, optional) Entity reference — eid, sys.attr.key,
;;                            or unique attribute value
;;   attr    : (IN, optional) Attribute key or aid, used to scope
;;                            unique-attribute resolution
;;------------------------------------------------------------------
	SET val=$GET(val,"")
	SET attr=$GET(attr,"")
    D PrintEntity(val,attr)
    QUIT

L0 PrintEntity^api

PrintEntity(val,attr)
;;------------------------------------------------------------------
;; Routine  : PrintEntity — Procedure
;;
;; Purpose  : Resolves an entity from any supported reference and
;;            prints all currently active attribute values in tabular
;;            format (eid | attribute | value).
;;
;;            Only live datoms (latest tx with OP=1) are displayed.
;;            Retracted datoms (OP=0) and empty resolved values are
;;            suppressed.
;;
;;            Cardinality-one attributes display only the most recent
;;            active value.
;;
;;            Cardinality-many attributes display all active values
;;            (deduplicated by most recent transaction precedence).
;;
;; Accepts:
;;   val  : eid | sys.attr.key | unique attribute value
;;   attr : optional aid (forces unique lookup attribute resolution path)
;;
;;------------------------------------------------------------------
    NEW eid,aid,tx,valkey,op
    NEW attrName,vk
    NEW seen,isMany,done
    NEW attrOrig

	; Process attr parameter
 	SET attr=$GET(attr)
    SET attrOrig=attr
    ; only resolve when attr is supplied AND not already numeric;
    ; -1 on failure is intentional — lets GetEID^apiGet attempt Path 2
    ; instead of silently falling back to Path 1
    SET attr=$SELECT(attr="":attr,attr=+attr:attr,1:$$GetEID^sapiGet(attr))

    DO BuildKeysCatalog^sapiBld
    SET eid=$$GetEID^apiGet(val,attr)

    IF eid=0 DO  QUIT
    . DO LOGINFO^logger("API","PrintEntity: entity not found — val=`"_val_"`")
    
    ; ------------------------------------------------------------------
    ; Header output
    ; ------------------------------------------------------------------
    WRITE !
    WRITE !,"======"
    WRITE !,val
    WRITE !,"======"

    ; ------------------------------------------------------------------
    ; Step 2 — iterate all attributes for this entity
    ; ^EATV(eid,aid,...) yields all attribute IDs in sorted order
    ; ------------------------------------------------------------------
    SET aid=""
    FOR  SET aid=$ORDER(^EATV(eid,aid)) QUIT:aid=""  DO
    . NEW attrName,val,vk,isMany,done
    . NEW seen
    . ; Step 3 — resolve attribute display name
    . SET attrName=""
    . SET vk=$ORDER(%Keys(aid,AKEYID,""))
    . IF vk'="" SET attrName=$GET(%Keys(aid,AKEYID,vk))
    . ; Step 4 — determine cardinality behavior
    . SET isMany=$$IsMultiValue^sapiVld(aid)
    . KILL seen
    . SET done=0
    . ; --------------------------------------------------------------
    . ; Step 5 — iterate transactions in descending order
    . ; Most recent transaction is processed first
    . ; --------------------------------------------------------------
    . SET tx=$ORDER(^EATV(eid,aid,""),-1)
    . FOR  QUIT:(tx="")!(done)  DO
    . . NEW valkey,op
    . . ; ----------------------------------------------------------
    . . ; Step 6 — iterate all value keys within this transaction
    . . ; ----------------------------------------------------------
    . . SET valkey=$ORDER(^EATV(eid,aid,tx,""))
    . . FOR  QUIT:(valkey="")!(done)  DO
    . . . ; Step 6a — deduplicate across transactions - first encounter = most recent occurrence
    . . . IF '$DATA(seen(valkey)) DO
    . . . . SET seen(valkey)=1
    . . . . ; Step 7 — check datom state (OP=1 live, OP=0 retracted)
    . . . . SET op=$GET(^EATV(eid,aid,tx,valkey),0)
    . . . . IF op DO
    . . . . . ; Step 8 — resolve value key to human-readable label
    . . . . . IF $$IsRefValType^sapiVld(aid) DO
	. . . . . . NEW targeteid
	. . . . . . SET targeteid=$GET(^TBD(aid,valkey))
	. . . . . . SET val=$SELECT(targeteid'="":$$GetKeyForRefEID^apiGet(targeteid),1:"")
	. . . . . ELSE  DO
	. . . . . . SET val=$$GetDictValue^sapiGet(aid,valkey)
    . . . . . ; suppress unresolved/blank values
    . . . . . IF val'="" DO
    . . . . . . WRITE !
    . . . . . . WRITE $JUSTIFY(eid,22)
    . . . . . . WRITE "  "
    . . . . . . WRITE $EXTRACT(attrName_$JUSTIFY("",33),1,33)
    . . . . . . WRITE "  "
    . . . . . . WRITE val
    . . . . . ; Step 9 — cardinality-one optimization stop after first live value is found
    . . . . . IF 'isMany SET done=1
    . . . SET valkey=$ORDER(^EATV(eid,aid,tx,valkey))
    . . SET tx=$ORDER(^EATV(eid,aid,tx),-1)

    WRITE !
    QUIT

L0 PH^api

PH(val,attr)
;;------------------------------------------------------------------
;; Routine  : PH — Procedure
;; Call     : DO PH^api(val,attr)
;;
;; Purpose  : Short-form alias for PrintHistory.
;;            Defaults val and attr at the call boundary so that
;;            undefined actuals do not shadow PrintHistory's own
;;            $GET handling with empty strings.
;;
;; Parameters
;;   val     : (IN, optional) Entity reference — eid, sys.attr.key,
;;                            or unique attribute value
;;   attr    : (IN, optional) Attribute key or aid, used to scope
;;                            unique-attribute resolution
;;------------------------------------------------------------------
	SET val=$GET(val,"")
	SET attr=$GET(attr,"")
    D PrintHistory(val,attr)
    QUIT

L0 PrintHistory^api

PrintHistory(val,attr)
;;------------------------------------------------------------------
;; Routine  : PrintHistory — Procedure
;;
;; Purpose  : Prints the raw ^EATV content for a resolved entity.
;;            Displays full datom history including retractions.
;;
;;            Accepts multiple entity reference forms via GetEID:
;;              - eid
;;              - sys.attr.key
;;              - unique attribute value
;;
;; Parameters
;;   val     : (IN, optional) Entity reference — eid, sys.attr.key,
;;                            or unique attribute value
;;   attr    : (IN, optional) Attribute key or aid, used to scope
;;                            unique-attribute resolution
;;
;; Resolution Flow:
;;   1. Entity resolution   val,attr -> GetEID -> eid
;;   2. Raw scan           eid -> ^EATV(eid,aid,tx,valkey)
;;
;; Output:
;;   One row per (eid, aid, tx, valkey, op)
;;
;;     eid | aid | tx | valkey | ( + / - )
;;
;; Globals:
;;   ^EATV - datom store
;;------------------------------------------------------------------

    NEW eid,aid,tx,valkey,op
    NEW attrOrig

    ; ------------------------------------------------------------------
    ; Step 0 — normalize attr into internal attribute id (aid)
    ; Supports:
    ;   - numeric aid (e.g. 1005)
    ;   - sys.attr.key (e.g. sandbox.dlc.id)
    ;
    ; Resolution is delegated to GetEID to avoid duplicate logic.
    ;
    ; attrOrig preserves the raw caller-supplied string BEFORE it is
    ; overwritten by the resolved/unresolved aid below. This is needed
    ; later — if resolution ultimately fails, the error branch must be
    ; able to tell whether the problem was an invalid ATTRIBUTE name
    ; (attrOrig was non-numeric and never resolved) versus an invalid
    ; VALUE under an otherwise-valid attribute.
    ; ------------------------------------------------------------------
    SET attr=$GET(attr)
    SET attrOrig=attr
    IF attr'="" DO
    . NEW tmp
    . ; If attr is already numeric (aid), keep it
    . IF attr=+attr QUIT
    . ; Otherwise treat it as sys.attr.key and resolve via GetEID.
    . ; Keep whatever GetEID^sapi returns, including -1 on failure,
    . ; so GetEID^api receives a non-empty aid and attempts Path 2
    . ; resolution rather than silently falling back to Path 1.
    . SET tmp=$$GetEID^sapiGet(attr)
    . SET attr=tmp

    ; ------------------------------------------------------------------
    ; Step 1 — resolve entity using unified resolver
    ; ------------------------------------------------------------------
    SET eid=$$GetEID^apiGet(val,attr)

    IF eid<0 DO  QUIT
    . NEW isEidShaped
    . SET isEidShaped=$$IsEID^utils(val)
    . ; Distinguish WHY resolution failed, for diagnosability.
    . ; attrOrig is used (not attr) because attr has already been
    . ; overwritten with the resolved aid (or -1) by Step 0.
    . ;
    . ;   Case A — caller supplied a non-numeric attr string, but it
    . ;            never resolved to a known attribute id at all
    . ;            (typo, or not a registered sys.attr.key). Invalid
    . ;            ATTRIBUTE — caller/schema mistake.
    . ;
    . ;   Case B — attr resolved successfully (or was passed in
    . ;            numerically), but val does not resolve as a unique
    . ;            value under that attribute. Invalid VALUE under a
    . ;            valid attribute.
    . ;
    . ;   Case C — no attr supplied, and val is structurally a valid
    . ;            EID (correct length/format) but does not exist in
    . ;            ^EATV. This is different from a garden-variety
    . ;            unresolvable string — it means someone passed a
    . ;            real-looking entity ID that isn't actually live,
    . ;            e.g. stale, retracted-and-purged, or mistyped in a
    . ;            way that still happens to be well-formed.
    . ;
    . ;   Case D — no attr supplied, val is not EID-shaped, and it
    . ;            also did not resolve via sys.attr.key. Ordinary
    . ;            "unrecognized reference" case.
    . IF (attrOrig'="")&(attrOrig'=+attrOrig)&(attr<0) DO
    . . DO LOGINFO^logger("API","PrintHistory: invalid attribute — `"_attrOrig_"` does not resolve to a known sys.attr.key")
    . ELSE  IF attr'="" DO
    . . DO LOGINFO^logger("API","PrintHistory: invalid value — val=`"_val_"` not found under aid="_attr)
    . ELSE  IF isEidShaped DO
    . . DO LOGINFO^logger("API","PrintHistory: invalid value — val=`"_val_"` has a valid EID structure but does not exist in ^EATV")
    . ELSE  DO
    . . DO LOGINFO^logger("API","PrintHistory: invalid value — val=`"_val_"` not found via sys.attr.key lookup")    

    ; ------------------------------------------------------------------
    ; Header
    ; ------------------------------------------------------------------
    WRITE !
    WRITE !,"======"
    WRITE !,val
    WRITE !,"======"
    WRITE !
    WRITE $JUSTIFY("eid",22)
    WRITE "  "
    WRITE $JUSTIFY("aid",6)
    WRITE "  "
    WRITE $JUSTIFY("tx",6)
    WRITE "  "
    WRITE $JUSTIFY("valkey",14)
    WRITE "  "
    WRITE "OP"
    WRITE !

    ; ------------------------------------------------------------------
    ; Step 2 — iterate all attributes for this entity
    ; ------------------------------------------------------------------
    SET aid=""
    FOR  SET aid=$ORDER(^EATV(eid,aid)) QUIT:aid=""  DO
    . NEW tx,valkey
    . ; --------------------------------------------------------------
    . ; Step 3 — iterate transactions (natural order preserved)
    . ; --------------------------------------------------------------
    . SET tx=""
    . FOR  SET tx=$ORDER(^EATV(eid,aid,tx)) QUIT:tx=""  DO
    . . NEW valkey
    . . ; ----------------------------------------------------------
    . . ; Step 4 — iterate valkeys for this tx
    . . ; ----------------------------------------------------------
    . . SET valkey=""
    . . FOR  SET valkey=$ORDER(^EATV(eid,aid,tx,valkey)) QUIT:valkey=""  DO
    . . . SET op=$GET(^EATV(eid,aid,tx,valkey),0)
    . . . WRITE !
    . . . WRITE $JUSTIFY(eid,22)
    . . . WRITE "  "
    . . . WRITE $JUSTIFY(aid,6)
    . . . WRITE "  "
    . . . WRITE $JUSTIFY(tx,6)
    . . . WRITE "  "
    . . . WRITE $JUSTIFY(valkey,14)
    . . . WRITE "  "
    . . . WRITE $SELECT(op=1:"( + )",1:"( - )")

    WRITE !
    QUIT
    
;; ========= END OF api.m =========

L0 AD^api

AD(ekv,akey,val,ns) ; Short-form alias for AssertDatom
;;------------------------------------------------------------------
;; Procedure : AD
;; Call      : DO AD^api(ekv,akey,val,ns)
;;
;; Purpose   : Short-form alias for AssertDatom.
;;------------------------------------------------------------------
    DO AssertDatom^api($GET(ekv),$GET(akey),$GET(val),$GET(ns,"sandbox"))
    QUIT

L0 AssertDatom^api

AssertDatom(ekv,akey,val,ns) ; Convenience wrapper of Stage to stage a single datom assertion for a new or existing entity
;;-------------------------------------------------------------------------------------------------------------------------
;; Procedure : AssertDatom
;; Call      : DO AssertDatom^api(ekv,akey,val,ns)
;;
;; Purpose   : Convenience wrapper of Stage to stage a single datom assertion for a new or existing entity.
;;             Detects the ekv format, builds the minimal Stage-compatible Datom structure
;;             and delegates to Stage.
;;
;; Parameters
;;   ekv     : (IN) entity specified with one of the following formats
;;   				Format A — raw EID       					e.g. "654f2564f8165hevmhe6"
;;   				Format B — compound lookup attr-key|value  	e.g. "sandbox.dlc.id|042"
;;   				Format C — entity's (sys.attr.key)			e.g. "obj.tom_hanks"
;;
;;   akey    : (IN) attribute key 								e.g. "movie.title"
;;
;;   val     : (IN) Value to assert — "|"-delimited for multi-value
;;
;;   ns      : (IN, optional) Namespace. Default: "sandbox"
;;
;;
;;	Examples : Format A  — DO AssertDatom^api("6583d90d9b0e5704qj59","sys.attr.name","Mr. Foobar")
;;			   Format B  — DO AssertDatom^api("sandbox.dlc.id|042","sandblox.dlc.checkup","F3 checkup update @en")
;;			   Format C  — DO AssertDatom^api("obj.tom_hanks","sys.attr.name","Tommy")
;;
;; 
;; TODO		: ATTENTION FORMAT C !!! it can accept anything here as ekv
;; 			  and if it is not found, it will create a new entity with 
;; 			  (1,"sys.attr.key")=ekv
;; 			  (1,akey)=val
;;
;;--------------------------------------------------------------------------------------------------------------------
    NEW Datom,attrkey,attrval
    SET ns=$GET(ns,"sandbox")

    IF $GET(ekv)=""  DO LOGERROR^logger("API","AssertDatom: ekv is required") QUIT
    IF $GET(akey)="" DO LOGERROR^logger("API","AssertDatom: akey is required") QUIT
    IF $GET(val)=""  DO LOGERROR^logger("API","AssertDatom: val is required") QUIT

    
    IF $$IsEID^utils(ekv) DO				; Format A — raw EID
    . SET Datom(1,"sys.attr.id")=ekv
    ELSE  IF ekv["|" DO						; Format B — compound lookup attr-key|value
    . SET attrkey=$PIECE(ekv,"|",1)
    . SET attrval=$PIECE(ekv,"|",2)    
    . SET Datom(1,attrkey)=attrval    
    ELSE  SET Datom(1,"sys.attr.key")=ekv 	; Format C — entity's sys.attr.key
	
    SET Datom(1,akey)=val    
    DO Stage(.Datom,ns,0)
    QUIT

L0 RD^api

RD(ekv,akey,val,ns) ; Short-form alias for RetractDatom
;;------------------------------------------------------------------
;; Procedure : RD
;; Call      : DO RD^api(ekv,akey,val,ns)
;;
;; Purpose   : Short-form alias for RetractDatom.
;;------------------------------------------------------------------
    DO RetractDatom^api($GET(ekv),$GET(akey),$GET(val),$GET(ns,"sandbox"))
    QUIT

L0 RetractDatom^api

RetractDatom(ekv,akey,val,ns) ; Convenience wrapper of Stage to stage a single datom retraction for an existing entity
;;---------------------------------------------------------------------------------------------------------------------
;; Procedure : RetractDatom
;; Call      : DO RetractDatom^api(ekv,akey,val,ns)
;;
;; Purpose   : Convenience wrapper of Stage to stage a single datom retraction for an existing entity.
;;             Detects the ekv format, builds the minimal Stage-compatible Datom structure, 
;;			   and delegates to Stage.
;;
;; Parameters
;;   ekv     : (IN) entity specified with one of the following formats
;;   				Format A — raw EID       					e.g. "654f2564f8165hevmhe6"
;;   				Format B — compound lookup attr-key|value  	e.g. "sandbox.dlc.id|042"
;;   				Format C — entity's (sys.attr.key)			e.g. "obj.tom_hanks"
;;
;;   akey    : (IN) attribute key 								e.g. "movie.title"
;;
;;   val     : (IN) value to retract
;;					if attribute is a single-valued then it is omitted (it is not optional)
;;				    if attribute is multi-valued then it must be present
;;
;;   ns      : (IN) optional namespace. Default: "sandbox"
;;
;;
;;	Examples : Format A  — DO RetractDatom^api("6583d90d9b0e5704qj59","sys.attr.name")
;;			   Format B  — DO RetractDatom^api("sandbox.dlc.id|042","sandblox.dlc.checkup") — single-value atttribute
;;			   Format C  — DO RetractDatom^api("obj.tom_hanks","sys.attr.name")
;;
;;
;;	Notice	 : Retractions always target existing entities and must never create a new one
;;             Format B and Format C both must check that entity already exists before Stage is called
;;             If the entity cannot be resolved, the call is aborted with an error before any staging occurs.
;;---------------------------------------------------------------------------------------------------------------------
    NEW Datom,attrkey,attrval,isEntityFound
    
    SET isEntityFound=1
    SET ns=$GET(ns,"sandbox")

    IF $GET(ekv)=""  DO LOGERROR^logger("API","RetractDatom: ekv is required") QUIT
    IF $GET(akey)="" DO LOGERROR^logger("API","RetractDatom: akey is required") QUIT    

    IF $$IsEID^utils(ekv) DO			; Format A — raw EID
    . SET isEntityFound=$$GetEID^apiGet(ekv)
    . SET Datom(1,"sys.attr.id")=ekv
    ELSE  IF ekv["|" DO 				; Format B — compound lookup attr-key|value
    . SET attrkey=$PIECE(ekv,"|",1)
    . SET attrval=$PIECE(ekv,"|",2)
    . SET Datom(1,attrkey)=attrval
    . SET isEntityFound=$$LookupEID^apiGet(attrval,attrkey)    
    ELSE  DO							; Format C — entity's sys.attr.key
    . SET isEntityFound=$$GetEID^apiGet(ekv)
    . SET Datom(1,"sys.attr.key")=ekv

	; Check if entity exists — Retractions must never create new entities.
	IF isEntityFound DO
	. SET Datom(1,akey,"-")=$GET(val) ; use $GET because for cardinality-one attributes val is omitted
    . ; Delegate datoms to Stage
    . DO Stage(.Datom,ns,0)
	ELSE  DO LOGERROR^logger("API","RetractDatom: entity not found — "_ekv)
    
    QUIT

L0 Transact^api

Transact(user) ; Commits the staged %ABR buffer to the ABox globals under a single transaction.
;;----------------------------------------------------------------------------------------------------
;; Routine  : Transact^api
;;
;; Purpose  : Commits the staged %ABR buffer to the ABox globals under a single transaction.
;;
;; Parameters
;;   user    : (IN)  User initiating the transaction, defaults to "Root" if not supplied.
;;
;;
;; Notice   : All writes are wrapped in TSTART/TCOMMIT. 
;;			  Any failure sets ok<1 and TROLLBACK discards all partial writes atomically.
;;          : %ABR is always KILLed on exit — success or failure.
;;----------------------------------------------------------------------------------------------------
    NEW ok      ;    transaction outcome:
    			;  2 no-op 
    		 	;  1 success, 
    			;  0 invalid, 
    			; -1 runtime error (drives TransactEND dispatch)
    
    NEW tx      ; transaction stamp shared by every assertion/retraction in this batch
    NEW aid     ; current attribute id while walking staged %ABR
    NEW valkey  ; current value key while walking staged %ABR
    NEW eid     ; current entity id while walking staged %ABR
    NEW cnt     ; $INCREMENT() return value (tx / entity / datom counters) — used for its side effect only

    SET ok=1
    SET user=$GET(user,"Root")

    ; Validate — %ABR must be initialised and populated before commit.
    IF '$DATA(%ABR) DO  GOTO TransactEND
    . SET ok=0

	; Empty staging is a valid no-op, not an error.
	IF $GET(%ABR,0)=0 DO  GOTO TransactEND
	. SET ok=2

; /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
    ; Wrap all global writes in a single transaction
    TSTART ()
    NEW $ETRAP
    SET $ETRAP="GOTO TransactERR^api"

    ; Generate transaction ID now — all assertions and retractions in this batch share a single
    ; tx stamp. Increment ^TXE root counter to track total indexed transactions.
    SET tx=$$TxAdd^sapiWFL(user)
    SET cnt=$INCREMENT(^TXE)

    ; Iterate all staged entity records. For each entity:    
    SET eid=""
    FOR  SET eid=$ORDER(%ABR(eid)) QUIT:(eid="")!(ok<1)  DO
    . QUIT:eid=%ABR            					; skip root node counter %ABR=n — not an entity subscript
    . IF '$$IsEntityRegistered^apiVld(eid) DO   ; Register entity in ABox entity index if not yet asserted
    . . SET ^ABE(eid,1)=""          			; mark entity as asserted
    . . SET cnt=$INCREMENT(^ABE)    			; increment total ABox entity counter
    . ; Iterate all attributes for this entity
    . SET aid=""
    . FOR  SET aid=$ORDER(%ABR(eid,aid)) QUIT:(aid="")!(ok<1)  DO
    . . ; Iterate all value keys for this attribute
    . . SET valkey=""
    . . FOR  SET valkey=$ORDER(%ABR(eid,aid,valkey)) QUIT:(valkey="")!(ok<1)  DO
    . . . NEW op
    . . . SET op=%ABR(eid,aid,valkey)		; Read OP from staging buffer
    . . . SET ^EATV(eid,aid,tx,valkey)=op   ; primary datom store, everything about a given entity
    . . . SET ^AVET(aid,valkey,eid,tx)=op   ; secondary-lookup index to find entities by attribute + value
    . . . SET ^AEVT(aid,eid,valkey,tx)=op   ; all values for a given attribute, column access
    . . . IF $$IsRefValType^sapiVld(aid) DO
    . . . . SET ^VAET(valkey,aid,eid,tx)=op ; reverse-reference index (reference attributes only)
    . . . SET ^TXE(tx,eid)=""               ; transaction → entity index
    . . . SET cnt=$INCREMENT(^EATV)         ; increment datom counter
    . . . DO LOGDEBUG^logger("API","Transact: ^EATV("_eid_","_aid_","_tx_","_valkey_")="_op)

TransactEND ; dispatch on ok/$TLEVEL to commit, rollback, or log-only
;;----------------------------------------------------------------------------------------------
;; Exit codes for ok:
;;   2  : no-op — no datom was asserted in the database, no changes, no entities affected
;;   1  : normal completion — TCOMMIT persists all globals
;;   0  : invalid data — validation guard failed, TROLLBACK discards partial writes
;;  -1  : runtime error — $ETRAP redirected here via TransactERR, TROLLBACK discards all writes
;;----------------------------------------------------------------------------------------------
    IF (ok=1)&($TLEVEL>0) DO  GOTO TransactCLEAR
    . TCOMMIT
    . DO LOGINFO^logger("API","Transact: committed "_$GET(%ABR,0)_" records, tx="_tx)

    IF (ok=0)&($TLEVEL>0) DO  GOTO TransactCLEAR
    . DO LOGERROR^logger("API","Transact: rolling back — invalid staging data")
    . TROLLBACK

    IF (ok=-1)&($TLEVEL>0) DO  GOTO TransactCLEAR
    . DO LOGCRITICAL^logger("API","Transact: rolling back — runtime error")
    . TROLLBACK

	; No transaction was ever open — this is the pre-TSTART guard-clause path
    ; (%ABR missing). Nothing to roll back, just log and clear.
    IF (ok=0)&($TLEVEL=0) DO  GOTO TransactCLEAR
    . DO LOGERROR^logger("API","Transact: %ABR not initialised — call INIT^api first")

	; No transaction was ever open — this is the pre-TSTART guard-clause path
    ; (%ABR empty). Nothing to commit or roll back, just log and clear.
    IF (ok=2)&($TLEVEL=0) DO  GOTO TransactCLEAR
    . DO LOGINFO^logger("API","Transact: no-op — no staged records, nothing committed")

TransactCLEAR ; reset staging buffer for the next batch, regardless of outcome
    KILL %ABR ; always clear — success or failure
    SET %ABR=0
    QUIT

TransactERR ; error trap target — captures runtime errors during the write loop
    ; Restore caller's trap immediately to prevent re-entry into this handler on any nested error
    NEW $ETRAP
    SET ok=-1
    DO LOGCRITICAL^logger("API","Transact: unexpected runtime error: "_$ZSTATUS)
    SET $ECODE=""          ; clear error code — required or error propagates
    GOTO TransactEND
; /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

L1 TxAdd^sapiWFL

TxAdd(user)
;;------------------------------------------------------------------
;; Function : TxAdd^sapiWFL
;; Purpose  : Creates and registers a new transaction record in the
;;            ^TX global, returning its unique transaction ID.
;;            Initialises the ^TX counter on first use.
;;
;; Scope
;;   Reads  : ^TX                    — transaction counter
;;   Writes : ^TX                    — incremented transaction counter
;;            ^TX(id,"dt")           — epoch timestamp of transaction
;;            ^TX(id,"user")         — user who initiated the transaction
;;
;; Parameters
;;   user   : (IN) String. The user initiating the transaction.
;;
;; Returns  : id — Integer. The newly assigned transaction ID.
;;
;; Call     : SET tx=$$TxAdd(user)
;;
;; Usage
;;   SET tx=$$TxAdd("Root")
;;
;;------------------------------------------------------------------
    NEW id,epoch
    
    ; Initialize counter if not set, starting counter at 7399
    IF '$DATA(^TX) SET ^TX=7399
    
    SET id=$INCREMENT(^TX)
    SET epoch=$$EPOCH^utils
    
    SET ^TX(id,"dt")=epoch
    SET ^TX(id,"usr")=user
    
    QUIT id

L2 EPOCH^utils

EPOCH()
;;------------------------------------------------------------------
;; Routine  : EPOCH Function
;;
;; Call     : WRITE $$EPOCH^<routine>
;;
;; Purpose  :
;;   Generates a hexadecimal representation of the current
;;   Unix/Universal timestamp in microseconds.
;;
;;   The timestamp is converted from decimal to hexadecimal,
;;   transformed to lowercase, and returned as a unique
;;   13-character string.
;;
;; Returns  :
;;   A lowercase hexadecimal string representing the current
;;   timestamp in microseconds.
;;
;; Notice   :
;;   - Uses YottaDB special variable $ZUT.
;;------------------------------------------------------------------
	NEW HEXUS
	; Convert the Unix/Universal timestamp in microseconds to a 13-character Hexadecimal string    
    SET HEXUS=$ZCONVERT($ZUT,"dec","hex")
    ; Convert it to lower case and return it
    QUIT $ZCONVERT(HEXUS,"L")

L1 IsRefValType^sapiVld

IsRefValType(aid)
;;------------------------------------------------------------------
;; Function : IsRefValType^sapiVld
;; Call     : IF $$IsRefValType^sapiVld(aid)
;;
;; Purpose  : Returns 1 if the metadata attribute is reference-typed, 0 otherwise. 
;;            A reference data type stores entity IDs as values 
;;
;;
;; Returns  :
;;   1 — reference data type (values are entity IDs)
;;   0 — literal type   	 (values are raw strings)
;;
;; Notes    :
;;			''$DATA(...) double-negation canonicalize $DATA's four-valued return into a strict 0/1 boolean.
;;------------------------------------------------------------------
    NEW isRef
    SET isRef=''$DATA(^TBAVET(RANGEID,REFKEY,aid))
    ;IF 'isRef DO LOGDEBUG^logger("sapiVld","IsRefValType: not reference-typed — aid="_aid)
    QUIT isRef

L0 Stage^api

Stage(Data,ns,isBulkLoad,ok) ; builds a staged set of datoms (all or nothing) that will later be consumed by Transact.
;;------------------------------------------------------------------------------------------------------------------------------
;; Routine  : Stage
;;
;; Call     : DO Stage^api(.Data,ns,isBulkLoad,.ok)
;;
;; Usage    : Entry point for staging multiple records into %ABR before
;;            transactional commit via Transact^api. Supports both assertions
;;            (default) and retractions ("-") within the same record.
;;
;; Purpose  : Resolves each record's attribute keys, orchestrates entity
;;            resolution/validation/staging via StageRecord, and guarantees
;;            batch atomicity — the FOR loop stops on the first failure, and
;;            %ABR is discarded in full, not just the failing record.
;;
;; Parameters
;;   Data       : (IN)  Multi-record input structure
;;                       Data(idx,attributeName)     = value  ; assert (default)
;;                       Data(idx,attributeName,"-") = value  ; retract (explicit)
;;
;;   ns         : (IN)  namespace prefix path of arbitrary depth (one or more dot-separated segments)
;;						It's not restricted to a single top-level segment; 
;;						it's simply whatever comes before the entity-type-qualified attribute name
;;						e.g. (john.sandbox.cinema.) --> ns
;;							 (movie.title)			--> akey
;;                      Entity's attributes are relative to this namespace. e.g. movie.title
;;
;;   isBulkLoad : (IN)  1 = skips record validation
;;                      0 = performs record validation
;;
;;   ok         : (OUT)  2 : every record staged cleanly, but nothing changed
;;						 1 : if every record staged; 
;;						 0 : validation/resolution failure 
;;						-1 : deeper failure surfaced the batch was aborted %ABR discarded
;;
;; Namespace convention:
;;   ns parameter is the schema namespace used for attribute resolution and validation.
;;
;;   Data Input Example		:	e.g. ns="john.sandbox.cinema"
;;      						Then input records use attributes relative to the schema namespace e.g.:
;;								S M(17,"movie.title")="Seven @en"
;;    							S M(17,"movie.genre")="thriller|mystery"
;;						etc...
;;   							Do NOT pass fully qualified attribute names when using Stage:
;;     							S M(17,"john.sandbox.cinema.movie.imdbid")="tt0011100"  <-- invalid attribute 
;;
;; %ABR Example				: Staged triplets of two records
;;							  %ABR("6583f05ec5dd43kbzqc3",1000,"K3E8.01889C")=1
;; 				  			  %ABR("6583f05ec5dd43kbzqc3",1001,"K3E9.018859")=1
;; 				  			  %ABR("6583f05ec5dd43kbzqc3",1001,"K3E9.01885A")=1
;; 				  			  %ABR("6583f05ec5dd43kbzqc3",1002,"K3EA.018880")=1
;;
;; 				  			  %ABR("6583f05ec5e850wgrgnc",1000,"K3E8.01889E")=1
;; 				  			  %ABR("6583f05ec5e850wgrgnc",1001,"K3E9.018876")=1
;; 				  			  %ABR("6583f05ec5e850wgrgnc",1002,"K3EA.01889F")=1
;;
;;------------------------------------------------------------------------------------------
    SET ok=1
    ; Guard — %ABR must exist before staging can begin
    IF '$DATA(%ABR) DO LOGERROR^logger("API","Stage: %ABR not initialised — call INIT^api first") SET ok=0 QUIT

    NEW idx			; drives the FOR loop over record indices in Data
    NEW ReqAttr		; required-attribute map for ns, built once for the whole batch
    NEW AttrCache	; forward/reverse attribute key cache for ns, built once for the whole batch
    NEW recCount	; total records attempted, for the summary log line
    NEW failedIdx	; idx of the record that caused the batch to abort, if any
    
	SET ns=$GET(ns,"sandbox")          ; default namespace when caller omits ns
    SET isBulkLoad=$GET(isBulkLoad,0)  ; default 0 — validation runs unless caller opts into bulk load
    SET recCount=0                     ; running count of records attempted, for the summary log line
    SET failedIdx=""                   ; idx of the record that aborted the batch, if any

	
	; Validate — %ABR must be initialised and populated before staging phase starts.
    IF '$DATA(%ABR) DO  QUIT
    . DO LOGERROR^logger("API","Transact: %ABR not initialised — call INIT^api first")

    ; Build required attributes map — used by ValidateRecord
    DO BuildAttrsRequired^sapiBld(ns,.ReqAttr)
	
    ; Build schema attributes cache — 
    ;	forward map used by ResolveRecAttrs
    ; 	reverse map used by ValidateRecord
    DO BuildAttrsCache^sapiBld(ns,.AttrCache)
		
    SET idx=""
    ; Iterate the top-level first subscript of Data input. Each idx represents a temporary ID for a record.
    ; Loop exits when idx is exhausted, or stops immediately on first failure (ok<1) —
    ; remaining records are never attempted.
    FOR  SET idx=$ORDER(Data(idx)) QUIT:(idx="")!(ok<1)  DO
    . NEW ResolvedRec 			; current record's attributes resolved to internal aids
    . SET recCount=recCount+1
    . SET ok=$$ResolveRecAttrs^apiRslv(ns,$NAME(Data(idx)),.AttrCache,.ResolvedRec) ; Resolve attribute keys to internal aids
    . IF ok=0 DO  QUIT
    . . SET failedIdx=idx
    . . DO LOGERROR^logger("API","Stage: attribute resolution failed for idx="_idx_" — batch will be aborted")
    . SET ok=$$StageRecord^apiWFL(.ResolvedRec,isBulkLoad,.ReqAttr,.AttrCache)
    . IF ok<1 SET failedIdx=idx

    IF ok<1 DO    
    . KILL %ABR
    . SET %ABR=0
    . DO LOGERROR^logger("API","Stage: batch aborted at record idx="_failedIdx_" (ok="_ok_") — entire batch discarded, "_recCount_" record(s) attempted for ns="_ns)
    ELSE  IF $GET(%ABR,0)=0 DO
    . SET ok=2
    . DO LOGINFO^logger("API","Stage: no-op — no staged records, nothing committed")
    ELSE  DO
    . DO LOGINFO^logger("API","Stage: processed "_recCount_" record(s) — "_$GET(%ABR,0)_" record(s) staged for ns="_ns_" isBulkLoad="_isBulkLoad)
    
    QUIT    

L1 BuildAttrsRequired^sapiBld

BuildAttrsRequired(ns,ReqAttr) ; collect required attribute IDs for a schema prefix
;;------------------------------------------------------------------
;; Routine  : BuildAttrsRequired^sapiBld
;;
;; Call     : DO BuildAttrsRequired^sapiBld(ns,.ReqAttr)
;;
;; Usage    : Called before staging a record to know which attributes must
;;            be present, then checked against the caller's own data
;;
;; Purpose  : Populates ReqAttr with the attribute IDs of all required
;;            attributes for the given schema prefix.
;;
;; Parameters
;;   ns      : (IN)  Schema prefix string, e.g. "sandbox.movie"
;;   ReqAttr : (OUT) Local array, passed by reference
;;
;; Scope (Indirect Globals Access)
;;   Reads  : %Schema — populated internally via BuildSchemaView
;;------------------------------------------------------------------
    ; Output:
    ;   ReqAttr      = total count of required attributes
    ;   ReqAttr(aid) = attribute's own key string (from sys.attr.key),
    ;                  present only for attributes flagged sys.attr.required=1

    KILL ReqAttr
    NEW aid ; iterates every attribute in %Schema
    SET ReqAttr=0
    DO BuildSchemaView(ns)
    SET aid=""
    ; Keep only attributes flagged required, storing their key string for lookup
    FOR  SET aid=$ORDER(%Schema(aid)) QUIT:aid=""  DO
    . IF $GET(%Schema(aid,"sys.attr.required"))=1 DO
    . . SET ReqAttr(aid)=$GET(%Schema(aid,"sys.attr.key"))
    . . SET ReqAttr=$INCREMENT(ReqAttr)

    QUIT

L1 BuildAttrsCache^sapiBld

BuildAttrsCache(ns,AttrsCache) ; build forward/reverse attribute caches for a namespace
;;------------------------------------------------------------------
;; Routine  : BuildAttrsCache^sapiBld
;;
;; Call     : DO BuildAttrsCache^sapiBld(ns,.AttrsCache)
;;
;; Usage    : DO BuildAttrsCache^sapiBld("sandbox",.AttrsCache)
;;
;; Purpose  : Builds a cache of schema attributes belonging to the specified
;;            namespace, always including the shared sys.attr namespace.
;;
;; Parameters
;;   ns         : (IN)  Namespace prefix, e.g. "sandbox" or "sandbox.movie"
;;   AttrsCache : (OUT) Local array, passed by reference
;;
;; Scope
;;   Reads  : ^TBDR(AKEYID,fullkey)
;;------------------------------------------------------------------
    ; Two entries are written per attribute:
    ;   AttrsCache(fullkey) = aid            — forward map, used by ResolveRecAttrs
    ;   AttrsCache(aid)     = sub-namespace  — reverse map, used by ValidateRecord
    ;                                          to check namespace membership without
    ;                                          a schema lookup per attribute
    ;
    ; Example:
    ;   AttrsCache("sandbox.movie.genre")  = 700
    ;   AttrsCache("sandbox.movie.imdbid") = 703
    ;   AttrsCache("sys.attr.key")         = 210
    ;   AttrsCache(700) = "sandbox.movie"
    ;   AttrsCache(703) = "sandbox.movie"
    ;   AttrsCache(210) = "sys.attr"

    NEW fullkey	; iterates registered attribute key strings in ^TBDR(AKEYID,*)
    NEW prefix	; current namespace + "." being matched against fullkey
    NEW nsIdx	; iterates nsList
    NEW nsList	; the two namespaces to scan: ns and the always-included sys.attr
    NEW aid		; resolved attribute entity ID for fullkey
    NEW nsDepth	; number of dot-separated segments in the current namespace,
    		; used so the reverse map keeps exactly that many segments of fullkey

    KILL AttrsCache

    ; Always include sys.attr namespace in addition to the requested namespace
    SET nsList(1)=ns
    SET nsList(2)="sys.attr"

	; Iterate over each namespace in nsList
    SET nsIdx=""
    FOR  SET nsIdx=$ORDER(nsList(nsIdx)) QUIT:nsIdx=""  DO
    . SET prefix=nsList(nsIdx)_"."
    . SET nsDepth=$LENGTH(nsList(nsIdx),".") ; e.g. "sandbox.movie" -> 2, "sandbox" -> 1
    . SET fullkey=""
    . ; Iterate over all registered attribute keys matching this namespace prefix
    . FOR  SET fullkey=$ORDER(^TBDR(AKEYID,fullkey)) QUIT:fullkey=""  DO
    . . IF $EXTRACT(fullkey,1,$LENGTH(prefix))'=prefix QUIT ; outside this namespace — skip
    . . SET aid=$$GetEID^sapiGet(fullkey)
    . . SET AttrsCache(fullkey)=aid                              ; forward map: key -> aid
    . . ; Reverse map: aid -> sub-namespace.
    . . ; sys.attr is flat — attribute names sit directly under sys.attr with
    . . ; no entity-type level in between (e.g. "sys.attr.key"), so nothing
    . . ; is stripped and the full fullkey is kept as-is.
    . . ; Every other namespace (e.g. "sandbox") has an entity-type segment
    . . ; between the namespace and the attribute name (e.g. "sandbox.movie.genre"),
    . . ; so the trailing attribute-name segment is stripped via $PIECE,
    . . ; leaving the entity-type-qualified sub-namespace (e.g. "sandbox.movie").
    . . SET AttrsCache(aid)=$SELECT(nsList(nsIdx)="sys.attr":fullkey,1:$PIECE(fullkey,".",1,nsDepth))

    QUIT

L1 ResolveRecAttrs^apiRslv

ResolveRecAttrs(ns,dataRef,AttrCache,ResolvedRec) ; Resolve a single record's attribute names into internal attribute IDs building ResolvedRec(op,aid)=value
;; ------------------------------------------------------------------------------------------------------------------
;; Function : $$ResolveRecAttrs^apiRslv
;;
;; Purpose:    	Resolve a single record's attribute names into internal attribute IDs
;;   			building ResolvedRec(op,aid)=value
;;
;; Parameters:
;;   ns         : (IN)  	Namespace prefix path of arbitrary depth
;;
;;   dataRef    : (IN)  	Named reference e.g. "Data(17)"
;;                      	that gives access to record No.17 in local array Data
;;
;;   AttrCache  : (IN)  	Attribute resolution cache, read-only here:
;;                      	AttrCache("sandbox.movie.title")=700
;;                      	AttrCache("sandbox.movie.genre")=701
;;
;;	ResolvedRec : (IN/OUT)	Starts empty, then nodes are stored - ResolvedRec(op,attributeId)=value
;;
;; 							Example:
;;   							Data(1,"movie.title")="The Reader"
;;   							Data(1,"movie.releaseYear")=2008
;;   							Data(1,"movie.genre","-")="thriller"
;;
;;   						becomes
;;   							ResolvedRec("+",700)="The Reader"
;;   							ResolvedRec("+",702)=2008
;;   							ResolvedRec("-",701)="thriller"
;;
;; Returns:
;;		 1 - if every attribute name resolved 
;;		 0 - if any attribute could not be resolved
;; ------------------------------------------------------------
    NEW akey,aid,ok
    SET akey=""
    SET ok=1

    ; Iterate the attribute-name subscripts of dataRef; resolve each to an aid via $$ResolveAttr
    ; Loop exits when akey is exhausted, or stops immediately on the first unresolvable attribute (ok=0)
    ; remaining attributes are never attempted.
    FOR  SET akey=$ORDER(@dataRef@(akey)) QUIT:(akey="")!(ok=0)  DO
    . SET aid=$$ResolveAttr(ns,akey,.AttrCache)
    . IF aid=0 SET ok=0 QUIT
    . IF $DATA(@dataRef@(akey))#10 SET ResolvedRec("+",aid)=@dataRef@(akey)
    . IF $DATA(@dataRef@(akey,"-")) SET ResolvedRec("-",aid)=$GET(@dataRef@(akey,"-"))

    QUIT ok

L2 ResolveAttr^apiRslv

ResolveAttr(ns,akey,AttrCache) ; Resolve a single attribute key to its internal attribute ID
;; ----------------------------------------------------------------------------------------------
;; Procedure : $$ResolveAttr^apiRslv
;;
;; Purpose:		Resolve a single attribute name to its internal attribute ID,
;;   			trying the namespace prefixed key first, then try with a bear key
;;
;; Inputs:
;;   ns        - Namespace prefix path of arbitrary depth
;;   akey      - Attribute name to resolve (e.g. "movie.title").
;;   AttrCache - Attributes resolution cache (see ResolveRecAttrs).
;;
;; Output:
;;   Returns aid - internal attribute ID (any number > 0), or 0 if unresolvable.
;; ----------------------------------------------------------------------------------------------
    NEW aid
    ; +$GET(...) coersion +""=0
    SET aid=+$GET(AttrCache(ns_"."_akey)) 	; try ns prefixed key 	e.g. sandbox. movie.genre --> 700
    IF aid=0 SET aid=+$GET(AttrCache(akey))	; try with a bear key  	e.g. sys.attr.id    	  --> 277

	; both attribute resolution methods failed
    IF aid=0 DO LOGERROR^logger("apiRslv","ResolveRecAttrs: unresolvable attribute name '"_akey_"' for ns="_ns_" — aborting record resolution")
    QUIT aid

L1 StageRecord^apiWFL

StageRecord(ResolvedRec,isBulkLoad,ReqAttr,AttrCache) ; resolve, validate, and stage one record's triplets into %ABR
;;-------------------------------------------------------------------------------------------------
;; Function : $$StageRecord^apiWFL
;;
;; Purpose  : Resolve target entity, validate, and stage one record's assertions and
;;            retractions into %ABR.
;;
;;            At the staging phase, ResolvedRec contains ONLY entity facts — control
;;            attributes have been consumed by ResolveTargetEntity and eid is immutable.
;;            Assertions "+" are processed before retractions "-" to ensure entity key is
;;            staged before any retractions are attempted.
;;
;; Parameters
;;   ResolvedRec : (IN) Resolved record — assertions and retractions; mutated by
;;                       ResolveTargetEntity during entity-key resolution
;;   isBulkLoad  : (IN) If 1, skips ValidateRecord entirely
;;   ReqAttr     : (IN) Required attributes map from BuildAttrsRequired
;;   AttrCache   : (IN) Schema attribute cache from BuildAttrsCache
;;
;; Returns
;;   2  — there have been no changes on the record no-op 
;;   1  — this record staged cleanly (see hasChanges handling below)
;;   0  — controlled failure (resolution/validation/staging rejected the record)
;;  -1  — uncaught runtime error
;;
;; Notes
;;   DatomAssert/DatomRetract may return 1 (staged), 2 (no-op), or 0 (failed) per attribute.
;;   Only 0 stops the staging loops early. A record where staging fully completes — whether
;;   every attribute wrote something, or some/all were no-ops — is reported as result=1;
;;   hasChanges (via $DATA(%ABR(eid))) is what actually distinguishes "wrote something" from
;;   "processed cleanly but nothing changed," so the per-attribute 1-vs-2 distinction doesn't
;;   need to be tracked separately here.
;;------------------------------------------------------------------------------------------------------
    NEW eid			; target entity being asserted or retracted against
    NEW isNewEntity	; whether the target entity is new or previously registered
    NEW hasChanges	; whether staging produced an actual change to %ABR
    NEW result		; outcome of this record's processing, returned to caller

    SET eid=0
    SET result=1
    SET hasChanges=0

    ; Phase 1 — Resolve target entity. No transaction open yet — QUIT directly on failure.
    SET eid=$$ResolveTargetEntity^apiRslv(.ResolvedRec) ; error or success message is logged in ResolveTargetEntity help functions
    IF eid=0  QUIT 0									; aborting — entity resolution failed


    ; Phase 2 — Validate entity facts. Still no transaction open — QUIT directly on failure.
    SET isNewEntity='$$IsEntityRegistered^apiVld(eid)
    IF 'isBulkLoad DO
    . SET result=$$ValidateRecord^apiVld(.ResolvedRec,.ReqAttr,.AttrCache,isNewEntity)
    ELSE  DO
    . DO LOGWARNING^logger("apiWFL","StageRecord: isBulkLoad=1 — ValidateRecord skipped for eid="_eid_" (required-attribute checks bypassed)")

    IF 'result DO  QUIT 0
    . DO LOGERROR^logger("apiWFL","StageRecord: aborting — entity fact validation failed")

	; /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
    ; Phase 3 — Stage datoms into %ABR (local only — no global write, no TSTART needed).
    ; $ETRAP armed to contain a runtime error inside this phase without aborting Stage^api's loop.
    NEW $ETRAP
    SET $ETRAP="GOTO StageRecordERR^apiWFL"

    NEW aid  ; current attribute id while walking ResolvedRec's staged assertions/retractions
    NEW ok   ; per-attribute staging outcome — loop stops early only when this is 0
    SET ok=1

    ; Process assertions "+" first
    SET aid=""
    FOR  SET aid=$ORDER(ResolvedRec("+",aid)) QUIT:aid=""!'ok  DO
    . SET ok=$$DatomAssert(eid,aid,ResolvedRec("+",aid))

    ; Process retractions "-" — only if assertions succeeded
    IF ok DO
    . SET aid=""
    . FOR  SET aid=$ORDER(ResolvedRec("-",aid)) QUIT:aid=""!'ok  DO
    . . SET ok=$$DatomRetract(eid,aid,ResolvedRec("-",aid))

    SET hasChanges=$DATA(%ABR(eid))>0
    
    IF ok=0  SET result=0
    ELSE  IF hasChanges SET result=1
    ELSE  SET result=2

    GOTO StageRecordEND

L2 ResolveTargetEntity^apiRslv

ResolveTargetEntity(ResolvedRec) ; Resolve target entity ID for the data record
;;------------------------------------------------------------------
;; Function : ResolveTargetEntity^apiRslv
;;
;; Purpose  : Resolve target entity ID for the data record
;;
;; Parameters
;;   ResolvedRec : (IN/OUT) Resolved record — control attributes consumed
;;
;; Returns  :
;;   			eid  — resolved target entity ID on success
;;   			  0  — any resolution failure
;;------------------------------------------------------------------
    NEW eid		; target entity id

    SET eid=0

    ; Path 0 — sys.attr.id (explicit raw EID)
    IF $DATA(ResolvedRec("+",SYSATTRID)) SET eid=$$ResolveByEID(.ResolvedRec)
    ; Path 1 — sys.attr.key (bare key)
    ELSE  IF $DATA(ResolvedRec("+",AKEYID)) SET eid=$$ResolveByNaturalKey(.ResolvedRec)
    ; Path 2 — unique attribute resolution
    ELSE  SET eid=$$ResolveByUnique(.ResolvedRec)

    QUIT eid
L3 ResolveByEID^apiRslv
ResolveByEID(ResolvedRec) ; resolves target entity via explicit raw EID reference using sys.attr.id
;;--------------------------------------------------------------------
;; Function  : $$ResolveByEID^apiRslv
;;
;; Purpose   : ResolveTargeEntity/Path 0 — explicit raw EID reference.
;;             Resolves the target entity using the value of sys.attr.id attribute
;;
;; Parameters
;;   ResolvedRec : (IN/OUT)  Resolved record — sys.attr.id node is killed
;;
;; Returns   	 :
;;             		eid resolved entity ID
;;             		0   failed to resolve
;;------------------------------------------------------------------
    NEW idval	; raw entity ID value from ResolvedRec
    NEW eid		; resolved entity ID, or 0 on failure

    SET idval=$GET(ResolvedRec("+",SYSATTRID))    
    
    ; remove sys.attr.id node ResolvedRec node — it must not reach DatomAssert as entity fact
    KILL ResolvedRec("+",SYSATTRID) 

    ; Validates both format (structurally valid EID) and existence (must exist in ^EATV)
    ; $$GetEID logs both succes and failure messages
    SET eid=$$GetEID^apiGet(idval)    
    
    QUIT eid
L4 GetEID^apiGet
GetEID(val,aid)
;;------------------------------------------------------------------
;; Function : GetEID^apiGet
;;
;; Call     : SET eid=$$GetEID^apiGet(val)
;;            SET eid=$$GetEID^apiGet(val,aid)
;;
;; Purpose  : Resolves any supported entity reference to an internal entity ID (eid).
;;
;;            Three resolution paths are attempted in order:
;;            Path 0 — Strict EID validation + existence check	(aid omitted)
;;            Path 1 — sys.attr.key lookup 						(aid omitted)
;;            Path 2 — UNQINSERT/UNQUPSERT attribute lookup 	(aid supplied)
;;
;; Parameters
;;   val     : (IN) One of:
;;               - persisted eid        e.g. "654f2564f8165hevmhe6"
;;               - sys.attr.key value   e.g. "obj.tom_hanks"
;;               - unique attr value    e.g. "042"
;;
;;   aid 	 : (IN) aid is required only for path 2.
;;
;; Returns  :
;;    eid   : resolved entity ID
;;    0     : cannot be resolved
;;
;; Notice   : LIVENESS
;;            Both Path 1 and Path 2 walk ALL eid candidates under ^AVET(aid,valkey,candidate) 
;; 			  and accept only the one for which GetCurrentVK(candidate,aid)=valkey 
;;            i.e. the one whose most recent tx for `aid` is still asserting this exact `valkey`. 
;;------------------------------------------------------------------
    ; Guard — empty val can never resolve via any path; fail fast before touching globals.
	IF val="" DO  QUIT 0
    . DO LOGERROR^logger("apiGet","GetEID: empty value supplied — cannot resolve")
    
    NEW eid,isEidShaped
    SET eid=0
	SET aid=$GET(aid)
	
	; Guard — aid, if supplied, must be a numeric attribute ID, not a sys.attr.key string.
	IF aid'="",aid'=+aid DO  QUIT 0
    . DO LOGERROR^logger("apiGet","GetEID: aid must be numeric — got '"_aid_"'. Use LookupEID^api if resolving by a sys.attr.key string.")
	
    ; ------------------------------------------------------------------
    ; Path 0 — Strict EID validation + existence check
    ; ------------------------------------------------------------------
    SET isEidShaped=$$IsEID^utils(val)					; performs structural validation
    IF isEidShaped DO  QUIT eid							; if structure is not valid QUIT immediately
    . IF $$IsEntityRegistered^apiVld(val) DO 			; if raw EID exists (val)
    . . SET eid=val
	. . DO LOGDEBUG^logger("apiGet","GetEID: resolved via EID: "_val)
    . ELSE  DO LOGDEBUG^logger("apiGet","GetEID: "_val_" is a valid EID but is not registered")

    ; ------------------------------------------------------------------
    ; Path 1 — sys.attr.key lookup (aid omitted)
    ; Skipped when aid is supplied, also unreachable when val is EID-shaped
    ; ------------------------------------------------------------------
    IF aid="" DO  QUIT eid
    . SET eid=$$ResolveLiveEID^apiRslv(AKEYID,val)
    . IF eid=0 DO LOGDEBUG^logger("apiGet","GetEID: entity cannot be resolved via sys.attr.key: "_val)
    . ELSE  DO LOGDEBUG^logger("apiGet","GetEID: resolved via sys.attr.key: "_val)

    ; ---------------------------------------------------------------------------------
    ; Path 2 — unique attribute lookup (aid must be supplied)
    ; Skipped when aid is omitted, also unreachable when val is EID-shaped
    ; ----------------------------------------------------------------------------------
    SET eid=$$ResolveLiveEID^apiRslv(aid,val)
    IF eid=0 DO LOGDEBUG^logger("apiGet","GetEID: entity cannot be resolved via aid="_aid_" val="_val)
    ELSE  DO LOGDEBUG^logger("apiGet","GetEID: resolved via aid="_aid_" val="_val)


    QUIT eid
L5 IsEID^utils
IsEID(val) ; check if entity ID is structurally valid
;;------------------------------------------------------------------
;; Function  : $$IsEID^utils
;;
;; Purpose   : Check if entity ID is structurally valid.
;;
;;             Structural rules derived from GENID:
;;               - Total length = 13 (hex timestamp) + 7 (base36 suffix) = exactly 20 characters (default RLEN=7)
;;               - First 13 characters are lowercase hex: 0-9, a-f only
;;                 (timestamp produced by EPOCH via $ZCONVERT hex output)
;;               - Last 7 characters are lowercase base36: 0-9, a-z only
;;               - No uppercase characters permitted anywhere
;;
;; Parameters
;;   val    : (IN)  eid string to test
;;
;; Returns   :
;;             1  val is a structurally valid EID — exactly 20 chars,
;;                first 13 lowercase hex, last 7 lowercase base36
;;             0  val fails structural validation
;;------------------------------------------------------------------
    NEW len			; length of val
    NEW ts			; first 13 chars — candidate hex timestamp
    NEW sfx			; last 7 chars — candidate base36 suffix
    NEW hexchars	; allowed lowercase hex character set
    NEW b36chars	; allowed lowercase base36 character set

    ; ------------------------------------------------------------------
    ; Length must be EXACTLY 20 — not "at least 8" as before.
    ; ------------------------------------------------------------------
    SET len=$LENGTH(val)
    IF len'=20 QUIT 0
    ;. DO LOGDEBUG^logger("utils","IsEID: malformed EID — expected 20 chars, got "_len_" — "_val)

    SET ts=$EXTRACT(val,1,13)
    SET sfx=$EXTRACT(val,14,20)

    ; ------------------------------------------------------------------
    ; Prefix — 13 chars, lowercase hex only (0-9,a-f)
    ; ------------------------------------------------------------------
    SET hexchars="0123456789abcdef"
    IF $TRANSLATE(ts,hexchars)'="" DO  QUIT 0
    . DO LOGDEBUG^logger("utils","IsEID: malformed EID — non-hex timestamp segment — "_val)

    ; ------------------------------------------------------------------
    ; Suffix — 7 chars, lowercase base36 only (0-9,a-z)
    ; ------------------------------------------------------------------
    SET b36chars="0123456789abcdefghijklmnopqrstuvwxyz"
    IF $TRANSLATE(sfx,b36chars)'="" DO  QUIT 0
    . DO LOGDEBUG^logger("utils","IsEID: malformed EID — non-base36 suffix segment — "_val)

    DO LOGDEBUG^logger("utils","IsEID: structurally valid — "_val)
    QUIT 1
L5 IsEntityRegistered^apiVld
IsEntityRegistered(eid) ; check whether entity is registered in ^ABE
;;----------------------------------------------------------------------------------------------------
;; Function : IsEntityRegistered^apiVld
;;
;; Call     : WRITE $$IsEntityRegistered^apiVld(eid)
;;
;; Purpose  : Determine whether an ABox entity is registered in ^ABE.
;;
;; Parameters
;;   eid    : (IN) Entity identifier.
;;
;; Scope
;;   Reads  : ^ABE(eid,1)
;;
;; Returns  : 1 if entity is registered in ^ABE, otherwise 0.
;;
;; Notes    : registration is indicated by the existence of the node, not by a non-empty value e.g.
;;			  ^ABE("65825611ba143blkjysi",1)=""
;;			  ^ABE("65825611ba2aemmuqtyi",1)=""
;;
;;	''$DATA(...) double-negation canonicalize $DATA's four-valued return into a strict 0/1 boolean.
;;----------------------------------------------------------------------------------------------------    
    QUIT ''$DATA(^ABE(eid,1))
L5 ResolveLiveEID^apiRslv
ResolveLiveEID(aid,val) ; find the live eid whose current value for aid equals val, via ^TBDR/^AVET
;;------------------------------------------------------------------
;; Function : $$ResolveLiveEID^apiGet
;;
;; Purpose  : Resolve val to its valkey under aid via ^TBDR, then walk ^AVET
;;            candidates for that (aid,valkey) and return the one still live.
;;
;; Parameters
;;   aid : (IN) Attribute ID whose value dictionary/index to search.
;;   val : (IN) Literal value to resolve (natural key or unique attr value).
;;
;; Returns  :
;;   eid — the live candidate entity ID
;;   0   — no valkey registered for aid/val, or no live candidate found
;;------------------------------------------------------------------
    NEW valkey		; resolved dictionary key for val under aid
    NEW candidate	; current eid candidate walked from ^AVET
    NEW eid			; live eid found, or 0

    SET eid=0
    SET valkey=$GET(^TBDR(aid,val))
    IF valkey'="" DO
    . SET candidate=""
    . ; Iterate all eid candidates under (aid,valkey) in order — stop
    . ; at the first one whose claim is still live. Presence in ^AVET
    . ; only means "held this value at some point," not "holds it now."
    . FOR  SET candidate=$ORDER(^AVET(aid,valkey,candidate)) QUIT:(candidate="")!eid  DO
    . . IF $$GetCurrentVK^apiGet(candidate,aid)=valkey SET eid=candidate

    QUIT eid
L3 ResolveByNaturalKey^apiRslv
ResolveByNaturalKey(ResolvedRec) ; resolve target entity via sys.attr.key. It finds or it creates EID
;;------------------------------------------------------------------
;; Function : ResolveByNaturalKey^apiRslv
;;
;; Purpose  : ResolveTargeEntity/Path 1 — natural key resolution.
;;            Resolves the target entity from sys.attr.key using
;;            find-or-create semantics.
;;
;;            If the key is already registered → return its existing eid.
;;            If the key is not found → call GENID, create new entity that 
;;            is anchored to this sys.attr.key value on all future transactions.
;;
;; Parameters
;;   ResolvedRec : (IN)  Resolved record
;;
;; Returns  	 :
;;   			 	eid — resolved or generated entity ID (find-or-create never fails)
;;------------------------------------------------------------------
    NEW keyval	; sys.attr.key value read from ResolvedRec
    NEW eid		; resolved or generated entity ID

    SET keyval=$GET(ResolvedRec("+",AKEYID)) ; it must reach DatomAssert as entity fact
    SET eid=$$GetEID^apiGet(keyval)
    
    IF eid>0 DO
    . DO LOGDEBUG^logger("apiRslv","ResolveByNaturalKey: resolved via sys.attr.key — "_keyval)
    ELSE  DO
    . SET eid=$$GENID^utils
    . DO LOGDEBUG^logger("apiRslv","ResolveByNaturalKey: new entity — generated EID "_eid)
	
    QUIT eid
L4 GENID^utils
GENID(RLEN) ; Generate a Time-Sorted Compact ID (KSUID-style)
;;------------------------------------------------------------------
;; Routine  : GENID Function
;;
;; Call     : WRITE $$GENID^<routine>(RLEN)
;;
;; Usage    :
;;   SET ID=$$GENID^<routine>()
;;   SET ID=$$GENID^<routine>(10)
;;
;; Purpose  :
;;   Generates a compact, chronologically sortable unique identifier
;;   similar in concept to a KSUID.
;;
;;   The identifier consists of:
;;     - A 13-character hexadecimal timestamp prefix generated
;;       by $$EPOCH
;;     - A random Base36 suffix of configurable length
;;
;;   This structure preserves time ordering while reducing the
;;   likelihood of collisions between generated IDs.
;;
;; Scope
;;   Reads  : $$EPOCH, $RANDOM
;;   Writes : CHARSET, RAND, I, POS, RLEN (local variables)
;;
;; Parameters
;;   RLEN   : (IN)
;;             Length of the random Base36 suffix.
;;             Defaults to 7 if not supplied.
;;
;; Returns  :
;;   A lowercase time-sorted identifier in the format:
;;
;;     [Hex Timestamp(13)][Random Base36 Suffix(RLEN)]
;;
;;   Example:
;;     66a1f4c9d2e7b4k9x2qa
;;
;; Notice   :
;;   - Uses Base36 characters: 0-9 and a-z.
;;   - Timestamp ordering allows IDs to sort chronologically.
;;   - Collision probability depends on suffix length and
;;     generation frequency within the same microsecond.
;;   - Requires the EPOCH function to be available in the
;;     same routine or namespace.
;;
;;------------------------------------------------------------------ 
    NEW CHARSET,RAND,I,POS
    
    ; Ensure RLEN has a value; default to 7 if not provided
    SET RLEN=$G(RLEN,7)    
        
    ; Define the character set for the random suffix (Base36: 0-9, a-z)
    SET CHARSET="0123456789abcdefghijklmnopqrstuvwxyz"
    SET RAND=""
    
    ; Generate the random portion of the ID
    ; This provides the entropy needed to prevent collisions in the same ms
    FOR I=1:1:RLEN DO
    . SET POS=$RANDOM($L(CHARSET))+1
    . SET RAND=RAND_$E(CHARSET,POS)
    
    ; Concatenate and return the result in lowercase
    ; The result is [Timestamp(13)][Random(RLEN)]
    QUIT $$EPOCH_RAND
L3 ResolveByUnique^apiRslv
ResolveByUnique(ResolvedRec) ; resolves target entity via a unique attribute optionally using sys.attr.resolvedby. It finds or it creates EID
;;--------------------------------------------------------------------------------------------------------------
;; Function : ResolveByUnique^apiRslv
;;
;; Purpose  : ResolveTargeEntity/Path 2 — resolve target entity via a unique attribute optionally using sys.attr.resolvedby. 
;;            It finds or it creates EID. Dispatches to the correct resolution sub-path based
;;            on the count of unique attributes present in ResolvedRec and the presence of sys.attr.resolvedby.
;;
;; Parameters
;;   ResolvedRec : (IN/OUT)  Resolved record — sys.attr.resolvedby node if present is killed
;;
;; Returns  	 :
;;   				eid — resolved or generated entity ID
;;   				0   — any resolution or validation failure
;;----------------------------------------------------------------------------------------------------------------
    NEW count		; number of unique attributes present in ResolvedRec
    NEW firstAid	; aid of the sole unique attribute, when count=1
    NEW eid			; resolved or generated entity ID
    

    SET eid=0 		; initialize eid, assume resolve failed
    DO CountUniqueAttributes(.ResolvedRec,.count,.firstAid)

    ; resolvedby attribute present but no unique attributes — error
    IF count=0,$DATA(ResolvedRec("+",RESOLVEDBYID)) DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveByUnique: sys.attr.resolvedby specified but no unique attributes were supplied in the transaction record.")

    ; No unique attributes and no resolvedby attribute — anonymous entity
    IF count=0 DO  QUIT eid
    . SET eid=$$GENID^utils
    . DO LOGWARNING^logger("apiRslv","ResolveByUnique: generated EID "_eid)

    ; Exactly one unique attribute — pass firstAid directly
    IF count=1 QUIT $$ResolveOwnership(firstAid,.ResolvedRec)

    ; More than one unique attribute — resolvedby required
    IF '$DATA(ResolvedRec("+",RESOLVEDBYID)) DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveByUnique: multiple unique attributes require sys.attr.resolvedby")

	SET eid=$$ResolveDesignatedUnique(.ResolvedRec)

    ; remove sys.attr.resolvedby ResolvedRec node — it must not reach DatomAssert as entity fact
    IF $DATA(ResolvedRec("+",RESOLVEDBYID)) DO
    . KILL ResolvedRec("+",RESOLVEDBYID) 

    QUIT eid
L4 CountUniqueAttributes^apiRslv
CountUniqueAttributes(ResolvedRec,count,firstAid)
;;------------------------------------------------------------------
;; Procedure : CountUniqueAttributes^apiRslv
;; Call      : DO CountUniqueAttributes^apiRslv(.ResolvedRec,.count,.firstAid)
;;
;; Purpose   : Scans ResolvedRec in a single pass and counts how many
;;             assertion attributes carry a uniqueness constraint
;;             (UNQINSERT or UNQUPSERT). Returns the count and the
;;             first unique aid found.
;;
;;             This routine only counts — it does not resolve, lookup,
;;             stage, or validate. It exists solely to inform
;;             ResolveByUnique() which resolution path to take.
;;
;; Parameters
;;   ResolvedRec : (IN)  Resolved record — assertions only
;;   count       : (OUT) Number of unique attributes found
;;   firstAid    : (OUT) Aid of first unique attribute found
;;                       "" if count=0
;;
;; Returns  : Nothing (procedure)
;;------------------------------------------------------------------
    NEW aid
    SET count=0
    SET firstAid=""
    SET aid=""
    FOR  SET aid=$ORDER(ResolvedRec("+",aid)) QUIT:aid=""  DO
    . IF $$GetUniqueMode^sapiGet(aid) DO
    . . SET count=count+1
    . . IF firstAid="" SET firstAid=aid
    QUIT
L5 GetUniqueMode^sapiGet
GetUniqueMode(aid)
;;------------------------------------------------------------------
;; Function : GetUniqueMode^sapiGet
;; Call     : SET mode=$$GetUniqueMode^sapiGet(aid)
;;
;; Purpose  : Returns the uniqueness constraint mode for a given
;;            attribute as a schema enumeration EID.
;;
;;            Replaces the pair $$IsUniqueInsert/$$IsUniqueUpsert
;;            with a single function that returns the mode directly,
;;            allowing callers to compare against schema EIDs rather
;;            than invented string constants.
;;
;; Parameters
;;   aid    : (IN) Attribute entity ID
;;
;; Returns  :
;;   UNQINSERTID — attribute enforces insert-unique (:db.unique/value)
;;   UNQUPSERTID — attribute enforces upsert-unique (:db.unique/identity)
;;   0           — attribute has no uniqueness constraint
;;------------------------------------------------------------------
    IF ''$DATA(^TBAVET(UNIQUEID,INSERTKEY,aid)) QUIT UNQINSERTID
    IF ''$DATA(^TBAVET(UNIQUEID,UPSERTKEY,aid)) QUIT UNQUPSERTID
    QUIT 0
L4 ResolveDesignatedUnique^apiRslv
ResolveDesignatedUnique(ResolvedRec) ; resolve target entity via caller-specified unique attribute (sys.attr.resolvedby)
;;------------------------------------------------------------------
;; Function : $$ResolveDesignatedUnique^apiRslv
;;
;; Purpose  : Resolves the target entity when multiple unique
;;            attributes are present and the caller has supplied
;;            sys.attr.resolvedby to identify which one to use.
;;
;;            Performs four validations before delegating to
;;            ResolveOwnership:
;;
;;              1. Attribute key exists in schema
;;              2. Attribute is unique (UNQINSERT or UNQUPSERT)
;;              3. Attribute is present in ResolvedRec
;;              4. Attribute value is non-empty
;;
;;            Only if all four pass does it call ResolveOwnership.
;;
;; Parameters
;;   ResolvedRec : (IN)  Resolved record — sys.attr.resolvedby already
;;                       present; read here, killed by the caller
;;                       (ResolveByUnique) once this returns
;;
;; Returns  :
;;   eid — successfully resolved or generated
;;   0   — any validation failure or UNQINSERT violation
;;------------------------------------------------------------------
    NEW attrkey	; sys.attr.resolvedby value — schema key naming the attribute to resolve by
    NEW aid		; resolved attribute ID for attrkey
    NEW val		; value of that attribute in ResolvedRec
    NEW eid		; resolved or generated entity ID

    SET eid=0
    SET attrkey=$GET(ResolvedRec("+",RESOLVEDBYID))

    ; Validation 1 — attribute key must exist in schema
    SET aid=$$GetEID^sapiGet(attrkey)
    IF aid<1 DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveDesignatedUnique: resolvedby attribute is not registered in the schema — "_attrkey)

    ; Validation 2 — attribute must be unique
    IF '$$GetUniqueMode^sapiGet(aid) DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveDesignatedUnique: resolvedby attribute is not unique — "_attrkey)

    ; Validation 3 — attribute must be present in record
    IF '$DATA(ResolvedRec("+",aid)) DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveDesignatedUnique: resolvedby attribute is missing from record — "_attrkey)

    ; Validation 4 — value must be non-empty
    SET val=$GET(ResolvedRec("+",aid))
    IF val="" DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveDesignatedUnique: resolvedby attribute has an empty value — "_attrkey)

    QUIT $$ResolveOwnership(aid,.ResolvedRec)
L5 ResolveOwnership^apiRslv
ResolveOwnership(aid,ResolvedRec) ; resolve target entity for one unique attribute via UNQINSERT/UNQUPSERT semantics
;;------------------------------------------------------------------------------------------------------------------
;; Function : $$ResolveOwnership^apiRslv
;;
;; Purpose  : Resolves the target entity EID for a single unique
;;            attribute by applying UNQINSERT or UNQUPSERT semantics.
;;
;; Parameters
;;   aid         : (IN)  Attribute entity ID (must be unique)
;;   ResolvedRec : (IN)  Resolved record
;;
;; Returns  :
;;   			eid — successfully resolved or generated
;;   			0   — empty value (internal error) or UNQINSERT violation
;;------------------------------------------------------------------
    NEW val			; value supplied for this unique attribute    
    NEW mode		; UNQINSERT or UNQUPSERT
    
    NEW ownereid	; eid currently holding valkey, if any (may be stale)
    NEW ownerIsLive	; whether ownereid's claim on valkey is still active
    ;
    ; ownereid/ownerIsLive: ^AVET keeps a subscript for every eid that ever claimed valkey,
    ; even a dead one — e.g. F23: Carol claims personal@example.com, retracts it, then Erin
    ; claims the same value. Both Carol's and Erin's eids now sit under the same (aid,valkey),
    ; in no guaranteed order, but only Erin's claim is live. Taking whichever eid $ORDER finds
    ; first would risk picking Carol — ResolveLiveEID walks all candidates and returns the one
    ; whose claim is still live (0 if none is), so ownereid is always the true current owner,
    ; not just the first subscript. This distinction changes the outcome by mode:
    ;   - UNQINSERT: no live owner means the value is free again — allow a new entity instead
    ;     of rejecting forever.
    ;   - UNQUPSERT: no live owner means the prior identity released this value — attach a new
    ;     eid instead of silently merging into a dead identity
    ;
    NEW eid			; resolved or generated entity ID

    SET eid=0
    SET val=$GET(ResolvedRec("+",aid))
    SET mode=$$GetUniqueMode^sapiGet(aid)

    IF val="" DO  QUIT 0
    . DO LOGERROR^logger("apiRslv","ResolveOwnership: internal error — empty value for unique attribute ("_aid_")")
    
	IF $$IsRangeValue^sapiVld(aid,val) DO
    . SET ownereid=$$ResolveLiveEID(aid,val)
    . SET ownerIsLive=(ownereid'=0)
    .
    . ; UNQINSERT — strict insert, never merges. Violation only while owner is still live.
    . IF mode=UNQINSERTID,ownerIsLive DO  QUIT
    . . SET eid=0
    . . DO LOGERROR^logger("apiRslv","ResolveOwnership: insert-unique violation — ("_aid_","_val_") already owned by ("_ownereid_")")
    .
    . ; UNQINSERT, no live owner — value is free again, treat as a brand-new entity.
    . IF mode=UNQINSERTID,'ownerIsLive DO  QUIT
    . . SET eid=$$GENID^utils
    . . DO LOGDEBUG^logger("apiRslv","ResolveOwnership: insert-unique — value released or new, generated EID "_eid)
    .
    . ; UNQUPSERT, owner still live — this claimant IS that same real-world identity.
    . IF mode=UNQUPSERTID,ownerIsLive DO  QUIT
    . . SET eid=ownereid
    . . DO LOGDEBUG^logger("apiRslv","ResolveOwnership: upsert-unique merge → existing EID ("_eid_")")
    .
    . ; UNQUPSERT, no live owner — a new claimant here is a DIFFERENT identity, so mint
    . ; a fresh eid instead of merging into the dead owner.
    . IF mode=UNQUPSERTID,'ownerIsLive DO  QUIT
    . . SET eid=$$GENID^utils
    . . DO LOGDEBUG^logger("apiRslv","ResolveOwnership: upsert-unique — prior identity released, generated new EID "_eid)

    ; val has never been registered at all — new entity, regardless of mode.
    ELSE  DO
    . SET eid=$$GENID^utils
    . DO LOGDEBUG^logger("apiRslv","ResolveOwnership: new entity — generated EID "_eid)

    QUIT eid

L2 IsEntityRegistered^apiVld

IsEntityRegistered(eid) ; check whether entity is registered in ^ABE
;;-------------------------------------------------------------------------
;; Function : IsEntityRegistered^apiVld
;;
;; Call     : WRITE $$IsEntityRegistered^apiVld(eid)
;;
;; Purpose  : Determine whether an ABox entity is registered in ^ABE.
;;
;; Parameters
;;   eid    : (IN) Entity identifier.
;;
;; Scope
;;   Reads  : ^ABE(eid,1)
;;
;; Returns  : 1 if entity is registered in ^ABE, otherwise 0.
;;
;; Notes    : registration is indicated by the existence of the node, not by a non-empty value e.g.
;;			  ^ABE("65825611ba143blkjysi",1)=""
;;			  ^ABE("65825611ba2aemmuqtyi",1)=""
;;
;;	''$DATA(...) double-negation canonicalize $DATA's four-valued return into a strict 0/1 boolean.
;;---------------------------------------------------------------------------    
    QUIT ''$DATA(^ABE(eid,1))

L2 ValidateRecord^apiVld

ValidateRecord(ResolvedRec,ReqAttr,AttrCache,isNewEntity)
;;------------------------------------------------------------------
;; Function : ValidateRecord^apiVld
;; Call     : SET ok=$$ValidateRecord^apiVld(.ResolvedRec,.ReqAttr,.AttrCache,isNewEntity)
;;
;; Purpose  : Validates entity facts in ResolvedRec after control
;;            attributes have been consumed by ResolveTargetEntity.
;;            At this point ResolvedRec contains ONLY entity facts —
;;            no sys.attr.id, sys.attr.key, or sys.attr.resolveby.
;;
;;            Two checks are performed:
;;
;;            CHECK 1 — Empty values
;;              Every assertion must have a non-empty value.
;;              Null subscripts crash ^TBDR and ^EATV globals on YottaDB.
;;              Applied unconditionally — insert or update.
;;
;;            CHECK 2 — Required attributes
;;              Every attribute marked as required in the schema must
;;              be present in ResolvedRec. Applied ONLY when
;;              isNewEntity is true — i.e. only when creating a brand
;;              new entity. Updates to an existing entity may
;;              legitimately assert only a subset of its attributes;
;;              the required-attribute set was already satisfied when
;;              the entity was first created, and re-demanding every
;;              required attribute on every subsequent update would
;;              make partial updates impossible.
;;
;;              When applied, this check is further scoped to records
;;              that contain at least one attribute from the same
;;              sub-namespace as the required attributes — prevents
;;              cross-namespace false failures e.g. a person.* record
;;              must not be checked against sandbox.movie.* required
;;              attributes. Namespace membership is resolved via
;;              AttrCache(aid) reverse map — O(1) lookup, no schema
;;              traversal per attribute.
;;
;;            Both checks concern the entity itself — not the
;;            transaction. This is Phase 2 validation, distinct from
;;            the transaction validation performed inside
;;            ResolveTargetEntity (Phase 1).
;;
;; Parameters
;;   ResolvedRec : (IN) Resolved record — entity facts only
;;   ReqAttr     : (IN) Required attributes map — built by
;;                      BuildAttrsRequired^sapi
;;                      ReqAttr      = total count of required attrs
;;                      ReqAttr(aid) = attrkey string
;;   AttrCache   : (IN) Schema attribute cache — built by
;;                      BuildAttrsCache^sapi
;;                      AttrCache(fullkey) = aid  (forward map)
;;                      AttrCache(aid)     = sub-namespace (reverse map)
;;   isNewEntity : (IN) 1 if this record is creating a brand new
;;                      entity (eid did not previously exist in
;;                      ^EATV), 0 if updating an existing entity.
;;                      CHECK 2 is skipped entirely when 0.
;;                      Defaults to 1 if omitted — preserves strict
;;                      create-time validation for direct callers
;;                      (e.g. standalone tests) that don't supply it.
;;
;; Returns  :
;;   1 — all checks passed
;;   0 — any check failed (error logged)
;;------------------------------------------------------------------
    NEW aid,ok,missingCount,reqns,hasNamespaceAttr
    SET ok=1
    SET isNewEntity=$GET(isNewEntity,1)

    ; ------------------------------------------------------------------
    ; CHECK 1 — Empty values
    ; Null subscripts crash ^TBDR and ^EATV globals on YottaDB.
    ; Check assertions only — retractions allow empty val for
    ; cardinality-one (DatomRetract handles that case itself).
    ; Applied unconditionally regardless of isNewEntity.
    ; ------------------------------------------------------------------
    ; Iterate all assertions — fail fast on first empty value
    SET aid=""    
    FOR  SET aid=$ORDER(ResolvedRec("+",aid)) QUIT:aid=""!'ok  DO
    . IF ResolvedRec("+",aid)="" SET ok=0 DO LOGERROR^logger("apiVld","ValidateRecord: empty value for aid "_aid_" — aborting")
    IF 'ok QUIT 0

	; ------------------------------------------------------------
    ; CHECK 2 — Attribute predicates
    ; ------------------------------------------------------------
    IF '$$ValidateAttrPredicates(.ResolvedRec) QUIT 0

    ; ------------------------------------------------------------------
    ; CHECK 3 — Required attributes
    ; Skipped entirely for updates to an existing entity.
    ; ------------------------------------------------------------------

    ; Short-circuit — no required attrs defined for this namespace
    IF ReqAttr=0 QUIT ok

    ; Short-circuit — updating an existing entity, not creating one
    IF 'isNewEntity QUIT ok

    ; Determine the required sub-namespace from the first ReqAttr entry
    SET aid=$ORDER(ReqAttr(""))
    SET reqns=$PIECE($GET(ReqAttr(aid)),".",1,2)

    ; Check whether the record contains any attribute from the required namespace
    ; Uses AttrCache reverse map — AttrCache(aid) = sub-namespace
    SET hasNamespaceAttr=0
    SET aid=""
    FOR  SET aid=$ORDER(ResolvedRec("+",aid)) QUIT:aid=""!hasNamespaceAttr  DO
    . IF $GET(AttrCache(aid))=reqns SET hasNamespaceAttr=1

    ; Skip required attribute check — record belongs to a different namespace
    IF 'hasNamespaceAttr QUIT ok

    ; Iterate required attributes and verify each is present in the record
    SET missingCount=0
    SET aid=""
    FOR  SET aid=$ORDER(ReqAttr(aid)) QUIT:aid=""  DO
    . IF '$DATA(ResolvedRec("+",aid)) DO
    . . SET missingCount=missingCount+1
    . . DO LOGERROR^logger("apiVld","ValidateRecord: Record is missing required attribute "_$GET(ReqAttr(aid),aid))

    IF missingCount>0 DO
    . SET ok=0
    . DO LOGERROR^logger("apiVld","ValidateRecord: Record is missing "_missingCount_" required attribute(s)")

    QUIT ok
L3 ValidateAttrPredicates^apiVld
ValidateAttrPredicates(ResolvedRec)
; ------------------------------------------------------------
; Validate schema-defined attribute predicates.
; Returns:
;   1 = all predicates passed
;   0 = one or more predicates failed
; ------------------------------------------------------------

    NEW aid,val,predicate,ok

    SET ok=1
    SET aid=""

	; Iterate every asserted attribute (aid) on this record. For
    ; each one, look up whether the schema defines a validation
    ; predicate for that attribute; if it does, run the predicate
    ; against the attribute's staged value. Stops at the first
    ; failing predicate — remaining attributes are not checked
    FOR  SET aid=$ORDER(ResolvedRec("+",aid)) QUIT:aid=""!'ok  DO
    . NEW predicate,val
    . ; Retrieve the schema predicate for this attribute.
    . SET predicate=$$GetAttrValidFn^sapiGet(aid)
    . ; No predicate defined for this attribute — nothing to enforce.
    . IF predicate="" QUIT
    . SET val=ResolvedRec("+",aid)
    . IF '$$ValidateAttrPredVal(predicate,val) DO
    . . SET ok=0
    . . DO LOGERROR^logger("apiVld","ValidateAttrPredicates: validation failed for aid="_aid_", value="""_val_""", predicate="_predicate)

    QUIT ok
L4 GetAttrValidFn^sapiGet
GetAttrValidFn(aid)
;;------------------------------------------------------------------
;; Function : GetAttrValidFn^sapiGet
;; Call     : SET fn=$$GetAttrValidFn^sapiGet(aid)
;;
;; Purpose  : Returns the validation predicate associated with an
;;            attribute, or "" if none has been defined.
;;
;; ^TBEAVT structure: ^TBEAVT(entity,attribute,valuekey,txn)=1
;; entity=aid, attribute=VALIDFNID — direct EAVT lookup, no scan.
;;
;; Returns  :
;;   predicate string (e.g. "$$IsInt^utils")
;;   "" if no validation predicate exists
;;------------------------------------------------------------------
    NEW vk

    IF '$DATA(VALIDFNID) QUIT ""
    IF VALIDFNID'>0 QUIT ""

    SET vk=$ORDER(^TBEAVT(aid,VALIDFNID,""))

    IF vk="" QUIT ""

    QUIT $$GetDictValue(VALIDFNID,vk)
L4 ValidateAttrPredVal^apiVld
ValidateAttrPredVal(predicate,val)
; ------------------------------------------------------------
; Procedure : ValidateAttrPredVal^apiVld
;
; Purpose   : Executes schema-defined validation predicate
;             against a single attribute value.
;
; predicate : fully-qualified predicate, e.g. "$$IsInt^utils"
;             (leading "$$" optional — stripped if present)
; val       : the attribute value to validate
;
; NOTE: Indirect call syntax ($$@fn(val), $$@call) does not work
;
; Returns   : 1 if predicate returned true, 0 otherwise
; ------------------------------------------------------------
    NEW result,safeVal

    SET result=0
    IF predicate="" QUIT 1

    ; Escape embedded quotes so val cannot terminate the string
    ; literal early and inject code into the executed command.
    SET safeVal=$$EscapeQuotes(val)

    XECUTE "SET result="_predicate_"("""_safeVal_""")"

    QUIT result

L2 DatomAssert^apiWFL

DatomAssert(eid,aid,val) ; dispatch to the single- or multi-value staging branch by attribute cardinality
;;----------------------------------------------------------------------------------------------
;; Function  : DatomAssert^apiWFL
;;
;; Purpose   : Dispatches to DatomAssertSingle or DatomAssertMulti based on the attribute's
;;             cardinality. Assumes %ABR has already been initialised by Stage.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID of the assertion predicate
;;   val     : (IN) Literal or reference value (can be multi-value "val1|val2" when cardinality-many)
;;
;; Returns
;;   0       : triplet assertion failed
;;   1       : triplet staged successfully
;;   2       : no-op — value already current, nothing staged (cardinality-one path only)
;;----------------------------------------------------------------------------------------------  
    IF $$IsMultiValue^sapiVld(aid) QUIT $$DatomAssertMulti(eid,aid,val)
    
    QUIT $$DatomAssertSingle(eid,aid,val)
L3 IsMultiValue^sapiVld
IsMultiValue(aid)
;;------------------------------------------------------------------
;; Function : IsMultiValue^sapiVld
;; Call     : IF $$IsMultiValue^sapiVld(aid)
;;
;; Purpose  : To support delimited multi-value expansion using "|" for attributes flagged as multi-valued. (see ^api routines)
;;
;;
;; Returns  :
;;   1 — multi-valued attribute  (cardinality many)
;;   0 — single-valued attribute (any other cardinality)
;;
;; Notes    : 
;;			: ''$DATA(...) double-negation canonicalize $DATA's four-valued return into a strict 0/1 boolean.
;;------------------------------------------------------------------
    NEW isMulti
    SET isMulti=''$DATA(^TBAVET(CARDINID,MANYKEY,aid))
    ;IF 'isMulti DO LOGDEBUG^logger("sapiVld","IsMultiValue: not multi-valued — aid="_aid)
    QUIT isMulti
L3 DatomAssertMulti^apiWFL
DatomAssertMulti(eid,aid,val) ; recurse per "|" token, then stage each via DatomStageMulti
;;----------------------------------------------------------------------------------------------
;; Function  : DatomAssertMulti^apiWFL
;;
;; Purpose   : Splits "|"-delimited val into independent DatomAssertMulti calls, or — for a
;;             single token — delegates staging to DatomStageMulti with op=1.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID of the assertion predicate — must be cardinality-many
;;   val     : (IN) Literal or reference value (can be multi-value "val1|val2")
;;
;; Returns
;;   1       : triplet(s) staged successfully
;;   2       : no-op — single token only, value already active, nothing staged
;;   0       : one or more triplets failed resolution/staging
;;----------------------------------------------------------------------------------------------
    NEW isAllOk   ; overall success flag returned to caller — cleared if any recursive/staging step fails
    NEW pIdx      ; 1-based piece index while splitting val on "|"
    NEW pieceVal  ; current "|"-delimited token during multi-value recursion
    NEW result    ; DatomStageMulti's return for this single token

    SET isAllOk=1

    ; Delimiter present — recurse per "|"-delimited token as an independent datom insertion.
    IF val["|" DO  QUIT isAllOk
    . FOR pIdx=1:1 SET pieceVal=$PIECE(val,"|",pIdx) QUIT:pieceVal=""!'isAllOk  DO
    . . IF '$$DatomAssertMulti(eid,aid,pieceVal) SET isAllOk=0

    SET result=$$DatomStageMulti(eid,aid,val,1)
    IF result=0 QUIT 0
    IF (result=1)&(aid=ALIASID) DO AddKeyword(eid,val) ; add alias as a keyword so it can be used in ydb console
    QUIT result
L4 DatomStageMulti^apiWFL
DatomStageMulti(eid,aid,val,op) ; stage a cardinality-many assert or retract for (eid,aid,val)
;;----------------------------------------------------------------------------------------------
;; Function  : DatomStageMulti^apiWFL
;;
;; Purpose   : Resolves val to a valkey. If newly allocated, it has no history in ^EATV, so
;;             assert creates it directly and retract fails directly — no scan needed. If the
;;             valkey already existed, walks ^EATV(eid,aid,*) from the most recent transaction
;;             backward until this valkey is found, and stages the requested polarity into %ABR
;;             based on its recorded state relative to the requested op.
;;
;;             Algorithm:
;;               1. Resolve val → valkey.
;;               2. NEW VALKEY (isNewValKey=1 — never existed before this call, no ^EATV history
;;                  is possible)
;;                    assert  → stage op=1                          CREATE
;;                    retract → fail, nothing to retract            FAIL
;;               3. EXISTING VALKEY (isNewValKey=0) — scan transactions for (eid,aid)
;;                  newest-to-oldest; stop at the first one where valkey is recorded — that op
;;                  IS the valkey's current state, since any more recent write would already
;;                  have been seen.
;;               4.   NOT FOUND (valkey has no recorded op in ^EATV despite being a known key)
;;                      assert  → stage op=1                        CREATE
;;                      retract → fail, nothing to retract          FAIL
;;               5.   FOUND, recorded op equals requested op
;;                      → no-op, nothing staged                     NO-OP
;;               6.   FOUND, recorded op differs from requested op
;;                      → stage requested op                        FLIP
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID — must be cardinality-many
;;   val     : (IN) Single value to assert/retract — must not contain "|"
;;   op      : (IN) 1 to assert, 0 to retract
;;
;; Returns
;;   1       : datom staged (CREATE or FLIP path)
;;   2       : no-op — recorded state already matches requested op
;;   0       : failed — value resolution failed, or valkey never recorded when retracting
;;
;; Scope
;;   Reads   : ^EATV
;;
;; Notes
;;   Branch 4 (existing valkey, not found in ^EATV) is kept defensively — pending confirmation
;;   of whether valkey registration can ever occur without a prior ^EATV write for it. If not,
;;   isNewValKey=0 always implies found=1 and branch 4 is unreachable.
;;----------------------------------------------------------------------------------------------
    NEW valkey       ; resolved value-type key for val
    NEW isNewValKey  ; resolution result — 1 new valkey allocated, 0 existing valkey reused, <0 resolution failure
    NEW curtx        ; tx cursor, walking ^EATV(eid,aid,*) from most recent transaction backward
    NEW found        ; 1 once valkey has been located at some transaction — stops the scan
    NEW recop        ; op recorded at the transaction where valkey was found; meaningful only if found=1

    SET valkey=""

    ; Resolve val into valkey. Reference-typed attributes resolve/register via ResolveAttrRef,
    ; everything else registers as a literal via RegisterAttrVal.
    IF $$IsRefValType^sapiVld(aid) DO
    . SET isNewValKey=$$ResolveAttrRef^sapiRslv(aid,val,.valkey,"ABox")
    ELSE  DO
    . SET isNewValKey=$$RegisterAttrVal^sapiRslv(aid,val,.valkey)

    ; Resolution failure (-1) must abort immediately — the resolver has already logged the cause.
    IF isNewValKey<0 DO  QUIT 0
    . DO LOGERROR^logger("apiWFL","DatomStageMulti: aborting — value resolution failed for (eid="_eid_",aid="_aid_",val="_val_")")

    ; New valkey, retract — never existed, nothing to retract, fail. No scan needed.
    IF isNewValKey&('op) DO  QUIT 0
    . DO LOGDEBUG^logger("apiWFL","DatomStageMulti: failed — ("_eid_","_aid_","_valkey_") is a new valkey, nothing to retract")

    ; New valkey, assert — create directly. No scan needed.
    IF isNewValKey&(op=1) DO  QUIT 1
    . SET %ABR(eid,aid,valkey)=1
    . DO LOGDEBUG^logger("apiWFL","DatomStageMulti: create staged ("_eid_","_aid_","_valkey_") — new valkey")

    ; Existing valkey — walk transactions for (eid,aid) newest-to-oldest. The first transaction
    ; where this valkey is recorded IS its current state — any more recent write would already
    ; have been visited first. Stop as soon as it's found.
    SET found=0
    SET recop=0
    SET curtx=$ORDER(^EATV(eid,aid,""),-1)
    FOR  QUIT:(curtx="")!found  DO
    . IF $$IsDatomRecorded^apiVld(eid,aid,curtx,valkey) DO
    . . SET recop=^EATV(eid,aid,curtx,valkey)
    . . SET found=1
    . ELSE  SET curtx=$ORDER(^EATV(eid,aid,curtx),-1) ; step backwards to previous transaction

    ; Existing valkey, not found in ^EATV, retract — fail
    IF 'found&('op) DO  QUIT 0
    . DO LOGDEBUG^logger("apiWFL","DatomStageMulti: failed — ("_eid_","_aid_","_valkey_") never recorded")

    ; Existing valkey, not found in ^EATV, assert — create
    IF 'found&(op=1) DO  QUIT 1
    . SET %ABR(eid,aid,valkey)=1
    . DO LOGDEBUG^logger("apiWFL","DatomStageMulti: create staged ("_eid_","_aid_","_valkey_")")

    ; Found, recorded state already matches requested op — no-op
    IF recop=op DO  QUIT 2
    . DO LOGDEBUG^logger("apiWFL","DatomStageMulti: no-op — ("_eid_","_aid_","_valkey_") already op="_op)

    ; Found, recorded state differs from requested op — flip it
    SET %ABR(eid,aid,valkey)=op
    DO LOGDEBUG^logger("apiWFL","DatomStageMulti: staged ("_eid_","_aid_","_valkey_")="_op)
    QUIT 1
L5 IsRefValType^sapiVld
IsRefValType(aid)
;;------------------------------------------------------------------
;; Function : IsRefValType^sapiVld
;; Call     : IF $$IsRefValType^sapiVld(aid)
;;
;; Purpose  : Returns 1 if the metadata attribute is reference-typed, 0 otherwise. 
;;            A reference data type stores entity IDs as values 
;;
;;
;; Returns  :
;;   1 — reference data type (values are entity IDs)
;;   0 — literal type   	 (values are raw strings)
;;
;; Notes    :
;;			''$DATA(...) double-negation canonicalize $DATA's four-valued return into a strict 0/1 boolean.
;;------------------------------------------------------------------
    NEW isRef
    SET isRef=''$DATA(^TBAVET(RANGEID,REFKEY,aid))
    ;IF 'isRef DO LOGDEBUG^logger("sapiVld","IsRefValType: not reference-typed — aid="_aid)
    QUIT isRef
L5 ResolveAttrRef^sapiRslv
ResolveAttrRef(aid,refeid,valkey,modelType) ; resolve the value key for an ABox reference attribute value
;;----------------------------------------------------------------------------------------------------
;; Function : ResolveAttrRef^sapiRslv
;;
;; Call     : SET isNewValKey=$$ResolveAttrRef^apiRslv(aid,refeid,.valkey)
;;
;; Usage    : ABox counterpart to ResolveAttrRef^sapiRslv. Called by
;;            DatomAssert^apiWFL and DatomRetract^apiWFL, ONLY for
;;            attributes where IsRefValType^sapiVld(aid) is true.
;;
;; Purpose  : Resolves the lookup key (valkey) for a reference-typed
;;            attribute (aid). refeid may arrive as any of three forms,
;;            each resolved by its own function below:
;;              • isa attribute value (label or eid) -> ResolveIsaRef
;;              • an ordinary key label              -> ResolveKeyLabel
;;              • an already-resolved EID            -> ResolveRawEid
;;
;; Parameters
;;   aid    	: (IN)  Reference-typed attribute entity ID
;;   refeid 	: (IN)  ABox entity ID in one of the three forms above
;;	 modelType	: (IN)  Model layer containing the entity "ABox" or "TBox"
;;   valkey 	: (OUT) Lookup value key for refeid within aid's data type
;;
;; Returns  :
;;   1  — new value key allocated
;;   0  — existing value key reused
;;  -1  — cannot be resolved; valkey is left ""
;;----------------------------------------------------------------------------------------------------
	NEW result	
	SET valkey=""       ; final value if resolution fails

	; refeid is handled by exactly one of the two branches below:
	;   isa value  -> ResolveIsaRef (label or raw TBox eid, registered directly)
	;   key label  -> resolved to an eid via GetEID^apiGet
	;   raw EID    -> validated directly (format, then existence)
	; every branch either registers the value (success) or sets result=RFailed;
	; valkey is only ever populated by RegisterAttrVal^sapiRslv, so it stays "" on every failure path.
	IF aid=ISAID SET result=$$ResolveIsaRef(aid,refeid,.valkey)
	ELSE  IF $$IsRangeValue^sapiVld(AKEYID,refeid) SET result=$$ResolveKeyLabel(aid,refeid,.valkey,modelType)
	ELSE  SET result=$$ResolveRawEid(aid,refeid,.valkey,modelType)

	QUIT result
== L6 ResolveIsaRef^sapiRslv
ResolveIsaRef(aid,refeid,valkey) ; resolve isa attribute values (key label or raw eid) against the TBox entity id
;;------------------------------------------------------------------
;; Function : ResolveIsaRef^sapiRslv
;;
;; Call     : SET result=$$ResolveIsaRef^sapiRslv(aid,refeid,.valkey)
;;
;; Usage    : Private helper for ResolveAttrRef^sapiRslv; not called elsewhere.
;;
;; Purpose  : The isa attribute (aid=ISAID) always takes a TBox type as its value,
;;            arriving as either a key label (e.g. "sys.type.obj") or an
;;            already-resolved eid (e.g. 900). GetEID^sapiGet normalizes either
;;            form to an eid; the eid is then registered directly against
;;            ^TBDR(ISAID,*) rather than going through ResolveRawEid/ResolveKeyLabel,
;;            since isa values are TBox dictionary literals, not ABox hash references.
;;
;; Parameters
;;   aid    : (IN)  ISAID (299) — passed through for symmetry/logging only
;;   refeid : (IN)  TBox type as a key label or raw eid (e.g. "sys.type.obj", 1013)
;;   valkey : (OUT) 1 new / 0 existing value key, or -1 on failure
;;
;; Notes    : valkey (by reference) is populated by RegisterAttrVal^sapiRslv
;;------------------------------------------------------------------
	NEW eid,result,msg
	w "ResolveIsaRef: resolving isa value '"_refeid_"' for aid="_aid,!
	SET eid=$$GetEID^sapiGet(refeid)

	IF eid="" DO  QUIT -1
	. DO LOGERROR^logger("sapiRslv","ResolveIsaRef: could not resolve '"_refeid_"' to an eid — aid="_aid)

	; Defensive: eid must actually be a registered TBox entity, otherwise a
	; typo'd/unresolvable isa value would silently mint a bogus new dictionary
	; entry instead of failing loudly.
	IF '$$IsEntityRegistered^sapiVld(eid) DO  QUIT -1
	. DO LOGERROR^logger("sapiRslv","ResolveIsaRef: refeid '"_refeid_"' (eid="_eid_") is not a known TBox entity — aid="_aid)

	SET msg="ResolveIsaRef: registering isa value eid="_eid_" (from refeid="_refeid_") for aid="_aid
	DO LOGDEBUG^logger("sapiRslv",msg)
	SET result=$$RegisterAttrVal^sapiRslv(aid,eid,.valkey)
	QUIT result
== L6 IsRangeValue^sapiRslv
IsRangeValue(aid,val) ; check if val (literal or reference) is already registered in aid's data type
;;------------------------------------------------------------------
;; Function : IsRangeValue^sapiVld
;;
;; Call     : IF $$IsRangeValue^sapiVld(aid,val)
;;
;; Usage    : Called by IsNewRangeValue to test literal (non-reference) values before insertion
;;
;; Purpose  : Returns 1 if the candidate value (val) is already registered in the data type of attribute (aid)
;;
;; Parameters
;;   aid  : (IN) Attribute entity ID identifying the data type being checked
;;   val  : (IN) Candidate value being checked for existing registration
;;
;; Scope
;;   Reads  : ^TBDR(aid,val)
;;
;; Returns  :
;;   1 : val is already registered
;;   0 : val is not yet registered
;;
;; Notes    : Used by IsNewRangeValue for literal data types only.
;;            ResolveAV handles reference value registration independently in CASE 3.
;;------------------------------------------------------------------
    NEW isReg ; holds the registered/not-registered result returned to caller

    ; val is registered if a ^TBDR node exists for it under this attribute
    SET isReg=($GET(^TBDR(aid,val))'="")

    ; Debug trace, left commented, for tracing why a value was found unregistered
    ; IF 'isReg DO LOGDEBUG^logger("sapiVld","IsRangeValue: not registered — aid="_aid_", val="_val)

    QUIT isReg
== L6 ResolveKeyLabel^sapiRslv
ResolveKeyLabel(aid,refeid,valkey,modelType) ; resolve an ordinary key label to an eid and register
;;------------------------------------------------------------------
;; Function : ResolveKeyLabel^sapiRslv
;;
;; Call     : SET result=$$ResolveKeyLabel^sapiRslv(aid,refeid,.valkey)
;;
;; Usage    : Private helper for ResolveAttrRef^sapiRslv
;;
;; Purpose  : Resolves an ordinary key label (e.g. a sys.attr.key alias)
;;            to an eid via GetEID^apiGet or GetEID^sapiGet and registers the value key.
;;
;; Parameters
;;   aid     	: (IN) Reference-typed attribute entity ID
;;   refeid  	: (IN) Ordinary key label to resolve
;;   modelType	: (IN) Model layer containing the entity "ABox" or "TBox"
;;	 valkey	    : (OUT) 1 new / 0 existing value key, or -1 if refeid is invalid or does not exist
;;
;;
;; Notes    	: valkey (by reference) is populated by RegisterAttrVal^sapiRslv.
;;------------------------------------------------------------------
	NEW eid 		; eid resolved from refeid, or <=0 if not found
	NEW result 		; status code to return
	NEW msg 		; debug log message text
	NEW RFailed		; flag to return failure
	
	SET RFailed=-1
	IF modelType="ABox" SET eid=$$GetEID^apiGet(refeid)
	ELSE  SET eid=$$GetEID^sapiGet(refeid)
	
	IF eid>0 DO  ; refeid resolved to an existing eid
	. SET msg="ResolveKeyLabel: resolved entity key '"_refeid_"' -> eid="_eid_" for aid="_aid
	. DO LOGDEBUG^logger("sapiRslv",msg)
	. SET result=$$RegisterAttrVal^sapiRslv(aid,eid,.valkey)
	ELSE  SET result=RFailed ; refeid did not resolve to an eid
	QUIT result ; new/existing/failed status
== L6 ResolveRawEid^sapiRslv
ResolveKeyLabel(aid,refeid,valkey,modelType) ; resolve an ordinary key label to an eid and register
;;------------------------------------------------------------------
;; Function : ResolveKeyLabel^sapiRslv
;;
;; Call     : SET result=$$ResolveKeyLabel^sapiRslv(aid,refeid,.valkey)
;;
;; Usage    : Private helper for ResolveAttrRef^sapiRslv
;;
;; Purpose  : Resolves an ordinary key label (e.g. a sys.attr.key alias)
;;            to an eid via GetEID^apiGet or GetEID^sapiGet and registers the value key.
;;
;; Parameters
;;   aid     	: (IN) Reference-typed attribute entity ID
;;   refeid  	: (IN) Ordinary key label to resolve
;;   modelType	: (IN) Model layer containing the entity "ABox" or "TBox"
;;	 valkey	    : (OUT) 1 new / 0 existing value key, or -1 if refeid is invalid or does not exist
;;
;;
;; Notes    	: valkey (by reference) is populated by RegisterAttrVal^sapiRslv.
;;------------------------------------------------------------------
	NEW eid 		; eid resolved from refeid, or <=0 if not found
	NEW result 		; status code to return
	NEW msg 		; debug log message text
	NEW RFailed		; flag to return failure
	
	SET RFailed=-1
	IF modelType="ABox" SET eid=$$GetEID^apiGet(refeid)
	ELSE  SET eid=$$GetEID^sapiGet(refeid)
	
	IF eid>0 DO  ; refeid resolved to an existing eid
	. SET msg="ResolveKeyLabel: resolved entity key '"_refeid_"' -> eid="_eid_" for aid="_aid
	. DO LOGDEBUG^logger("sapiRslv",msg)
	. SET result=$$RegisterAttrVal^sapiRslv(aid,eid,.valkey)
	ELSE  SET result=RFailed ; refeid did not resolve to an eid
	QUIT result ; new/existing/failed status
L5 RegisterAttrVal^sapiRslv
RegisterAttrVal(aid,value,valkey) ; register the value key for (aid,value)
;;--------------------------------------------------------------------------------------
;; Function : RegisterAttrVal^sapiRslv
;;
;; Call     : SET isNewValKey=$$RegisterAttrVal^sapiRslv(aid,value,.valkey)
;;
;; Usage    : Shared register allocator for both literal and reference-typed
;;            attribute values. Literal values are passed in as-is; reference values
;;            must already be resolved to an eid by the caller (ResolveKeyLabel^sapiRslv /
;;            ResolveRawEid^sapiRslv) before reaching this routine.
;;
;; Parameters
;;   aid 	: (IN) Attribute entity ID, pre-resolved by caller. Must be a
;;              literal-typed attribute — e.g. 210 for "sandbox.movie.title".
;;
;;   value 	: (IN) Literal value or reference
;;	 valkey	: (OUT)
;;
;;
;; Notice  	:  Common to both TBox and ABox. Called by DatomAssert^sapiWFL,
;;            DatomAssert^apiWFL, and DatomRetract^apiWFL
;;
;; Returns  :
;;   1  : new value key allocated   (CASE 1)
;;   0  : existing value key reused (CASE 2)
;;  -1  : resolution failed valkey is left ""
;;------------------------------------------------------------------
    NEW isNewValKey	; result returned to caller
    NEW RFailed     ; Resolve Failed
    NEW RNew        ; Resolve New
    NEW RExists     ; Resolve Existing

    SET RFailed=-1
    SET RNew=1
    SET RExists=0

    SET valkey="" ; this is the value it takes if the function fails to resolve the reference
    SET isNewValKey=RExists

    ; Is this a new value for this attribute? If so, allocate a new key and register it.
    IF $$IsNewRangeValue^sapiVld(aid,value) DO  QUIT isNewValKey
    . SET valkey=$$CreateNewValKey(aid,value)
    . IF valkey<0 DO
    . . DO LOGERROR^logger("sapiRslv","RegisterAttrVal: failed allocating value key for aid="_aid_", value="_value)
    . . SET isNewValKey=RFailed
    . ELSE  DO
    . . SET isNewValKey=RNew
    . . DO LOGDEBUG^logger("sapiRslv","RegisterAttrVal: (RES)-NEW ^TBDR("_aid_","_value_") -> "_valkey)

    ; Is this value already registered for this attribute? If so, reuse the existing key.
    IF $$IsRangeValue^sapiVld(aid,value) DO  QUIT isNewValKey
    . SET valkey=$$GetValKey^sapiGet(aid,value)
    . IF valkey="" DO
    . . DO LOGERROR^logger("sapiRslv","RegisterAttrVal: ^TBDR index missing aid="_aid_", value="_value)
    . . SET isNewValKey=RFailed
    . ELSE  DO
    . . DO LOGDEBUG^logger("sapiRslv","RegisterAttrVal: (RES)-EXISTS ^TBDR("_aid_","_value_") -> "_valkey)

	; SAFETY NET — should be unreachable now that IsNewRangeValue/IsRangeValue are
    ; exact complements. If this ever fires, that invariant has been broken again;
    ; fail loudly rather than silently returning RExists with an empty valkey.
    DO LOGERROR^logger("sapiRslv","RegisterAttrVal: neither new nor existing — invariant broken, aid="_aid_", value="_value)
    SET isNewValKey=RFailed

    QUIT isNewValKey
== L6 RegisterAttrVal^sapiRslv
RegisterAttrVal(aid,value,valkey) ; register the value key for (aid,value)
;;------------------------------------------------------------------------------------------
;; Function : RegisterAttrVal^sapiRslv
;;
;; Call     : SET isNewValKey=$$RegisterAttrVal^sapiRslv(aid,value,.valkey)
;;
;; Usage    : Shared register allocator for both literal and reference-typed
;;            attribute values. Literal values are passed in as-is; reference values
;;            must already be resolved to an eid by the caller (ResolveKeyLabel^sapiRslv /
;;            ResolveRawEid^sapiRslv) before reaching this routine.
;;
;; Parameters
;;   aid 	: (IN) Attribute entity ID, pre-resolved by caller. Must be a
;;              literal-typed attribute — e.g. 210 for "sandbox.movie.title".
;;
;;   value 	: (IN) Literal value or reference
;;	 valkey	: (OUT)
;;
;;
;; Notice  	:  Common to both TBox and ABox. Called by DatomAssert^sapiWFL,
;;            DatomAssert^apiWFL, and DatomRetract^apiWFL
;;
;; Returns  :
;;   1  : new value key allocated   (CASE 1)
;;   0  : existing value key reused (CASE 2)
;;  -1  : resolution failed valkey is left ""
;;------------------------------------------------------------------
    NEW isNewValKey	; result returned to caller
    NEW RFailed     ; Resolve Failed
    NEW RNew        ; Resolve New
    NEW RExists     ; Resolve Existing

    SET RFailed=-1
    SET RNew=1
    SET RExists=0

    SET valkey="" ; this is the value it takes if the function fails to resolve the reference
    SET isNewValKey=RExists

    ; Is this a new value for this attribute? If so, allocate a new key and register it.
    IF $$IsNewRangeValue^sapiVld(aid,value) DO  QUIT isNewValKey
    . SET valkey=$$CreateNewValKey(aid,value)
    . IF valkey<0 DO
    . . DO LOGERROR^logger("sapiRslv","RegisterAttrVal: failed allocating value key for aid="_aid_", value="_value)
    . . SET isNewValKey=RFailed
    . ELSE  DO
    . . SET isNewValKey=RNew
    . . DO LOGDEBUG^logger("sapiRslv","RegisterAttrVal: (RES)-NEW ^TBDR("_aid_","_value_") -> "_valkey)

    ; Is this value already registered for this attribute? If so, reuse the existing key.
    IF $$IsRangeValue^sapiVld(aid,value) DO  QUIT isNewValKey
    . SET valkey=$$GetValKey^sapiGet(aid,value)
    . IF valkey="" DO
    . . DO LOGERROR^logger("sapiRslv","RegisterAttrVal: ^TBDR index missing aid="_aid_", value="_value)
    . . SET isNewValKey=RFailed
    . ELSE  DO
    . . DO LOGDEBUG^logger("sapiRslv","RegisterAttrVal: (RES)-EXISTS ^TBDR("_aid_","_value_") -> "_valkey)

	; SAFETY NET — should be unreachable now that IsNewRangeValue/IsRangeValue are
    ; exact complements. If this ever fires, that invariant has been broken again;
    ; fail loudly rather than silently returning RExists with an empty valkey.
    DO LOGERROR^logger("sapiRslv","RegisterAttrVal: neither new nor existing — invariant broken, aid="_aid_", value="_value)
    SET isNewValKey=RFailed

    QUIT isNewValKey
L5 IsDatomRecorded^apiVld
IsDatomRecorded(eid,aid,tx,valkey) ; check whether a datom is recorded
;;------------------------------------------------------------------------------------------------
;; Function : IsDatomRecorded^apiVld
;;
;; Call     : WRITE $$IsDatomRecorded^apiVld(eid,aid,tx,valkey)
;;
;; Purpose  : Determine whether a datom transaction record exists.
;;
;; Parameters
;;   eid    : (IN) Entity identifier.
;;   aid    : (IN) Attribute identifier.
;;   tx     : (IN) Transaction identifier.
;;   valkey : (IN) Value key.
;;
;; Scope
;;   Reads  : ^EATV(eid,aid,tx,valkey)
;;
;; Returns  : 1 if the datom record exists, otherwise 0.
;;------------------------------------------------------------------------------------------------
    ; Check the primary datom store.
    QUIT $DATA(^EATV(eid,aid,tx,valkey))>0    ; Return record existence
L3 DatomAssertSingle
DatomAssertSingle(eid,aid,val) ; resolve and stage a single-value (cardinality-one) datom
;;----------------------------------------------------------------------------------------------
;; Function  : DatomAssertSingle^apiWFL
;;
;; Purpose   : Resolves val to a valkey, enforces the uniqueness constraint, and stages the
;;             Revise operation for the entity's current cardinality-one value via DatomRevise.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID of the assertion predicate — must be cardinality-one
;;   val     : (IN) Literal or reference value — must not contain "|"
;;
;; Returns
;;   2       : no-op — value already current, nothing staged
;;   1       : triplet staged successfully
;;   0       : triplet assertion failed — bad delimiter, resolution failure, or uniqueness violation
;;----------------------------------------------------------------------------------------------
    NEW isAllOk       ; overall success flag returned to caller
    NEW valkey        ; resolved value-type key for val
    NEW isNewValKey   ; resolution result — 1 new valkey allocated, 0 existing valkey reused, <0 resolution failure

    SET isAllOk=1
    SET valkey=""

    ; Reject "|" outright — never silently store a pipe-delimited literal as a single value.
    IF val["|" DO  QUIT 0
    . DO LOGERROR^logger("apiWFL","DatomAssertSingle: '|' delimiter not allowed for cardinality-one attribute ("_aid_") — val="_val)

    ; Resolve into valkey. Reference-typed attributes resolve/register via ResolveAttrRef,
    ; everything else registers as a literal via RegisterAttrVal.
    IF $$IsRefValType^sapiVld(aid) DO
    . SET isNewValKey=$$ResolveAttrRef^sapiRslv(aid,val,.valkey,"ABox")
    ELSE  DO
    . SET isNewValKey=$$RegisterAttrVal^sapiRslv(aid,val,.valkey)

    ; Resolution failure (-1) must abort this datom immediately — the resolver has already
    ; logged the specific cause; this just enforces the abort.
    IF isNewValKey<0 DO  QUIT 0
    . DO LOGERROR^logger("apiWFL","DatomAssertSingle: aborting — value resolution failed for (eid="_eid_",aid="_aid_",val="_val_")")

    ; Uniqueness constraint — failed abort immediately, skipping DatomRevise
    IF '$$ValidateUniqueConstraint^apiVld(eid,aid,val,valkey,isNewValKey) QUIT 0

	; Apply the revise operation to stage the datom
    SET isAllOk=$$DatomRevise(eid,aid,valkey)
    QUIT isAllOk
L4 ValidateUniqueConstraint^apiVld
ValidateUniqueConstraint(eid,aid,val,valkey,isNewValKey) ; validate insert/upsert uniqueness
;;------------------------------------------------------------------
;; Function  : ValidateUniqueConstraint^apiVld
;;
;; Call      : SET ok=$$ValidateUniqueConstraint^apiVld
;;
;; Usage     : Called after value resolution and before datom staging
;;
;; Purpose   : Enforce unique attribute ownership
;;
;; Parameters
;;   eid         : (IN) Entity asserting the value
;;   aid         : (IN) Attribute identifier
;;   val         : (IN) Original caller supplied value
;;   valkey      : (IN) Resolved value key
;;   isNewValKey : (IN) Value resolution status
;;
;; Returns  : 1 if uniqueness validation succeeds, otherwise 0
;;
;; Notes    :
;;------------------------------------------------------------------
    NEW uniqueMode     ; Attribute uniqueness mode
    NEW ownereid       ; Existing live owner of valkey, 0/"" if none

    SET ownereid=""
    SET uniqueMode=$$GetUniqueMode^sapiGet(aid)

    ; Live-owner lookup is only required for reused value keys.
    ; ResolveLiveEID already applies liveness — historical (retracted)
    ; ownership alone yields no live owner, matching the "not a violation" rule.
    IF (uniqueMode=UNQINSERTID)!(uniqueMode=UNQUPSERTID),'isNewValKey DO
    . SET ownereid=$$ResolveLiveEID^apiRslv(aid,val)

    ; Reject only when another entity currently owns the value.
    IF ownereid'="",ownereid'=0,(ownereid'=eid) DO  QUIT 0
    . IF uniqueMode=UNQINSERTID DO
    . . DO LOGERROR^logger("apiVld","DatomAssert: insert-unique violation — ("_aid_","_val_":"_valkey_") already owned by ("_ownereid_")")
    . ELSE  DO
    . . DO LOGERROR^logger("apiVld","DatomAssert: upsert-unique collision — ("_aid_","_val_":"_valkey_") already owned by ("_ownereid_")")

    ; Uniqueness constraint satisfied.
    QUIT 1
L4 ResolveLiveEID^apiRslv
GetCurrentVK(eid,aid)
;;------------------------------------------------------------------
;; Function : GetCurrentVK^apiGet
;;
;; Purpose  : Returns the currently active valkey for a given (eid,aid)
;;            pair by reading ^EATV at the most recent transaction.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID
;;
;; Returns  :
;;   valkey  — current valkey (fact is currently TRUE)
;;
;;   0       — valkey found but its most recent state is a retraction (fact is currently FALSE)
;;
;;  -1       — valkey not found for an entity with this attribute, i.e. for this (eid,aid) pair
;;------------------------------------------------------------------
    NEW curtx,vk1,vk2

    ; Find the most recent tx for this (eid,aid) — O(1) via descending $ORDER
    SET curtx=$ORDER(^EATV(eid,aid,""),-1)

	; valkey not found for an entity with this attribute, i.e. for this (eid,aid) pair
    IF curtx="" DO  QUIT -1 ; return -1
    . DO LOGDEBUG^logger("apiGet","GetCurrentVK: valkey Not found for ("_eid_","_aid_")")
    
    ; The datom (eid,aid,tx,vk) has been set currently set to TRUE a revision or assertion
    SET vk1=$ORDER(^EATV(eid,aid,curtx,""))    
    IF $GET(^EATV(eid,aid,curtx,vk1))=1 QUIT vk1 ; return the valkey

	; The valkey has been currently retracted, i.e. the datom (eid,aid,tx,vk) has been set to FALSE
    SET vk2=$ORDER(^EATV(eid,aid,curtx,vk1))
    IF vk2="" DO  QUIT 0 ; return 0
    . DO LOGDEBUG^logger("apiGet","GetCurrentVK: retracted, ("_eid_","_aid_") is currently false")

    QUIT vk2
L4 DatomRevise^apiWFL
DatomRevise(eid,aid,valkey) ; stage the cardinality-one Revise operation for (eid,aid) → valkey
;;------------------------------------------------------------------
;; Function : DatomRevise^apiWFL
;;
;; Purpose  : Implements the Revise compound operation for a cardinality-one (eid,aid) pair —
;;            retracts the currently active value at t1 and asserts the new valkey at t2,
;;            by staging the appropriate datoms into %ABR.
;;
;;            Three paths depending on current state of (eid, aid):
;;
;;            ABSENT (no active datom)
;;              → stage assert (OP=1) for new valkey           INSERT
;;
;;            ACTIVE, same valkey
;;              → no-op, nothing staged                        NO-OP
;;
;;            ACTIVE, different valkey
;;              → stage retract (OP=0) for old valkey          REPLACE
;;                stage assert  (OP=1) for new valkey
;;                (both in same transaction)
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID (must be cardinality-one)
;;   valkey  : (IN) Resolved value key for the new assertion
;;
;; Returns  :
;;   2       — no-op (value already current, nothing to stage)
;;   1       — datom(s) staged (INSERT or REPLACE path)
;;   0       — failure (reserved for future error conditions)
;;------------------------------------------------------------------
    NEW curvk
    SET curvk=$$GetCurrentVK^apiGet(eid,aid)

    ; Valkey is not found or it has been previously retracted
    IF curvk=-1 DO  QUIT 1
    . SET %ABR(eid,aid,valkey)=1
    . DO LOGDEBUG^logger("apiWFL","DatomRevise: insert ("_eid_","_aid_","_valkey_") — (eid,aid) not found")
    IF curvk=0 DO  QUIT 1
    . SET %ABR(eid,aid,valkey)=1
    . DO LOGDEBUG^logger("apiWFL","DatomRevise: insert ("_eid_","_aid_","_valkey_") — prior datom retracted")

    ; Same valkey with the current valkey — no-op
    IF curvk=valkey DO  QUIT 2
    . DO LOGDEBUG^logger("apiWFL","DatomRevise: no-op ("_eid_","_aid_","_valkey_") — value unchanged")

    ; Different valkey from the current valkey — retract old, assert new (revision operation)
    SET %ABR(eid,aid,curvk)=0
    SET %ABR(eid,aid,valkey)=1
    DO LOGDEBUG^logger("apiWFL","DatomRevise: replace ("_eid_","_aid_","_curvk_") → ("_valkey_")")
    QUIT 1
L5 GetCurrentVK^apiGet
GetCurrentVK(eid,aid)
;;------------------------------------------------------------------
;; Function : GetCurrentVK^apiGet
;;
;; Purpose  : Returns the currently active valkey for a given (eid,aid)
;;            pair by reading ^EATV at the most recent transaction.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID
;;
;; Returns  :
;;   valkey  — current valkey (fact is currently TRUE)
;;
;;   0       — valkey found but its most recent state is a retraction (fact is currently FALSE)
;;
;;  -1       — valkey not found for an entity with this attribute, i.e. for this (eid,aid) pair
;;------------------------------------------------------------------
    NEW curtx,vk1,vk2

    ; Find the most recent tx for this (eid,aid) — O(1) via descending $ORDER
    SET curtx=$ORDER(^EATV(eid,aid,""),-1)

	; valkey not found for an entity with this attribute, i.e. for this (eid,aid) pair
    IF curtx="" DO  QUIT -1 ; return -1
    . DO LOGDEBUG^logger("apiGet","GetCurrentVK: valkey Not found for ("_eid_","_aid_")")
    
    ; The datom (eid,aid,tx,vk) has been set currently set to TRUE a revision or assertion
    SET vk1=$ORDER(^EATV(eid,aid,curtx,""))    
    IF $GET(^EATV(eid,aid,curtx,vk1))=1 QUIT vk1 ; return the valkey

	; The valkey has been currently retracted, i.e. the datom (eid,aid,tx,vk) has been set to FALSE
    SET vk2=$ORDER(^EATV(eid,aid,curtx,vk1))
    IF vk2="" DO  QUIT 0 ; return 0
    . DO LOGDEBUG^logger("apiGet","GetCurrentVK: retracted, ("_eid_","_aid_") is currently false")

    QUIT vk2

L2 DatomRetract^apiWFL

DatomRetract(eid,aid,val) ; dispatch to the single- or multi-value retraction branch by attribute cardinality
;;---------------------------------------------------------------------------------------------
;; Function  : DatomRetract^apiWFL
;;
;; Purpose   : Normalizes val, then dispatches to DatomRetractSingle or DatomRetractMulti based
;;             on the attribute's cardinality.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;
;;   aid     : (IN) Attribute ID
;;
;;   val     : (IN, must be omitted for cardinality-one)
;;             (IN, required for cardinality-many) Value to retract
;;
;; Returns
;;   1       : triplet retraction staged
;;   0       : triplet retraction failed
;;--------------------------------------------------------------------------------------------
    SET val=$GET(val)
    
    IF $$IsMultiValue^sapiVld(aid) QUIT $$DatomRetractMulti(eid,aid,val)
    
    QUIT $$DatomRetractSingle(eid,aid,val)
L3 DatomRetractMulti^apiWFL
DatomRetractMulti(eid,aid,val) ; recurse per "|" token, then stage each via DatomStageMulti
;;----------------------------------------------------------------------------------------------
;; Function  : DatomRetractMulti^apiWFL
;;
;; Purpose   : Validates val is supplied, splits "|"-delimited val into independent
;;             DatomRetractMulti calls, or — for a single token — delegates to
;;             DatomStageMulti with op=0.
;;
;; Parameters
;;   eid     : (IN) Entity ID
;;   aid     : (IN) Attribute ID — must be cardinality-many
;;   val     : (IN) Value(s) to retract — required, can be multi-value "val1|val2"
;;
;; Returns
;;   2       : no-op — single token only, value already inactive/retracted
;;   1       : triplet(s) staged successfully
;;   0       : one or more retractions failed — val omitted, resolution failed, or never recorded
;;----------------------------------------------------------------------------------------------
    IF val="" DO  QUIT 0
    . DO LOGERROR^logger("apiWFL","DatomRetractMulti: value required for cardinality-many attribute ("_aid_")")

    NEW isAllOk   ; overall success flag returned to caller — cleared if any recursive/staging step fails
    NEW pIdx      ; 1-based piece index while splitting val on "|"
    NEW pieceVal  ; current "|"-delimited token during multi-value recursion

    SET isAllOk=1

    ; Delimiter present — recurse per "|"-delimited token as an independent datom retraction.
    IF val["|" DO  QUIT isAllOk
    . FOR pIdx=1:1 SET pieceVal=$PIECE(val,"|",pIdx) QUIT:pieceVal=""!'isAllOk  DO
    . . IF '$$DatomRetractMulti(eid,aid,pieceVal) SET isAllOk=0

    QUIT $$DatomStageMulti(eid,aid,val,0)