")+'"')
+;throw e.mode=x,e}if("end"===r.type){const e=h(r);if(e!==ee)return e}
+if("illegal"===r.type&&""===o)return 1
+;if(R>1e5&&R>3*r.index)throw Error("potential infinite loop, way more iterations than matches")
+;return A+=o,o.length}const w=v(e)
+;if(!w)throw K(o.replace("{}",e)),Error('Unknown language: "'+e+'"')
+;const O=Q(w);let k="",x=r||O;const M={},S=new p.__emitter(p);(()=>{const e=[]
+;for(let n=x;n!==w;n=n.parent)n.scope&&e.unshift(n.scope)
+;e.forEach((e=>S.openNode(e)))})();let A="",C=0,T=0,R=0,D=!1;try{
+if(w.__emitTokens)w.__emitTokens(t,S);else{for(x.matcher.considerAll();;){
+R++,D?D=!1:x.matcher.considerAll(),x.matcher.lastIndex=T
+;const e=x.matcher.exec(t);if(!e)break;const n=N(t.substring(T,e.index),e)
+;T=e.index+n}N(t.substring(T))}return S.finalize(),k=S.toHTML(),{language:e,
+value:k,relevance:C,illegal:!1,_emitter:S,_top:x}}catch(n){
+if(n.message&&n.message.includes("Illegal"))return{language:e,value:J(t),
+illegal:!0,relevance:0,_illegalBy:{message:n.message,index:T,
+context:t.slice(T-100,T+100),mode:n.mode,resultSoFar:k},_emitter:S};if(s)return{
+language:e,value:J(t),illegal:!1,relevance:0,errorRaised:n,_emitter:S,_top:x}
+;throw n}}function E(e,n){n=n||p.languages||Object.keys(a);const t=(e=>{
+const n={value:J(e),illegal:!1,relevance:0,_top:c,_emitter:new p.__emitter(p)}
+;return n._emitter.addText(e),n})(e),i=n.filter(v).filter(k).map((n=>f(n,e,!1)))
+;i.unshift(t);const r=i.sort(((e,n)=>{
+if(e.relevance!==n.relevance)return n.relevance-e.relevance
+;if(e.language&&n.language){if(v(e.language).supersetOf===n.language)return 1
+;if(v(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=r,l=s
+;return l.secondBest=o,l}function y(e){let n=null;const t=(e=>{
+let n=e.className+" ";n+=e.parentNode?e.parentNode.className:""
+;const t=p.languageDetectRe.exec(n);if(t){const n=v(t[1])
+;return n||(H(o.replace("{}",t[1])),
+H("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"}
+return n.split(/\s+/).find((e=>_(e)||v(e)))})(e);if(_(t))return
+;if(x("before:highlightElement",{el:e,language:t
+}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e)
+;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),
+console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),
+console.warn("The element with unescaped HTML:"),
+console.warn(e)),p.throwUnescapedHTML))throw new V("One of your code blocks includes unescaped HTML.",e.innerHTML)
+;n=e;const a=n.textContent,r=t?h(a,{language:t,ignoreIllegals:!0}):E(a)
+;e.innerHTML=r.value,e.dataset.highlighted="yes",((e,n,t)=>{const a=n&&i[n]||t
+;e.classList.add("hljs"),e.classList.add("language-"+a)
+})(e,t,r.language),e.result={language:r.language,re:r.relevance,
+relevance:r.relevance},r.secondBest&&(e.secondBest={
+language:r.secondBest.language,relevance:r.secondBest.relevance
+}),x("after:highlightElement",{el:e,result:r,text:a})}let N=!1;function w(){
+"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(y):N=!0
+}function v(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]}
+function O(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{
+i[e.toLowerCase()]=n}))}function k(e){const n=v(e)
+;return n&&!n.disableAutodetect}function x(e,n){const t=e;r.forEach((e=>{
+e[t]&&e[t](n)}))}
+"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{
+N&&w()}),!1),Object.assign(t,{highlight:h,highlightAuto:E,highlightAll:w,
+highlightElement:y,
+highlightBlock:e=>(q("10.7.0","highlightBlock will be removed entirely in v12.0"),
+q("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{p=Y(p,e)},
+initHighlighting:()=>{
+w(),q("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")},
+initHighlightingOnLoad:()=>{
+w(),q("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")
+},registerLanguage:(e,n)=>{let i=null;try{i=n(t)}catch(n){
+if(K("Language definition for '{}' could not be registered.".replace("{}",e)),
+!s)throw n;K(n),i=c}
+i.name||(i.name=e),a[e]=i,i.rawDefinition=n.bind(null,t),i.aliases&&O(i.aliases,{
+languageName:e})},unregisterLanguage:e=>{delete a[e]
+;for(const n of Object.keys(i))i[n]===e&&delete i[n]},
+listLanguages:()=>Object.keys(a),getLanguage:v,registerAliases:O,
+autoDetection:k,inherit:Y,addPlugin:e=>{(e=>{
+e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{
+e["before:highlightBlock"](Object.assign({block:n.el},n))
+}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{
+e["after:highlightBlock"](Object.assign({block:n.el},n))})})(e),r.push(e)},
+removePlugin:e=>{const n=r.indexOf(e);-1!==n&&r.splice(n,1)}}),t.debugMode=()=>{
+s=!1},t.safeMode=()=>{s=!0},t.versionString="11.9.0",t.regex={concat:b,
+lookahead:d,either:m,optional:u,anyNumberOfTimes:g}
+;for(const n in C)"object"==typeof C[n]&&e(C[n]);return Object.assign(t,C),t
+},te=ne({});te.newInstance=()=>ne({});var ae=te;const ie=e=>({IMPORTANT:{
+scope:"meta",begin:"!important"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{
+scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},
+FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},
+ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",
+contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{
+scope:"number",
+begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",
+relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}
+}),re=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],se=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],oe=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],le=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],ce=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),de=oe.concat(le)
+;var ge="[0-9](_*[0-9])*",ue=`\\.(${ge})`,be="[0-9a-fA-F](_*[0-9a-fA-F])*",me={
+className:"number",variants:[{
+begin:`(\\b(${ge})((${ue})|\\.)?|(${ue}))[eE][+-]?(${ge})[fFdD]?\\b`},{
+begin:`\\b(${ge})((${ue})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{
+begin:`(${ue})[fFdD]?\\b`},{begin:`\\b(${ge})[fFdD]\\b`},{
+begin:`\\b0[xX]((${be})\\.?|(${be})?\\.(${be}))[pP][+-]?(${ge})[fFdD]?\\b`},{
+begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${be})[lL]?\\b`},{
+begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],
+relevance:0};function pe(e,n,t){return-1===t?"":e.replace(n,(a=>pe(e,n,t-1)))}
+const _e="[A-Za-z$_][0-9A-Za-z$_]*",he=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],fe=["true","false","null","undefined","NaN","Infinity"],Ee=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],ye=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],Ne=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],we=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],ve=[].concat(Ne,Ee,ye)
+;function Oe(e){const n=e.regex,t=_e,a={begin:/<[A-Za-z0-9\\._:-]+/,
+end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{
+const t=e[0].length+e.index,a=e.input[t]
+;if("<"===a||","===a)return void n.ignoreMatch();let i
+;">"===a&&(((e,{after:n})=>{const t=""+e[0].slice(1)
+;return-1!==e.input.indexOf(t,n)})(e,{after:t})||n.ignoreMatch())
+;const r=e.input.substring(t)
+;((i=r.match(/^\s*=/))||(i=r.match(/^\s+extends\s+/))&&0===i.index)&&n.ignoreMatch()
+}},i={$pattern:_e,keyword:he,literal:fe,built_in:ve,"variable.language":we
+},r="[0-9](_?[0-9])*",s=`\\.(${r})`,o="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",l={
+className:"number",variants:[{
+begin:`(\\b(${o})((${s})|\\.)?|(${s}))[eE][+-]?(${r})\\b`},{
+begin:`\\b(${o})\\b((${s})\\b|\\.)?|(${s})\\b`},{
+begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
+begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
+begin:"\\b0[0-7]+n?\\b"}],relevance:0},c={className:"subst",begin:"\\$\\{",
+end:"\\}",keywords:i,contains:[]},d={begin:"html`",end:"",starts:{end:"`",
+returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,c],subLanguage:"xml"}},g={
+begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
+contains:[e.BACKSLASH_ESCAPE,c],subLanguage:"css"}},u={begin:"gql`",end:"",
+starts:{end:"`",returnEnd:!1,contains:[e.BACKSLASH_ESCAPE,c],
+subLanguage:"graphql"}},b={className:"string",begin:"`",end:"`",
+contains:[e.BACKSLASH_ESCAPE,c]},m={className:"comment",
+variants:[e.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
+begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",
+begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,
+excludeBegin:!0,relevance:0},{className:"variable",begin:t+"(?=\\s*(-)|$)",
+endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
+}),e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]
+},p=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,d,g,u,b,{match:/\$\d+/},l]
+;c.contains=p.concat({begin:/\{/,end:/\}/,keywords:i,contains:["self"].concat(p)
+});const _=[].concat(m,c.contains),h=_.concat([{begin:/\(/,end:/\)/,keywords:i,
+contains:["self"].concat(_)}]),f={className:"params",begin:/\(/,end:/\)/,
+excludeBegin:!0,excludeEnd:!0,keywords:i,contains:h},E={variants:[{
+match:[/class/,/\s+/,t,/\s+/,/extends/,/\s+/,n.concat(t,"(",n.concat(/\./,t),")*")],
+scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{
+match:[/class/,/\s+/,t],scope:{1:"keyword",3:"title.class"}}]},y={relevance:0,
+match:n.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),
+className:"title.class",keywords:{_:[...Ee,...ye]}},N={variants:[{
+match:[/function/,/\s+/,t,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],
+className:{1:"keyword",3:"title.function"},label:"func.def",contains:[f],
+illegal:/%/},w={
+match:n.concat(/\b/,(v=[...Ne,"super","import"],n.concat("(?!",v.join("|"),")")),t,n.lookahead(/\(/)),
+className:"title.function",relevance:0};var v;const O={
+begin:n.concat(/\./,n.lookahead(n.concat(t,/(?![0-9A-Za-z$_(])/))),end:t,
+excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},k={
+match:[/get|set/,/\s+/,t,/(?=\()/],className:{1:"keyword",3:"title.function"},
+contains:[{begin:/\(\)/},f]
+},x="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+e.UNDERSCORE_IDENT_RE+")\\s*=>",M={
+match:[/const|var|let/,/\s+/,t,/\s*/,/=\s*/,/(async\s*)?/,n.lookahead(x)],
+keywords:"async",className:{1:"keyword",3:"title.function"},contains:[f]}
+;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:i,exports:{
+PARAMS_CONTAINS:h,CLASS_REFERENCE:y},illegal:/#(?![$_A-z])/,
+contains:[e.SHEBANG({label:"shebang",binary:"node",relevance:5}),{
+label:"use_strict",className:"meta",relevance:10,
+begin:/^\s*['"]use (strict|asm)['"]/
+},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,d,g,u,b,m,{match:/\$\d+/},l,y,{
+className:"attr",begin:t+n.lookahead(":"),relevance:0},M,{
+begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
+keywords:"return throw case",relevance:0,contains:[m,e.REGEXP_MODE,{
+className:"function",begin:x,returnBegin:!0,end:"\\s*=>",contains:[{
+className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{
+className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,
+excludeEnd:!0,keywords:i,contains:h}]}]},{begin:/,/,relevance:0},{match:/\s+/,
+relevance:0},{variants:[{begin:"<>",end:">"},{
+match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:a.begin,
+"on:begin":a.isTrulyOpeningTag,end:a.end}],subLanguage:"xml",contains:[{
+begin:a.begin,end:a.end,skip:!0,contains:["self"]}]}]},N,{
+beginKeywords:"while if switch catch for"},{
+begin:"\\b(?!function)"+e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
+returnBegin:!0,label:"func.def",contains:[f,e.inherit(e.TITLE_MODE,{begin:t,
+className:"title.function"})]},{match:/\.\.\./,relevance:0},O,{match:"\\$"+t,
+relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},
+contains:[f]},w,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
+className:"variable.constant"},E,k,{match:/\$[(.]/}]}}
+const ke=e=>b(/\b/,e,/\w$/.test(e)?/\b/:/\B/),xe=["Protocol","Type"].map(ke),Me=["init","self"].map(ke),Se=["Any","Self"],Ae=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],Ce=["false","nil","true"],Te=["assignment","associativity","higherThan","left","lowerThan","none","right"],Re=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],De=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],Ie=m(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),Le=m(Ie,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),Be=b(Ie,Le,"*"),$e=m(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),ze=m($e,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),Fe=b($e,ze,"*"),Ue=b(/[A-Z]/,ze,"*"),je=["attached","autoclosure",b(/convention\(/,m("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",b(/objc\(/,Fe,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],Pe=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"]
+;var Ke=Object.freeze({__proto__:null,grmr_bash:e=>{const n=e.regex,t={},a={
+begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]}
+;Object.assign(t,{className:"variable",variants:[{
+begin:n.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},a]});const i={
+className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},r={
+begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,
+end:/(\w+)/,className:"string"})]}},s={className:"string",begin:/"/,end:/"/,
+contains:[e.BACKSLASH_ESCAPE,t,i]};i.contains.push(s);const o={begin:/\$?\(\(/,
+end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t]
+},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10
+}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,
+contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{
+name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,
+keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"],
+literal:["true","false"],
+built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"]
+},contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,r,{match:/(\/[a-z._-]+)+/},s,{
+match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}},
+grmr_c:e=>{const n=e.regex,t=e.COMMENT("//","$",{contains:[{begin:/\\\n/}]
+}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={
+className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{
+match:/\batomic_[a-z]{3,6}\b/}]},o={className:"string",variants:[{
+begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},e.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{
+className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={
+className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0
+},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={
+keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"],
+type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"],
+literal:"true false NULL",
+built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr"
+},b=[c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],m={variants:[{begin:/=/,end:/;/},{
+begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],
+keywords:u,contains:b.concat([{begin:/\(/,end:/\)/,keywords:u,
+contains:b.concat(["self"]),relevance:0}]),relevance:0},p={
+begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,
+keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{
+begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})],
+relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,
+keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/,
+end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s]
+}]},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u,
+disableAutodetect:!0,illegal:"",contains:[].concat(m,p,b,[c,{
+begin:e.IDENT_RE+"::",keywords:u},{className:"class",
+beginKeywords:"enum class struct union",end:/[{;:<>=]/,contains:[{
+beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c,
+strings:o,keywords:u}}},grmr_cpp:e=>{const n=e.regex,t=e.COMMENT("//","$",{
+contains:[{begin:/\\\n/}]
+}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="(?!struct)("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={
+className:"type",begin:"\\b[a-z\\d_]*_t\\b"},o={className:"string",variants:[{
+begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{
+begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",
+end:"'",illegal:"."},e.END_SAME_AS_BEGIN({
+begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={
+className:"number",variants:[{begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"
+},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{
+keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{
+className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={
+className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0
+},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={
+type:["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"],
+keyword:["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"],
+literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"],
+_type_hints:["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"]
+},b={className:"function.dispatch",relevance:0,keywords:{
+_hint:["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"]
+},
+begin:n.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,n.lookahead(/(<[^<>]+>|)\s*\(/))
+},m=[b,c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],p={variants:[{begin:/=/,end:/;/},{
+begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],
+keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u,
+contains:m.concat(["self"]),relevance:0}]),relevance:0},_={className:"function",
+begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,
+keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{
+begin:g,returnBegin:!0,contains:[d],relevance:0},{begin:/::/,relevance:0},{
+begin:/:/,endsWithParent:!0,contains:[o,l]},{relevance:0,match:/,/},{
+className:"params",begin:/\(/,end:/\)/,keywords:u,relevance:0,
+contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/,end:/\)/,keywords:u,
+relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s]}]
+},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C++",
+aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"",
+classNameAliases:{"function.dispatch":"built_in"},
+contains:[].concat(p,_,b,m,[c,{
+begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array|tuple|optional|variant|function)\\s*<(?!<)",
+end:">",keywords:u,contains:["self",s]},{begin:e.IDENT_RE+"::",keywords:u},{
+match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],
+className:{1:"keyword",3:"title.class"}}])}},grmr_csharp:e=>{const n={
+keyword:["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]),
+built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"],
+literal:["default","false","null","true"]},t=e.inherit(e.TITLE_MODE,{
+begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{
+begin:"\\b(0b[01']+)"},{
+begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{
+begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"
+}],relevance:0},i={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]
+},r=e.inherit(i,{illegal:/\n/}),s={className:"subst",begin:/\{/,end:/\}/,
+keywords:n},o=e.inherit(s,{illegal:/\n/}),l={className:"string",begin:/\$"/,
+end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/
+},e.BACKSLASH_ESCAPE,o]},c={className:"string",begin:/\$@"/,end:'"',contains:[{
+begin:/\{\{/},{begin:/\}\}/},{begin:'""'},s]},d=e.inherit(c,{illegal:/\n/,
+contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},o]})
+;s.contains=[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],
+o.contains=[d,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{
+illegal:/\n/})];const g={variants:[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},u={begin:"<",end:">",contains:[{beginKeywords:"in out"},t]
+},b=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",m={
+begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],
+keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,
+contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{
+begin:"\x3c!--|--\x3e"},{begin:"?",end:">"}]}]
+}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",
+end:"$",keywords:{
+keyword:"if else elif endif define undef warning error line region endregion pragma checksum"
+}},g,a,{beginKeywords:"class interface",relevance:0,end:/[{;=]/,
+illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"
+},t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",
+relevance:0,end:/[{;=]/,illegal:/[^\s:]/,
+contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{
+beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,
+contains:[t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",
+begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{
+className:"string",begin:/"/,end:/"/}]},{
+beginKeywords:"new return throw await else",relevance:0},{className:"function",
+begin:"("+b+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,
+end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{
+beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial",
+relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,
+contains:[e.TITLE_MODE,u],relevance:0},{match:/\(\)/},{className:"params",
+begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,
+contains:[g,a,e.C_BLOCK_COMMENT_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},m]}},grmr_css:e=>{
+const n=e.regex,t=ie(e),a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{
+name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{
+keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"},
+contains:[t.BLOCK_COMMENT,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/
+},t.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0
+},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0
+},t.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{
+begin:":("+oe.join("|")+")"},{begin:":(:)?("+le.join("|")+")"}]
+},t.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b"},{
+begin:/:/,end:/[;}{]/,
+contains:[t.BLOCK_COMMENT,t.HEXCOLOR,t.IMPORTANT,t.CSS_NUMBER_MODE,...a,{
+begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"
+},contains:[...a,{className:"string",begin:/[^)]/,endsWithParent:!0,
+excludeEnd:!0}]},t.FUNCTION_DISPATCH]},{begin:n.lookahead(/@/),end:"[{;]",
+relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/
+},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{
+$pattern:/[a-z-]+/,keyword:"and or not only",attribute:se.join(" ")},contains:[{
+begin:/[a-z-]+(?=:)/,className:"attribute"},...a,t.CSS_NUMBER_MODE]}]},{
+className:"selector-tag",begin:"\\b("+re.join("|")+")\\b"}]}},grmr_diff:e=>{
+const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{
+className:"meta",relevance:10,
+match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)
+},{className:"comment",variants:[{
+begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),
+end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{
+className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,
+end:/$/}]}},grmr_go:e=>{const n={
+keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"],
+type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"],
+literal:["true","false","iota","nil"],
+built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"]
+};return{name:"Go",aliases:["golang"],keywords:n,illegal:"",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",
+variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{
+className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1
+},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",
+end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",
+begin:/\(/,end:/\)/,endsParent:!0,keywords:n,illegal:/["']/}]}]}},
+grmr_graphql:e=>{const n=e.regex;return{name:"GraphQL",aliases:["gql"],
+case_insensitive:!0,disableAutodetect:!1,keywords:{
+keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"],
+literal:["true","false","null"]},
+contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{
+scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation",
+begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/,
+end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{
+scope:"symbol",begin:n.concat(/[_A-Za-z][_0-9A-Za-z]*/,n.lookahead(/\s*:/)),
+relevance:0}],illegal:[/[;<']/,/BEGIN/]}},grmr_ini:e=>{const n=e.regex,t={
+className:"number",relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{
+begin:e.NUMBER_RE}]},a=e.COMMENT();a.variants=[{begin:/;/,end:/$/},{begin:/#/,
+end:/$/}];const i={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{
+begin:/\$\{(.*?)\}/}]},r={className:"literal",
+begin:/\bon|off|true|false|yes|no\b/},s={className:"string",
+contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{
+begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]
+},o={begin:/\[/,end:/\]/,contains:[a,r,i,s,t,"self"],relevance:0
+},l=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{
+name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,
+contains:[a,{className:"section",begin:/\[+/,end:/\]+/},{
+begin:n.concat(l,"(\\s*\\.\\s*",l,")*",n.lookahead(/\s*=\s*[^#\s]/)),
+className:"attr",starts:{end:/$/,contains:[a,o,r,i,s,t]}}]}},grmr_java:e=>{
+const n=e.regex,t="[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",a=t+pe("(?:<"+t+"~~~(?:\\s*,\\s*"+t+"~~~)*>)?",/~~~/g,2),i={
+keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"],
+literal:["false","true","null"],
+type:["char","boolean","long","float","int","byte","short","double"],
+built_in:["super","this"]},r={className:"meta",begin:"@"+t,contains:[{
+begin:/\(/,end:/\)/,contains:["self"]}]},s={className:"params",begin:/\(/,
+end:/\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0}
+;return{name:"Java",aliases:["jsp"],keywords:i,illegal:/<\/|#/,
+contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,
+relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{
+begin:/import java\.[a-z]+\./,keywords:"import",relevance:2
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/,
+className:"string",contains:[e.BACKSLASH_ESCAPE]
+},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{
+match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,t],className:{
+1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{
+begin:[n.concat(/(?!else)/,t),/\s+/,t,/\s+/,/=(?!=)/],className:{1:"type",
+3:"variable",5:"operator"}},{begin:[/record/,/\s+/,t],className:{1:"keyword",
+3:"title.class"},contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{
+beginKeywords:"new throw return else",relevance:0},{
+begin:["(?:"+a+"\\s+)",e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{
+2:"title.function"},keywords:i,contains:[{className:"params",begin:/\(/,
+end:/\)/,keywords:i,relevance:0,
+contains:[r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,me,e.C_BLOCK_COMMENT_MODE]
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},me,r]}},grmr_javascript:Oe,
+grmr_json:e=>{const n=["true","false","null"],t={scope:"literal",
+beginKeywords:n.join(" ")};return{name:"JSON",keywords:{literal:n},contains:[{
+className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{
+match:/[{}[\],:]/,className:"punctuation",relevance:0
+},e.QUOTE_STRING_MODE,t,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],
+illegal:"\\S"}},grmr_kotlin:e=>{const n={
+keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",
+built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",
+literal:"true false null"},t={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"
+},a={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},i={
+className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string",
+variants:[{begin:'"""',end:'"""(?=[^"])',contains:[i,a]},{begin:"'",end:"'",
+illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,
+contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={
+className:"meta",
+begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"
+},o={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,
+end:/\)/,contains:[e.inherit(r,{className:"string"}),"self"]}]
+},l=me,c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),d={
+variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,
+contains:[]}]},g=d;return g.variants[1].contains=[d],d.variants[1].contains=[g],
+{name:"Kotlin",aliases:["kt","kts"],keywords:n,
+contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",
+begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",
+begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",
+begin:/@\w+/}]}},t,s,o,{className:"function",beginKeywords:"fun",end:"[(]|$",
+returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{
+begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
+contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/,end:/>/,
+keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,
+endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,
+endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,c],relevance:0
+},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{
+begin:[/class|interface|trait/,/\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{
+3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,
+illegal:"extends implements",contains:[{
+beginKeywords:"public protected internal private constructor"
+},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/,end:/>/,excludeBegin:!0,
+excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/,
+excludeBegin:!0,returnEnd:!0},s,o]},r,{className:"meta",begin:"^#!/usr/bin/env",
+end:"$",illegal:"\n"},l]}},grmr_less:e=>{
+const n=ie(e),t=de,a="[\\w-]+",i="("+a+"|@\\{"+a+"\\})",r=[],s=[],o=e=>({
+className:"string",begin:"~?"+e+".*?"+e}),l=(e,n,t)=>({className:e,begin:n,
+relevance:t}),c={$pattern:/[a-z-]+/,keyword:"and or not only",
+attribute:se.join(" ")},d={begin:"\\(",end:"\\)",contains:s,keywords:c,
+relevance:0}
+;s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,o("'"),o('"'),n.CSS_NUMBER_MODE,{
+begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",
+excludeEnd:!0}
+},n.HEXCOLOR,d,l("variable","@@?"+a,10),l("variable","@\\{"+a+"\\}"),l("built_in","~?`[^`]*?`"),{
+className:"attribute",begin:a+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0
+},n.IMPORTANT,{beginKeywords:"and not"},n.FUNCTION_DISPATCH);const g=s.concat({
+begin:/\{/,end:/\}/,contains:r}),u={beginKeywords:"when",endsWithParent:!0,
+contains:[{beginKeywords:"and not"}].concat(s)},b={begin:i+"\\s*:",
+returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/
+},n.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b",
+end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]
+},m={className:"keyword",
+begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",
+starts:{end:"[;{}]",keywords:c,returnEnd:!0,contains:s,relevance:0}},p={
+className:"variable",variants:[{begin:"@"+a+"\\s*:",relevance:15},{begin:"@"+a
+}],starts:{end:"[;}]",returnEnd:!0,contains:g}},_={variants:[{
+begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:i,end:/\{/}],returnBegin:!0,
+returnEnd:!0,illegal:"[<='$\"]",relevance:0,
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,u,l("keyword","all\\b"),l("variable","@\\{"+a+"\\}"),{
+begin:"\\b("+re.join("|")+")\\b",className:"selector-tag"
+},n.CSS_NUMBER_MODE,l("selector-tag",i,0),l("selector-id","#"+i),l("selector-class","\\."+i,0),l("selector-tag","&",0),n.ATTRIBUTE_SELECTOR_MODE,{
+className:"selector-pseudo",begin:":("+oe.join("|")+")"},{
+className:"selector-pseudo",begin:":(:)?("+le.join("|")+")"},{begin:/\(/,
+end:/\)/,relevance:0,contains:g},{begin:"!important"},n.FUNCTION_DISPATCH]},h={
+begin:a+":(:)?"+`(${t.join("|")})`,returnBegin:!0,contains:[_]}
+;return r.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,m,p,h,b,_,u,n.FUNCTION_DISPATCH),
+{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:r}},
+grmr_lua:e=>{const n="\\[=*\\[",t="\\]=*\\]",a={begin:n,end:t,contains:["self"]
+},i=[e.COMMENT("--(?!"+n+")","$"),e.COMMENT("--"+n,t,{contains:[a],relevance:10
+})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,
+literal:"true false nil",
+keyword:"and break do else elseif end for goto if in local not or repeat return then until while",
+built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"
+},contains:i.concat([{className:"function",beginKeywords:"function",end:"\\)",
+contains:[e.inherit(e.TITLE_MODE,{
+begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",
+begin:"\\(",endsWithParent:!0,contains:i}].concat(i)
+},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",
+begin:n,end:t,contains:[a],relevance:5}])}},grmr_makefile:e=>{const n={
+className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",
+contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%\^\+\*]/}]},t={className:"string",
+begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n]},a={className:"variable",
+begin:/\$\([\w-]+\s/,end:/\)/,keywords:{
+built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"
+},contains:[n]},i={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},r={
+className:"section",begin:/^[^\s]+:/,end:/$/,contains:[n]};return{
+name:"Makefile",aliases:["mk","mak","make"],keywords:{$pattern:/[\w-]+/,
+keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"
+},contains:[e.HASH_COMMENT_MODE,n,t,a,i,{className:"meta",begin:/^\.PHONY:/,
+end:/$/,keywords:{$pattern:/[\.\w]+/,keyword:".PHONY"}},r]}},grmr_markdown:e=>{
+const n={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},t={
+variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{
+begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
+relevance:2},{
+begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),
+relevance:2},{begin:/\[.+?\]\([./?].*?\)/,relevance:1},{
+begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/
+},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,
+returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",
+excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",
+end:"\\]",excludeBegin:!0,excludeEnd:!0}]},a={className:"strong",contains:[],
+variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]
+},i={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{
+begin:/_(?![_\s])/,end:/_/,relevance:0}]},r=e.inherit(a,{contains:[]
+}),s=e.inherit(i,{contains:[]});a.contains.push(s),i.contains.push(r)
+;let o=[n,t];return[a,i,r,s].forEach((e=>{e.contains=e.contains.concat(o)
+})),o=o.concat(a,i),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{
+className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:o},{
+begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",
+contains:o}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",
+end:"\\s+",excludeEnd:!0},a,i,{className:"quote",begin:"^>\\s+",contains:o,
+end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{
+begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{
+begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",
+contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{
+begin:"^[-\\*]{3,}",end:"$"},t,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{
+className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{
+className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}},grmr_objectivec:e=>{
+const n=/[a-zA-Z@][a-zA-Z0-9_]*/,t={$pattern:n,
+keyword:["@interface","@class","@protocol","@implementation"]};return{
+name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"],
+keywords:{"variable.language":["this","super"],$pattern:n,
+keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"],
+literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"],
+built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"],
+type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"]
+},illegal:"",contains:[{className:"built_in",
+begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{
+className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",
+contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,
+keywords:{
+keyword:"if else elif endif define undef warning error line pragma ifdef ifndef include"
+},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{
+className:"string"}),{className:"string",begin:/<.*?>/,end:/$/,illegal:"\\n"
+},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",
+begin:"("+t.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:t,
+contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,
+relevance:0}]}},grmr_perl:e=>{const n=e.regex,t=/[dualxmsipngr]{0,12}/,a={
+$pattern:/[\w.]+/,
+keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0"
+},i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:a},r={begin:/->\{/,
+end:/\}/},s={variants:[{begin:/\$\d/},{
+begin:n.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])")
+},{begin:/[$%@][^\s\w{]/,relevance:0}]
+},o=[e.BACKSLASH_ESCAPE,i,s],l=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],c=(e,a,i="\\1")=>{
+const r="\\1"===i?i:n.concat(i,a)
+;return n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,r,/(?:\\.|[^\\\/])*?/,i,t)
+},d=(e,a,i)=>n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,i,t),g=[s,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{
+endsWithParent:!0}),r,{className:"string",contains:o,variants:[{
+begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",
+end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{
+begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">",
+relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",
+contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",
+contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{
+begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number",
+begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",
+relevance:0},{
+begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",
+keywords:"split return print reverse grep",relevance:0,
+contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{
+begin:c("s|tr|y",n.either(...l,{capture:!0}))},{begin:c("s|tr|y","\\(","\\)")},{
+begin:c("s|tr|y","\\[","\\]")},{begin:c("s|tr|y","\\{","\\}")}],relevance:2},{
+className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{
+begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",n.either(...l,{capture:!0
+}),/\1/)},{begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{
+begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub",
+end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{
+begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",
+subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]
+}];return i.contains=g,r.contains=g,{name:"Perl",aliases:["pl","pm"],keywords:a,
+contains:g}},grmr_php:e=>{
+const n=e.regex,t=/(?![A-Za-z0-9])(?![$])/,a=n.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,t),i=n.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,t),r={
+scope:"variable",match:"\\$+"+a},s={scope:"subst",variants:[{begin:/\$\w+/},{
+begin:/\{\$/,end:/\}/}]},o=e.inherit(e.APOS_STRING_MODE,{illegal:null
+}),l="[ \t\n]",c={scope:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{
+illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(s)}),o,{
+begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/,
+contains:e.QUOTE_STRING_MODE.contains.concat(s),"on:begin":(e,n)=>{
+n.data._beginMatch=e[1]||e[2]},"on:end":(e,n)=>{
+n.data._beginMatch!==e[1]&&n.ignoreMatch()}},e.END_SAME_AS_BEGIN({
+begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/})]},d={scope:"number",variants:[{
+begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{
+begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{
+begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"
+}],relevance:0
+},g=["false","null","true"],u=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],b=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],m={
+keyword:u,literal:(e=>{const n=[];return e.forEach((e=>{
+n.push(e),e.toLowerCase()===e?n.push(e.toUpperCase()):n.push(e.toLowerCase())
+})),n})(g),built_in:b},p=e=>e.map((e=>e.replace(/\|\d+$/,""))),_={variants:[{
+match:[/new/,n.concat(l,"+"),n.concat("(?!",p(b).join("\\b|"),"\\b)"),i],scope:{
+1:"keyword",4:"title.class"}}]},h=n.concat(a,"\\b(?!\\()"),f={variants:[{
+match:[n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{2:"variable.constant"
+}},{match:[/::/,/class/],scope:{2:"variable.language"}},{
+match:[i,n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{1:"title.class",
+3:"variable.constant"}},{match:[i,n.concat("::",n.lookahead(/(?!class\b)/))],
+scope:{1:"title.class"}},{match:[i,/::/,/class/],scope:{1:"title.class",
+3:"variable.language"}}]},E={scope:"attr",
+match:n.concat(a,n.lookahead(":"),n.lookahead(/(?!::)/))},y={relevance:0,
+begin:/\(/,end:/\)/,keywords:m,contains:[E,r,f,e.C_BLOCK_COMMENT_MODE,c,d,_]
+},N={relevance:0,
+match:[/\b/,n.concat("(?!fn\\b|function\\b|",p(u).join("\\b|"),"|",p(b).join("\\b|"),"\\b)"),a,n.concat(l,"*"),n.lookahead(/(?=\()/)],
+scope:{3:"title.function.invoke"},contains:[y]};y.contains.push(N)
+;const w=[E,f,e.C_BLOCK_COMMENT_MODE,c,d,_];return{case_insensitive:!1,
+keywords:m,contains:[{begin:n.concat(/#\[\s*/,i),beginScope:"meta",end:/]/,
+endScope:"meta",keywords:{literal:g,keyword:["new","array"]},contains:[{
+begin:/\[/,end:/]/,keywords:{literal:g,keyword:["new","array"]},
+contains:["self",...w]},...w,{scope:"meta",match:i}]
+},e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{
+scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,
+keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE,
+contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},{scope:"meta",variants:[{
+begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{
+begin:/\?>/}]},{scope:"variable.language",match:/\$this\b/},r,N,f,{
+match:[/const/,/\s/,a],scope:{1:"keyword",3:"variable.constant"}},_,{
+scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,
+excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"
+},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",
+begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:m,
+contains:["self",r,f,e.C_BLOCK_COMMENT_MODE,c,d]}]},{scope:"class",variants:[{
+beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait",
+illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{
+beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{
+beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/,
+contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{
+beginKeywords:"use",relevance:0,end:";",contains:[{
+match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},c,d]}
+},grmr_php_template:e=>({name:"PHP template",subLanguage:"xml",contains:[{
+begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",
+end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0
+},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,
+skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,
+contains:null,skip:!0})]}]}),grmr_plaintext:e=>({name:"Plain text",
+aliases:["text","txt"],disableAutodetect:!0}),grmr_python:e=>{
+const n=e.regex,t=/[\p{XID_Start}_]\p{XID_Continue}*/u,a=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],i={
+$pattern:/[A-Za-z]\w+|__\w+__/,keyword:a,
+built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"],
+literal:["__debug__","Ellipsis","False","None","NotImplemented","True"],
+type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"]
+},r={className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/,
+end:/\}/,keywords:i,illegal:/#/},o={begin:/\{\{/,relevance:0},l={
+className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{
+begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,
+contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{
+begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,
+contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{
+begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,
+contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,
+end:/"""/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([uU]|[rR])'/,end:/'/,
+relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{
+begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,
+end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,
+contains:[e.BACKSLASH_ESCAPE,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,
+contains:[e.BACKSLASH_ESCAPE,o,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]
+},c="[0-9](_?[0-9])*",d=`(\\b(${c}))?\\.(${c})|\\b(${c})\\.`,g="\\b|"+a.join("|"),u={
+className:"number",relevance:0,variants:[{
+begin:`(\\b(${c})|(${d}))[eE][+-]?(${c})[jJ]?(?=${g})`},{begin:`(${d})[jJ]?`},{
+begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${g})`},{
+begin:`\\b0[bB](_?[01])+[lL]?(?=${g})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${g})`
+},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${g})`},{begin:`\\b(${c})[jJ](?=${g})`
+}]},b={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:i,
+contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={
+className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/,
+end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i,
+contains:["self",r,u,l,e.HASH_COMMENT_MODE]}]};return s.contains=[l,u,r],{
+name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:i,
+illegal:/(<\/|\?)|=>/,contains:[r,u,{begin:/\bself\b/},{beginKeywords:"if",
+relevance:0},l,b,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,t],scope:{
+1:"keyword",3:"title.function"},contains:[m]},{variants:[{
+match:[/\bclass/,/\s+/,t,/\s*/,/\(\s*/,t,/\s*\)/]},{match:[/\bclass/,/\s+/,t]}],
+scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{
+className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[u,m,l]}]}},
+grmr_python_repl:e=>({aliases:["pycon"],contains:[{className:"meta.prompt",
+starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{
+begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}),grmr_r:e=>{
+const n=e.regex,t=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,a=n.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,r=n.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/)
+;return{name:"R",keywords:{$pattern:t,
+keyword:"function if in break next repeat else for while",
+literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10",
+built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm"
+},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/,
+starts:{end:n.lookahead(n.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)),
+endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{
+scope:"variable",variants:[{match:t},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0
+}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}]
+}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE],
+variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/
+}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/
+}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/
+}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/
+}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/
+}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"',
+relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{
+1:"operator",2:"number"},match:[i,a]},{scope:{1:"operator",2:"number"},
+match:[/%[^%]*%/,a]},{scope:{1:"punctuation",2:"number"},match:[r,a]},{scope:{
+2:"number"},match:[/[^a-zA-Z0-9._]|^/,a]}]},{scope:{3:"operator"},
+match:[t,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:i},{
+match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:r},{begin:"`",end:"`",
+contains:[{begin:/\\./}]}]}},grmr_ruby:e=>{
+const n=e.regex,t="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",a=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(a,/(::\w+)*/),r={
+"variable.constant":["__FILE__","__LINE__","__ENCODING__"],
+"variable.language":["self","super"],
+keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"],
+built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"],
+literal:["true","false","nil"]},s={className:"doctag",begin:"@[A-Za-z]+"},o={
+begin:"#<",end:">"},l=[e.COMMENT("#","$",{contains:[s]
+}),e.COMMENT("^=begin","^=end",{contains:[s],relevance:10
+}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],c={className:"subst",begin:/#\{/,
+end:/\}/,keywords:r},d={className:"string",contains:[e.BACKSLASH_ESCAPE,c],
+variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{
+begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{
+begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?,end:/>/},{begin:/%[qQwWx]?\//,
+end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{
+begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{
+begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{
+begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{
+begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{
+begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),
+contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,
+contains:[e.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",u={className:"number",
+relevance:0,variants:[{
+begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{
+begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"
+},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{
+begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{
+begin:"\\b0(_?[0-7])+r?i?\\b"}]},b={variants:[{match:/\(\)/},{
+className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,
+keywords:r}]},m=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{
+match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class",
+4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,i],scope:{
+2:"title.class"},keywords:r},{relevance:0,match:[i,/\.new[. (]/],scope:{
+1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,
+className:"variable.constant"},{relevance:0,match:a,scope:"title.class"},{
+match:[/def/,/\s+/,t],scope:{1:"keyword",3:"title.function"},contains:[b]},{
+begin:e.IDENT_RE+"::"},{className:"symbol",
+begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",
+begin:":(?!\\s)",contains:[d,{begin:t}],relevance:0},u,{className:"variable",
+begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{
+className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0,
+relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",
+keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],
+illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{
+begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",
+end:"\\][a-z]*"}]}].concat(o,l),relevance:0}].concat(o,l)
+;c.contains=m,b.contains=m;const p=[{begin:/^\s*=>/,starts:{end:"$",contains:m}
+},{className:"meta.prompt",
+begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])",
+starts:{end:"$",keywords:r,contains:m}}];return l.unshift(o),{name:"Ruby",
+aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/,
+contains:[e.SHEBANG({binary:"ruby"})].concat(p).concat(l).concat(m)}},
+grmr_rust:e=>{const n=e.regex,t={className:"title.function.invoke",relevance:0,
+begin:n.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,n.lookahead(/\s*\(/))
+},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"]
+;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r,
+keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"],
+literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:"",
+contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]
+}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{
+className:"string",variants:[{begin:/b?r(#*)"(.|\n)*?"\1(?!#)/},{
+begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",
+begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{
+begin:"\\b0b([01_]+)"+a},{begin:"\\b0o([0-7_]+)"+a},{
+begin:"\\b0x([A-Fa-f0-9_]+)"+a},{
+begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+a}],relevance:0},{
+begin:[/fn/,/\s+/,e.UNDERSCORE_IDENT_RE],className:{1:"keyword",
+3:"title.function"}},{className:"meta",begin:"#!?\\[",end:"\\]",contains:[{
+className:"string",begin:/"/,end:/"/}]},{
+begin:[/let/,/\s+/,/(?:mut\s+)?/,e.UNDERSCORE_IDENT_RE],className:{1:"keyword",
+3:"keyword",4:"variable"}},{
+begin:[/for/,/\s+/,e.UNDERSCORE_IDENT_RE,/\s+/,/in/],className:{1:"keyword",
+3:"variable",5:"keyword"}},{begin:[/type/,/\s+/,e.UNDERSCORE_IDENT_RE],
+className:{1:"keyword",3:"title.class"}},{
+begin:[/(?:trait|enum|struct|union|impl|for)/,/\s+/,e.UNDERSCORE_IDENT_RE],
+className:{1:"keyword",3:"title.class"}},{begin:e.IDENT_RE+"::",keywords:{
+keyword:"Self",built_in:i,type:r}},{className:"punctuation",begin:"->"},t]}},
+grmr_scss:e=>{const n=ie(e),t=le,a=oe,i="@[a-z-]+",r={className:"variable",
+begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS",
+case_insensitive:!0,illegal:"[=/|']",
+contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,n.CSS_NUMBER_MODE,{
+className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{
+className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0
+},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag",
+begin:"\\b("+re.join("|")+")\\b",relevance:0},{className:"selector-pseudo",
+begin:":("+a.join("|")+")"},{className:"selector-pseudo",
+begin:":(:)?("+t.join("|")+")"},r,{begin:/\(/,end:/\)/,
+contains:[n.CSS_NUMBER_MODE]},n.CSS_VARIABLE,{className:"attribute",
+begin:"\\b("+ce.join("|")+")\\b"},{
+begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"
+},{begin:/:/,end:/[;}{]/,relevance:0,
+contains:[n.BLOCK_COMMENT,r,n.HEXCOLOR,n.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.IMPORTANT,n.FUNCTION_DISPATCH]
+},{begin:"@(page|font-face)",keywords:{$pattern:i,keyword:"@page @font-face"}},{
+begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,
+keyword:"and or not only",attribute:se.join(" ")},contains:[{begin:i,
+className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute"
+},r,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE]
+},n.FUNCTION_DISPATCH]}},grmr_shell:e=>({name:"Shell Session",
+aliases:["console","shellsession"],contains:[{className:"meta.prompt",
+begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,
+subLanguage:"bash"}}]}),grmr_sql:e=>{
+const n=e.regex,t=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],r=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=r,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!r.includes(e))),c={
+begin:n.concat(/\b/,n.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}}
+;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{
+$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:n,when:t}={})=>{const a=t
+;return n=n||[],e.map((e=>e.match(/\|\d+$/)||n.includes(e)?e:a(e)?e+"|0":e))
+})(l,{when:e=>e.length<3}),literal:a,type:i,
+built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"]
+},contains:[{begin:n.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/,
+keyword:l.concat(s),literal:a,type:i}},{className:"type",
+begin:n.either("double precision","large object","with timezone","without timezone")
+},c,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string",
+variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/,
+contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{
+className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,
+relevance:0}]}},grmr_swift:e=>{const n={match:/\s+/,relevance:0
+},t=e.COMMENT("/\\*","\\*/",{contains:["self"]}),a=[e.C_LINE_COMMENT_MODE,t],i={
+match:[/\./,m(...xe,...Me)],className:{2:"keyword"}},r={match:b(/\./,m(...Ae)),
+relevance:0},s=Ae.filter((e=>"string"==typeof e)).concat(["_|0"]),o={variants:[{
+className:"keyword",
+match:m(...Ae.filter((e=>"string"!=typeof e)).concat(Se).map(ke),...Me)}]},l={
+$pattern:m(/\b\w+/,/#\w+/),keyword:s.concat(Re),literal:Ce},c=[i,r,o],g=[{
+match:b(/\./,m(...De)),relevance:0},{className:"built_in",
+match:b(/\b/,m(...De),/(?=\()/)}],u={match:/->/,relevance:0},p=[u,{
+className:"operator",relevance:0,variants:[{match:Be},{match:`\\.(\\.|${Le})+`}]
+}],_="([0-9]_*)+",h="([0-9a-fA-F]_*)+",f={className:"number",relevance:0,
+variants:[{match:`\\b(${_})(\\.(${_}))?([eE][+-]?(${_}))?\\b`},{
+match:`\\b0x(${h})(\\.(${h}))?([pP][+-]?(${_}))?\\b`},{match:/\b0o([0-7]_*)+\b/
+},{match:/\b0b([01]_*)+\b/}]},E=(e="")=>({className:"subst",variants:[{
+match:b(/\\/,e,/[0\\tnr"']/)},{match:b(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}]
+}),y=(e="")=>({className:"subst",match:b(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/)
+}),N=(e="")=>({className:"subst",label:"interpol",begin:b(/\\/,e,/\(/),end:/\)/
+}),w=(e="")=>({begin:b(e,/"""/),end:b(/"""/,e),contains:[E(e),y(e),N(e)]
+}),v=(e="")=>({begin:b(e,/"/),end:b(/"/,e),contains:[E(e),N(e)]}),O={
+className:"string",
+variants:[w(),w("#"),w("##"),w("###"),v(),v("#"),v("##"),v("###")]
+},k=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,
+contains:[e.BACKSLASH_ESCAPE]}],x={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//,
+contains:k},M=e=>{const n=b(e,/\//),t=b(/\//,e);return{begin:n,end:t,
+contains:[...k,{scope:"comment",begin:`#(?!.*${t})`,end:/$/}]}},S={
+scope:"regexp",variants:[M("###"),M("##"),M("#"),x]},A={match:b(/`/,Fe,/`/)
+},C=[A,{className:"variable",match:/\$\d+/},{className:"variable",
+match:`\\$${ze}+`}],T=[{match:/(@|#(un)?)available/,scope:"keyword",starts:{
+contains:[{begin:/\(/,end:/\)/,keywords:Pe,contains:[...p,f,O]}]}},{
+scope:"keyword",match:b(/@/,m(...je))},{scope:"meta",match:b(/@/,Fe)}],R={
+match:d(/\b[A-Z]/),relevance:0,contains:[{className:"type",
+match:b(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,ze,"+")
+},{className:"type",match:Ue,relevance:0},{match:/[?!]+/,relevance:0},{
+match:/\.\.\./,relevance:0},{match:b(/\s+&\s+/,d(Ue)),relevance:0}]},D={
+begin:/,end:/>/,keywords:l,contains:[...a,...c,...T,u,R]};R.contains.push(D)
+;const I={begin:/\(/,end:/\)/,relevance:0,keywords:l,contains:["self",{
+match:b(Fe,/\s*:/),keywords:"_|0",relevance:0
+},...a,S,...c,...g,...p,f,O,...C,...T,R]},L={begin:/,end:/>/,
+keywords:"repeat each",contains:[...a,R]},B={begin:/\(/,end:/\)/,keywords:l,
+contains:[{begin:m(d(b(Fe,/\s*:/)),d(b(Fe,/\s+/,Fe,/\s*:/))),end:/:/,
+relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params",
+match:Fe}]},...a,...c,...p,f,O,...T,R,I],endsParent:!0,illegal:/["']/},$={
+match:[/(func|macro)/,/\s+/,m(A.match,Fe,Be)],className:{1:"keyword",
+3:"title.function"},contains:[L,B,n],illegal:[/\[/,/%/]},z={
+match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"},
+contains:[L,B,n],illegal:/\[|%/},F={match:[/operator/,/\s+/,Be],className:{
+1:"keyword",3:"title"}},U={begin:[/precedencegroup/,/\s+/,Ue],className:{
+1:"keyword",3:"title"},contains:[R],keywords:[...Te,...Ce],end:/}/}
+;for(const e of O.variants){const n=e.contains.find((e=>"interpol"===e.label))
+;n.keywords=l;const t=[...c,...g,...p,f,O,...C];n.contains=[...t,{begin:/\(/,
+end:/\)/,contains:["self",...t]}]}return{name:"Swift",keywords:l,
+contains:[...a,$,z,{beginKeywords:"struct protocol class extension enum actor",
+end:"\\{",excludeEnd:!0,keywords:l,contains:[e.inherit(e.TITLE_MODE,{
+className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...c]
+},F,U,{beginKeywords:"import",end:/$/,contains:[...a],relevance:0
+},S,...c,...g,...p,f,O,...C,...T,R,I]}},grmr_typescript:e=>{
+const n=Oe(e),t=_e,a=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],i={
+beginKeywords:"namespace",end:/\{/,excludeEnd:!0,
+contains:[n.exports.CLASS_REFERENCE]},r={beginKeywords:"interface",end:/\{/,
+excludeEnd:!0,keywords:{keyword:"interface extends",built_in:a},
+contains:[n.exports.CLASS_REFERENCE]},s={$pattern:_e,
+keyword:he.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]),
+literal:fe,built_in:ve.concat(a),"variable.language":we},o={className:"meta",
+begin:"@"+t},l=(e,n,t)=>{const a=e.contains.findIndex((e=>e.label===n))
+;if(-1===a)throw Error("can not find mode to replace");e.contains.splice(a,1,t)}
+;return Object.assign(n.keywords,s),
+n.exports.PARAMS_CONTAINS.push(o),n.contains=n.contains.concat([o,i,r]),
+l(n,"shebang",e.SHEBANG()),l(n,"use_strict",{className:"meta",relevance:10,
+begin:/^\s*['"]use strict['"]/
+}),n.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(n,{
+name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),n},grmr_vbnet:e=>{
+const n=e.regex,t=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,i=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,s={
+className:"literal",variants:[{begin:n.concat(/# */,n.either(a,t),/ *#/)},{
+begin:n.concat(/# */,r,/ *#/)},{begin:n.concat(/# */,i,/ *#/)},{
+begin:n.concat(/# */,n.either(a,t),/ +/,n.either(i,r),/ *#/)}]
+},o=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}]
+}),l=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]})
+;return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0,
+classNameAliases:{label:"symbol"},keywords:{
+keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield",
+built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort",
+type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort",
+literal:"true false nothing"},
+illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{
+className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/,
+end:/"/,illegal:/\n/,contains:[{begin:/""/}]},s,{className:"number",relevance:0,
+variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/
+},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{
+begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{
+className:"label",begin:/^\w+:/},o,l,{className:"meta",
+begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,
+end:/$/,keywords:{
+keyword:"const disable else elseif enable end externalsource if region then"},
+contains:[l]}]}},grmr_wasm:e=>{e.regex;const n=e.COMMENT(/\(;/,/;\)/)
+;return n.contains.push("self"),{name:"WebAssembly",keywords:{$pattern:/[\w.]+/,
+keyword:["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"]
+},contains:[e.COMMENT(/;;/,/$/),n,{match:[/(?:offset|align)/,/\s*/,/=/],
+className:{1:"keyword",3:"operator"}},{className:"variable",begin:/\$[\w_]+/},{
+match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},{
+begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword",
+3:"title.function"}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/,
+className:"type"},{className:"keyword",
+match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/
+},{className:"number",relevance:0,
+match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/
+}]}},grmr_xml:e=>{
+const n=e.regex,t=n.concat(/[\p{L}_]/u,n.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),a={
+className:"symbol",begin:/&[a-z]+;|[0-9]+;|[a-f0-9]+;/},i={begin:/\s/,
+contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]
+},r=e.inherit(i,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{
+className:"string"}),o=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),l={
+endsWithParent:!0,illegal:/,relevance:0,contains:[{className:"attr",
+begin:/[\p{L}0-9._:-]+/u,relevance:0},{begin:/=\s*/,relevance:0,contains:[{
+className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[a]},{
+begin:/'/,end:/'/,contains:[a]},{begin:/[^\s"'=<>`]+/}]}]}]};return{
+name:"HTML, XML",
+aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],
+case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,o,s,r,{begin:/\[/,end:/\]/,contains:[{
+className:"meta",begin://,contains:[i,r,o,s]}]}]
+},e.COMMENT(//,{relevance:10}),{begin://,
+relevance:10},a,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,
+relevance:10,contains:[o]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",
+begin:/
+
+
+
+
+
+
+
Total Logs: 0
+
Errors: 0
+
Warnings: 0
+
Last Updated: Never
+
+
+
+
🔄 Reload
+
Clear All
+
Auto-Refresh: OFF
+
+ All
+ Errors
+ Warnings
+ Logs
+
+
+
+
+
+
Waiting for console output...
+
To capture logs, visit another page on this site and use console.log(), console.error(), etc.
+
+ 📝 Add Test Log
+
+
+
+
+
+
+
+
+
+
diff --git a/site/site/_public/robots.txt b/site/site/_public/robots.txt
new file mode 100644
index 0000000..25a33bd
--- /dev/null
+++ b/site/site/_public/robots.txt
@@ -0,0 +1,14 @@
+User-agent: *
+Allow: /
+
+# Sitemap location
+Sitemap: {{base_url}}/sitemap.xml
+
+# Crawl-delay (optional, in seconds)
+# Crawl-delay: 1
+
+# Disallow specific paths (customize as needed)
+Disallow: /admin
+Disallow: /.rustelo/
+Disallow: /target/
+Disallow: /node_modules/
\ No newline at end of file
diff --git a/site/site/_public/styles/app.min.css b/site/site/_public/styles/app.min.css
new file mode 100644
index 0000000..f76d59a
--- /dev/null
+++ b/site/site/_public/styles/app.min.css
@@ -0,0 +1 @@
+*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x:;--un-pan-y:;--un-pinch-zoom:;--un-scroll-snap-strictness:proximity;--un-ordinal:;--un-slashed-zero:;--un-numeric-figure:;--un-numeric-spacing:;--un-numeric-fraction:;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset:;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset:;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur:;--un-brightness:;--un-contrast:;--un-drop-shadow:;--un-grayscale:;--un-hue-rotate:;--un-invert:;--un-saturate:;--un-sepia:;--un-backdrop-blur:;--un-backdrop-brightness:;--un-backdrop-contrast:;--un-backdrop-grayscale:;--un-backdrop-hue-rotate:;--un-backdrop-invert:;--un-backdrop-opacity:;--un-backdrop-saturate:;--un-backdrop-sepia:}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif}:root{--code-bg:#f8fafc;--code-border:#e2e8f0;--code-text:#334155;--code-comment:#64748b;--code-keyword:#0f172a;--code-string:#059669;--code-number:#dc2626;--code-function:#2563eb;--code-variable:#7c3aed;--inline-code-bg:#f1f5f9;--inline-code-text:#475569}.dark{--code-bg:#1e293b;--code-border:#334155;--code-text:#e2e8f0;--code-comment:#94a3b8;--code-keyword:#f8fafc;--code-string:#34d399;--code-number:#fca5a5;--code-function:#60a5fa;--code-variable:#a78bfa;--inline-code-bg:#374151;--inline-code-text:#d1d5db}pre{background-color:var(--code-bg) !important;border:1px solid var(--code-border);border-radius:var(--radius-md,0.375rem);padding:1.25rem 1.5rem;margin:1.5rem 0;overflow-x:auto;line-height:1.6;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;font-size:0.875rem;position:relative}pre code{background-color:transparent !important;padding:0 !important;border-radius:0;color:var(--code-text);font-size:inherit;font-weight:400}code:not(pre code){background-color:var(--inline-code-bg) !important;color:var(--inline-code-text) !important;padding:0.125rem 0.375rem;border-radius:var(--radius-sm,0.125rem);font-size:0.875em;font-weight:500;font-family:ui-monospace,SFMono-Regular,'SF Mono',Consolas,'Liberation Mono',Menlo,monospace;border:1px solid var(--code-border)}:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::before,:is([prose=""],.prose):where(:not(pre)>code):not(:where(.not-prose,.not-prose *))::after{content:"" !important}.hljs{background:var(--code-bg) !important;color:var(--code-text) !important}pre::-webkit-scrollbar{height:8px}pre::-webkit-scrollbar-track{background:transparent}pre::-webkit-scrollbar-thumb{background-color:var(--code-border);border-radius:4px}pre::-webkit-scrollbar-thumb:hover{background-color:var(--code-comment)}.dark pre::-webkit-scrollbar-thumb{background-color:var(--color-neutral-600)}.dark pre::-webkit-scrollbar-thumb:hover{background-color:var(--color-neutral-500)}.prose pre,[prose=""] pre{margin:2rem 0 !important;line-height:1.7}.prose code,[prose=""] code{font-size:0.875em}@media (max-width:768px){pre{padding:1rem;margin:1rem -1rem;border-radius:0;border-left:none;border-right:none}}pre:focus-within{outline:2px solid var(--color-brand-primary);outline-offset:2px}code:focus{outline:2px solid var(--color-brand-primary);outline-offset:1px}pre code.hljs{background:transparent !important}.filter-selected{background-color:#2563eb !important;border-color:#2563eb !important;color:#ffffff !important}.filter-hidden{display:none !important}.i-carbon-moon{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M13.503 5.414a15.076 15.076 0 0 0 11.593 18.194a11.1 11.1 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1 1 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.07 13.07 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-settings{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M27 16.76v-1.53l1.92-1.68A2 2 0 0 0 29.3 11l-2.36-4a2 2 0 0 0-1.73-1a2 2 0 0 0-.64.1l-2.43.82a11 11 0 0 0-1.31-.75l-.51-2.52a2 2 0 0 0-2-1.61h-4.68a2 2 0 0 0-2 1.61l-.51 2.52a11.5 11.5 0 0 0-1.32.75l-2.38-.86A2 2 0 0 0 6.79 6a2 2 0 0 0-1.73 1L2.7 11a2 2 0 0 0 .41 2.51L5 15.24v1.53l-1.89 1.68A2 2 0 0 0 2.7 21l2.36 4a2 2 0 0 0 1.73 1a2 2 0 0 0 .64-.1l2.43-.82a11 11 0 0 0 1.31.75l.51 2.52a2 2 0 0 0 2 1.61h4.72a2 2 0 0 0 2-1.61l.51-2.52a11.5 11.5 0 0 0 1.32-.75l2.42.82a2 2 0 0 0 .64.1a2 2 0 0 0 1.73-1l2.28-4a2 2 0 0 0-.41-2.51ZM25.21 24l-3.43-1.16a8.9 8.9 0 0 1-2.71 1.57L18.36 28h-4.72l-.71-3.55a9.4 9.4 0 0 1-2.7-1.57L6.79 24l-2.36-4l2.72-2.4a8.9 8.9 0 0 1 0-3.13L4.43 12l2.36-4l3.43 1.16a8.9 8.9 0 0 1 2.71-1.57L13.64 4h4.72l.71 3.55a9.4 9.4 0 0 1 2.7 1.57L25.21 8l2.36 4l-2.72 2.4a8.9 8.9 0 0 1 0 3.13L27.57 20Z'/%3E%3Cpath fill='currentColor' d='M16 22a6 6 0 1 1 6-6a5.94 5.94 0 0 1-6 6m0-10a3.91 3.91 0 0 0-4 4a3.91 3.91 0 0 0 4 4a3.91 3.91 0 0 0 4-4a3.91 3.91 0 0 0-4-4'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}.i-carbon-sun{--un-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1.2em' height='1.2em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6M5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E");-webkit-mask:var(--un-icon) no-repeat;mask:var(--un-icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit;width:1.2em;height:1.2em}:is(.prose),:is([prose=""]){color:var(--un-prose-body);max-width:65ch;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-lead);font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}:where(a):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-links);text-decoration:underline;font-weight:500}:where(strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bold);font-weight:600}:where(a strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol[type="A"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="A" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-alpha}:where(ol[type="a" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-alpha}:where(ol[type="I"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="I" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:upper-roman}:where(ol[type="i" s]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:lower-roman}:where(ol[type="1"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:decimal}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){list-style-type:disc;margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em}:where(ol>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:400;color:var(--un-prose-counters)}:where(ul>li::marker):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-bullets)}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.25em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-color:var(--un-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-style:italic;color:var(--un-prose-quotes);border-inline-start-width:0.25rem;border-inline-start-color:var(--un-prose-quote-borders);quotes:"\201C""\201D""\2018""\2019";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em}:where(blockquote p:first-of-type::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:open-quote}:where(blockquote p:last-of-type::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:close-quote}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:0.8888889em;line-height:1.1111111}:where(h1 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:900;color:inherit}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:700;font-size:1.5em;margin-top:2em;margin-bottom:1em;line-height:1.3333333}:where(h2 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:800;color:inherit}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;font-size:1.25em;margin-top:1.6em;margin-bottom:0.6em;line-height:1.6}:where(h3 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;margin-top:1.5em;margin-bottom:0.5em;line-height:1.5}:where(h4 strong):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:700;color:inherit}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){display:block;margin-top:2em;margin-bottom:2em}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-weight:500;font-family:inherit;color:var(--un-prose-kbd);box-shadow:0 0 0 1px rgb(var(--un-prose-kbd-shadows) / 10%),0 3px 0 rgb(var(--un-prose-kbd-shadows) / 10%);font-size:0.875em;border-radius:0.3125rem;padding-top:0.1875em;padding-inline-end:0.375em;padding-bottom:0.1875em;padding-inline-start:0.375em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-code);font-weight:600;font-size:0.875em}:where(code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:"`"}:where(a code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h1 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.875em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit;font-size:0.9em}:where(h4 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(blockquote code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(thead th code):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:inherit}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-pre-code);background-color:var(--un-prose-pre-bg);overflow-x:auto;font-weight:400;font-size:0.875em;line-height:1.7142857;margin-top:1.7142857em;margin-bottom:1.7142857em;border-radius:0.375rem;padding-top:0.8571429em;padding-inline-end:1.1428571em;padding-bottom:0.8571429em;padding-inline-start:1.1428571em}:where(pre code):not(:where([class~="not-prose"],[class~="not-prose"] *)){background-color:transparent;border-width:0;border-radius:0;padding:0;font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}:where(pre code::before):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(pre code::after):not(:where([class~="not-prose"],[class~="not-prose"] *)){content:none}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){width:100%;table-layout:auto;margin-top:2em;margin-bottom:2em;font-size:0.875em;line-height:1.7142857}:where(thead):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-th-borders)}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-headings);font-weight:600;vertical-align:bottom;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody tr):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:1px;border-bottom-color:var(--un-prose-td-borders)}:where(tbody tr:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-bottom-width:0}:where(tbody td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:baseline}:where(tfoot):not(:where([class~="not-prose"],[class~="not-prose"] *)){border-top-width:1px;border-top-color:var(--un-prose-th-borders)}:where(tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){vertical-align:top}:where(th,td):not(:where([class~="not-prose"],[class~="not-prose"] *)){text-align:start}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){color:var(--un-prose-captions);font-size:0.875em;line-height:1.4285714;margin-top:0.8571429em}font-size:1rem;line-height:1.75;:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;margin-bottom:0.5em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.375em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.25em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.75em;margin-bottom:0.75em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.25em;margin-bottom:1.25em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.5em;padding-inline-start:1.625em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.5714286em;padding-inline-end:0.5714286em;padding-bottom:0.5714286em;padding-inline-start:0.5714286em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:2em;margin-bottom:2em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}--un-prose-body:#374151;--un-prose-invert-body:#d1d5db;--un-prose-headings:#111827;--un-prose-invert-headings:white;--un-prose-lead:#4b5563;--un-prose-invert-lead:#9ca3af;--un-prose-links:#111827;--un-prose-invert-links:white;--un-prose-bold:#111827;--un-prose-invert-bold:white;--un-prose-counters:#6b7280;--un-prose-invert-counters:#9ca3af;--un-prose-bullets:#d1d5db;--un-prose-invert-bullets:#4b5563;--un-prose-hr:#e5e7eb;--un-prose-invert-hr:#374151;--un-prose-quotes:#111827;--un-prose-invert-quotes:#f3f4f6;--un-prose-quote-borders:#e5e7eb;--un-prose-invert-quote-borders:#374151;--un-prose-captions:#6b7280;--un-prose-invert-captions:#9ca3af;--un-prose-kbd:#111827;--un-prose-invert-kbd:white;--un-prose-kbd-shadows:#111827;--un-prose-invert-kbd-shadows:white;--un-prose-code:#111827;--un-prose-invert-code:white;--un-prose-pre-code:#e5e7eb;--un-prose-invert-pre-code:#d1d5db;--un-prose-pre-bg:#1f2937;--un-prose-invert-pre-bg:rgb(0 0 0 / 50%);--un-prose-th-borders:#d1d5db;--un-prose-invert-th-borders:#4b5563;--un-prose-td-borders:#e5e7eb;--un-prose-invert-td-borders:#374151}:is(.prose-lg),:is([prose-lg=""]){font-size:1.125rem;line-height:1.7777778;:where(p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where([class~="lead"]):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.2222222em;line-height:1.4545455;margin-top:1.0909091em;margin-bottom:1.0909091em}:where(blockquote):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.6666667em;margin-bottom:1.6666667em;padding-inline-start:1em}:where(h1):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:2.6666667em;margin-top:0;margin-bottom:0.8333333em;line-height:1}:where(h2):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.6666667em;margin-top:1.8666667em;margin-bottom:1.0666667em;line-height:1.3333333}:where(h3):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:1.3333333em;margin-top:1.6666667em;margin-bottom:0.6666667em;line-height:1.5}:where(h4):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:0.4444444em;line-height:1.5555556}:where(img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(picture>img):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(video):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(kbd):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;border-radius:0.3125rem;padding-top:0.2222222em;padding-inline-end:0.4444444em;padding-bottom:0.2222222em;padding-inline-start:0.4444444em}:where(code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em}:where(h2 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8666667em}:where(h3 code):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.875em}:where(pre):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.75;margin-top:2em;margin-bottom:2em;border-radius:0.375rem;padding-top:1em;padding-inline-end:1.5em;padding-bottom:1em;padding-inline-start:1.5em}:where(ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(ul):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em;padding-inline-start:1.5555556em}:where(li):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;margin-bottom:0.6666667em}:where(ol>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(ul>li):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0.4444444em}:where(>ul>li p):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(>ul>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ul>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(>ol>li>p:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(>ol>li>p:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:1.3333333em}:where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.8888889em;margin-bottom:0.8888889em}:where(dl):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em;margin-bottom:1.3333333em}:where(dt):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.3333333em}:where(dd):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0.6666667em;padding-inline-start:1.5555556em}:where(hr):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:3.1111111em;margin-bottom:3.1111111em}:where(hr+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h2+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h3+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(h4+*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(table):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5}:where(thead th):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(thead th:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(thead th:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(tbody td,tfoot td):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-top:0.75em;padding-inline-end:0.75em;padding-bottom:0.75em;padding-inline-start:0.75em}:where(tbody td:first-child,tfoot td:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-start:0}:where(tbody td:last-child,tfoot td:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){padding-inline-end:0}:where(figure):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:1.7777778em;margin-bottom:1.7777778em}:where(figure>*):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0;margin-bottom:0}:where(figcaption):not(:where([class~="not-prose"],[class~="not-prose"] *)){font-size:0.8888889em;line-height:1.5;margin-top:1em}:where(>:first-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-top:0}:where(>:last-child):not(:where([class~="not-prose"],[class~="not-prose"] *)){margin-bottom:0}}.dark:is(.dark\:prose-invert){--un-prose-body:var(--un-prose-invert-body);--un-prose-headings:var(--un-prose-invert-headings);--un-prose-lead:var(--un-prose-invert-lead);--un-prose-links:var(--un-prose-invert-links);--un-prose-bold:var(--un-prose-invert-bold);--un-prose-counters:var(--un-prose-invert-counters);--un-prose-bullets:var(--un-prose-invert-bullets);--un-prose-hr:var(--un-prose-invert-hr);--un-prose-quotes:var(--un-prose-invert-quotes);--un-prose-quote-borders:var(--un-prose-invert-quote-borders);--un-prose-captions:var(--un-prose-invert-captions);--un-prose-kbd:var(--un-prose-invert-kbd);--un-prose-kbd-shadows:var(--un-prose-invert-kbd-shadows);--un-prose-code:var(--un-prose-invert-code);--un-prose-pre-code:var(--un-prose-invert-pre-code);--un-prose-pre-bg:var(--un-prose-invert-pre-bg);--un-prose-th-borders:var(--un-prose-invert-th-borders);--un-prose-td-borders:var(--un-prose-invert-td-borders)}.-container{width:-100%}.container,[container=""]{width:100%}.btn:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}[btn=""]:disabled{pointer-events:none;cursor:default;--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity));opacity:0.5 !important}.ds-btn:disabled{pointer-events:none;opacity:0.5}[ds-btn=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-content-category-filter:disabled{pointer-events:none;opacity:0.5}.ds-btn-ghost:disabled{pointer-events:none;opacity:0.5}[ds-btn-ghost=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-primary:disabled{pointer-events:none;opacity:0.5}[ds-btn-primary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-secondary:disabled{pointer-events:none;opacity:0.5}[ds-btn-secondary=""]:disabled{pointer-events:none;opacity:0.5}.ds-btn-sm:disabled{pointer-events:none;opacity:0.5}[ds-btn-sm=""]:disabled{pointer-events:none;opacity:0.5}.ds-theme-toggle:disabled{pointer-events:none;opacity:0.5}[ds-theme-toggle=""]:disabled{pointer-events:none;opacity:0.5}.ds-card-text,[ds-card-text=""]{margin-left:auto;margin-right:auto;margin-bottom:var(--space-4);max-width:42rem;color:var(--color-neutral-600);color:var(--text-base);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-container,[ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-container-lg{margin-left:auto;margin-right:auto;max-width:72rem;padding-left:var(--space-4);padding-right:var(--space-4)}.mx-auto,[mx-auto=""]{margin-left:auto;margin-right:auto}.ds-body,[ds-body=""]{margin-bottom:var(--space-4);color:var(--text-base);color:var(--color-neutral-600);line-height:var(--leading-normal);line-height:var(--leading-relaxed)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-3);margin-bottom:0.5rem;margin-bottom:var(--space-4,1rem);text-wrap:balance;color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.ds-heading-1{margin-bottom:var(--space-6);color:var(--text-4xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-bold)}.ds-heading-2{margin-bottom:var(--space-4);color:var(--text-3xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-3,[ds-heading-3=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.ds-heading-4{margin-bottom:var(--space-3);color:var(--text-xl);color:var(--color-neutral-900);line-height:var(--leading-relaxed);font-family:var(--font-semibold)}.btn,[btn=""]{display:inline-block;cursor:pointer;border-radius:0.375rem;background-color:var(--c-primary);padding-left:1rem;padding-right:1rem;padding-top:0.25rem;padding-bottom:0.25rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));letter-spacing:0.025em;opacity:0.9}.ds-input,[ds-input=""]{width:100%;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-select,[ds-select=""]{width:100%;cursor:pointer;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-textarea{width:100%;min-height:6rem;resize:vertical;border-width:1px;border-color:var(--color-neutral-300);border-radius:var(--radius-md);background-color:var(--color-neutral-50);padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.nav-link{display:flex;align-items:center;column-gap:var(--space-2);padding:var(--space-1);color:var(--text-sm);color:var(--color-neutral-600);font-family:var(--font-sans);text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-badge{display:inline-flex;align-items:center;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-ghost{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-neutral-100);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-badge-outline{display:inline-flex;align-items:center;border-width:1px;border-color:var(--color-neutral-300);border-radius:9999px;background-color:transparent;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;color:var(--color-neutral-700);font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-btn,[ds-btn=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-style:none;font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-content-category-filter,.ds-btn-ghost,[ds-btn-ghost=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:transparent;padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-700);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-primary,[ds-btn-primary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-brand-primary);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-50);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-secondary,[ds-btn-secondary=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;background-color:var(--color-neutral-200);padding-left:var(--space-4);padding-right:var(--space-4);padding-top:var(--space-2);padding-bottom:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-btn-sm,[ds-btn-sm=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-radius:var(--radius-md);border-style:none;padding-left:var(--space-3);padding-right:var(--space-3);padding-top:var(--space-1-5);padding-bottom:var(--space-1-5);color:var(--text-sm);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.ds-card-featured,[ds-card-featured=""]{display:inline-flex;align-items:flex-end;border-radius:9999px;padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-theme-toggle,[ds-theme-toggle=""]{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);border-style:none;background-color:var(--color-neutral-50);padding:var(--space-2);color:var(--text-sm);color:var(--color-neutral-900);font-family:var(--font-medium);text-decoration:none;transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms;transition-duration:200ms}.hover\:ds-badge-accent:hover{display:inline-flex;align-items:center;border-radius:9999px;background-color:var(--color-success);padding-left:0.625rem;padding-right:0.625rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem;--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));font-weight:500;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.ds-card,[ds-card=""]{border-width:1px;border-color:var(--color-neutral-200);border-radius:var(--radius-lg);background-color:var(--color-neutral-50);padding:var(--space-6);--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.dark .ds-badge-outline{border-color:var(--color-neutral-600);color:var(--color-neutral-300)}.dark .dark .dark\:ds-border,.dark .ds-border,.dark [ds-border=""]{border-color:var(--color-neutral-700)}.dark .dark\:ds-border,.ds-border,[ds-border=""]{border-color:var(--color-neutral-200)}.dark .ds-card,.dark .ds-input,.dark .ds-select,.dark .ds-textarea,.dark [ds-card=""],.dark [ds-input=""],.dark [ds-select=""]{border-color:var(--color-neutral-600);background-color:var(--color-neutral-800)}.dark .ds-theme-toggle,.dark [ds-theme-toggle=""]{border-color:var(--color-neutral-700);background-color:var(--color-neutral-900);color:var(--color-neutral-50)}.dark .hover\:ds-border:hover{border-color:var(--color-neutral-700)}.hover\:ds-border:hover{border-color:var(--color-neutral-200)}.ds-input:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-input=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-select:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}[ds-select=""]:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-textarea:focus{border-color:var(--color-brand-primary);outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-color:var(--color-brand-primary)}.ds-rounded{border-radius:var(--radius-base)}.ds-rounded-lg,[ds-rounded-lg=""]{border-radius:var(--radius-lg)}.ds-rounded-md,[ds-rounded-md=""]{border-radius:var(--radius-md)}.ds-rounded-xl{border-radius:var(--radius-xl)}.round,.rounded-full{border-radius:9999px}.rounded,[rounded=""]{border-radius:0.375rem}.rounded-2xl{border-radius:1rem}.rounded-lg,[rounded-lg=""]{border-radius:0.5rem}.rounded-xl{border-radius:0.75rem}.dark .ds-badge-ghost{background-color:var(--color-neutral-800);color:var(--color-neutral-300)}.dark .ds-bg,.dark [ds-bg=""]{background-color:var(--color-neutral-950)}.ds-bg,[ds-bg=""]{background-color:var(--color-neutral-50)}.dark .ds-bg-page,.dark [ds-bg-page=""]{background-color:var(--color-neutral-900)}.ds-bg-page,.ds-bg-secondary,[ds-bg-page=""]{background-color:var(--color-neutral-100)}.dark .ds-bg-secondary{background-color:var(--color-neutral-800)}.hover\:ds-badge-accent:hover:hover{background-color:var(--color-success)}.dark .ds-badge-ghost:hover{background-color:var(--color-neutral-700)}.ds-badge-ghost:hover{background-color:var(--color-neutral-200)}.dark .ds-badge-outline:hover{background-color:var(--color-neutral-800)}.ds-badge-outline:hover{background-color:var(--color-neutral-100)}.hover\:ds-badge-primary-focus:hover{background-color:var(--color-brand-primary);--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.dark .hover\:ds-bg:hover{background-color:var(--color-neutral-950)}.dark [hover\:ds-bg=""]:hover{background-color:var(--color-neutral-950)}.hover\:ds-bg:hover{background-color:var(--color-neutral-50)}[hover\:ds-bg=""]:hover{background-color:var(--color-neutral-50)}.dark .ds-btn-content-category-filter:hover{background-color:var(--color-neutral-800)}.ds-btn-content-category-filter:hover{background-color:var(--color-neutral-100)}.dark .ds-btn-ghost:hover{background-color:var(--color-neutral-800)}.dark [ds-btn-ghost=""]:hover{background-color:var(--color-neutral-800)}.ds-btn-ghost:hover{background-color:var(--color-neutral-100)}[ds-btn-ghost=""]:hover{background-color:var(--color-neutral-100)}.ds-btn-primary:hover{background-color:var(--color-brand-primary)}[ds-btn-primary=""]:hover{background-color:var(--color-brand-primary)}.ds-btn-secondary:hover{background-color:var(--color-neutral-300)}[ds-btn-secondary=""]:hover{background-color:var(--color-neutral-300)}.dark .ds-theme-toggle:hover{background-color:var(--color-neutral-800)}.dark [ds-theme-toggle=""]:hover{background-color:var(--color-neutral-800)}.ds-theme-toggle:hover{background-color:var(--color-neutral-100)}[ds-theme-toggle=""]:hover{background-color:var(--color-neutral-100)}.ds-badge-sm{padding-left:0.5rem;padding-right:0.5rem;padding-top:0.125rem;padding-bottom:0.125rem;font-size:0.75rem;line-height:1rem}.text-center,[text-center=""]{text-align:center}.text-left,[text-left=""]{text-align:left}.text-right{text-align:right}.dark .ds-body,.dark .ds-card-text,.dark .ds-text-secondary,.dark .nav-link,.dark [ds-body=""],.dark [ds-card-text=""],.dark [ds-text-secondary=""]{color:var(--color-neutral-400)}.dark .ds-btn-content-category-filter,.dark .ds-btn-ghost,.dark [ds-btn-ghost=""]{color:var(--color-neutral-300)}.dark .ds-caption,.dark .ds-text-muted,.dark [ds-caption=""],.dark [ds-text-muted=""],.ds-text-muted,[ds-text-muted=""],.group:hover .dark .group-hover\:ds-text-muted,.group:hover .group-hover\:ds-text-muted{color:var(--color-neutral-500)}.ds-caption,[ds-caption=""]{color:var(--text-sm);color:var(--color-neutral-500);line-height:var(--leading-normal)}.dark .dark .dark\:ds-text,.dark .ds-card-title,.dark .ds-heading-1,.dark .ds-heading-2,.dark .ds-heading-3,.dark .ds-heading-4,.dark .ds-text,.dark [ds-card-title=""],.dark [ds-heading-3=""],.dark [ds-text=""]{color:var(--color-neutral-50)}.dark .dark\:ds-text,.ds-text,[ds-text=""]{color:var(--color-neutral-900)}.ds-text-secondary,[ds-text-secondary=""]{color:var(--color-neutral-600)}.dark .hover\:ds-text:hover{color:var(--color-neutral-50)}.hover\:ds-text:hover{color:var(--color-neutral-900)}.dark .hover\:ds-text-secondary:hover{color:var(--color-neutral-400)}.hover\:ds-text-secondary:hover{color:var(--color-neutral-600)}.nav-link:hover{color:var(--color-brand-primary)}.dark .placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}.dark [placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.placeholder-ds-text-muted::placeholder{color:var(--color-neutral-500)}[placeholder-ds-text-muted=""]::placeholder{color:var(--color-neutral-500)}.btn:hover{opacity:1}[btn=""]:hover{opacity:1}.ds-shadow-lg,[ds-shadow-lg=""]{--un-shadow:var(--shadow-lg);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-md{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-shadow-sm,[ds-shadow-sm=""]{--un-shadow:var(--shadow-sm);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow,[shadow=""]{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-lg,[shadow-lg=""]{--un-shadow:var(--un-shadow-inset) 0 10px 15px -3px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 4px 6px -4px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-none{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-sm{--un-shadow:var(--un-shadow-inset) 0 1px 2px 0 var(--un-shadow-color,rgb(0 0 0 / 0.05));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-xl{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:ds-shadow-md:hover{--un-shadow:var(--shadow-md);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-none:hover{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.hover\:shadow-xl:hover{--un-shadow:var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.disabled\:shadow-none:disabled{--un-shadow:0 0 var(--un-shadow-color,rgb(0 0 0 / 0));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.ds-btn:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-content-category-filter:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-ghost:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-ghost=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-btn-primary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-btn-primary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}.ds-btn-secondary:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}[ds-btn-secondary=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-neutral-200)}.ds-btn-sm:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}[ds-btn-sm=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px}.ds-theme-toggle:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}[ds-theme-toggle=""]:focus{outline:2px solid transparent;outline-offset:2px;--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow);--un-ring-offset-width:2px;--un-ring-color:var(--color-brand-primary)}@media (min-width:640px){.-container{max-width:-640px}.container,[container=""]{max-width:640px}.sm\:ds-container,[sm\:ds-container=""]{margin-left:auto;margin-right:auto;max-width:80rem;padding-left:var(--space-4);padding-right:var(--space-4)}.ds-card-title,[ds-card-title=""]{margin-bottom:var(--space-4);color:var(--text-2xl);color:var(--color-neutral-900);line-height:var(--leading-tight);font-family:var(--font-semibold)}.dark .ds-card-title,.dark [ds-card-title=""]{color:var(--color-neutral-50)}}@media (min-width:768px){.-container{max-width:-768px}.container,[container=""]{max-width:768px}}@media (min-width:1024px){.-container{max-width:-1024px}.container,[container=""]{max-width:1024px}.lg\:mx-auto,[lg\:mx-auto=""]{margin-left:auto;margin-right:auto}.dark .lg\:ds-border{border-color:var(--color-neutral-700)}.lg\:ds-border{border-color:var(--color-neutral-200)}}@media (min-width:1280px){.-container{max-width:-1280px}.container,[container=""]{max-width:1280px}}@media (min-width:1536px){.-container{max-width:-1536px}.container,[container=""]{max-width:1536px}}:root,[data-theme]{background-color:hsl(var(--b1) / var(--un-bg-opacity,1));color:hsl(var(--bc) / var(--un-text-opacity,1))}html{-webkit-tap-highlight-color:transparent}.alert{display:grid;width:100%;grid-auto-flow:row;align-content:flex-start;align-items:center;justify-items:center;gap:1rem;text-align:center;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));padding:1rem;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem);--alert-bg:hsl(var(--b2));--alert-bg-mix:hsl(var(--b1));background-color:var(--alert-bg)}.alert{grid-auto-flow:column;grid-template-columns:auto minmax(auto,1fr);justify-items:start;text-align:left}.avatar{position:relative;display:inline-flex}.avatar>div{display:block;aspect-ratio:1 / 1;overflow:hidden}.avatar img{height:100%;width:100%;-o-object-fit:cover;object-fit:cover}.avatar.placeholder>div{display:flex;align-items:center;justify-content:center}.avatar.online:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.avatar.offline:before{content:"";position:absolute;z-index:10;display:block;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));outline-style:solid;outline-width:2px;outline-color:hsl(var(--b1) / 1);width:15%;height:15%;top:7%;right:7%}.badge{display:inline-flex;align-items:center;justify-content:center;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;height:1.25rem;font-size:0.875rem;line-height:1.25rem;width:-moz-fit-content;width:fit-content;padding-left:0.563rem;padding-right:0.563rem;border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-badge,1.9rem)}.breadcrumbs{max-width:100%;overflow-x:auto;padding-top:0.5rem;padding-bottom:0.5rem}.breadcrumbs>ul,.breadcrumbs>ol{display:flex;align-items:center;white-space:nowrap;min-height:-moz-min-content;min-height:min-content}.breadcrumbs>ul>li,.breadcrumbs>ol>li{display:flex;align-items:center}.breadcrumbs>ul>li>a,.breadcrumbs>ol>li>a{display:flex;cursor:pointer;align-items:center}.breadcrumbs>ul>li>a:hover,.breadcrumbs>ol>li>a:hover{text-decoration-line:underline}.breadcrumbs>ul>li>a:focus,.breadcrumbs>ol>li>a:focus{outline:2px solid transparent;outline-offset:2px}.breadcrumbs>ul>li>a:focus-visible,.breadcrumbs>ol>li>a:focus-visible{outline:2px solid currentColor;outline-offset:2px}.breadcrumbs>ul>li+*:before,.breadcrumbs>ol>li+*:before{content:"";margin-left:0.5rem;margin-right:0.75rem;display:block;height:0.375rem;width:0.375rem;--un-rotate:45deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));opacity:0.4;border-top:1px solid;border-right:1px solid;background-color:transparent}[dir="rtl"] .breadcrumbs>ul>li+*:before,[dir="rtl"] .breadcrumbs>ol>li+*:before{--un-rotate:-45deg}.link-hover:hover{text-decoration-line:underline}.link-primary:hover{--un-text-opacity:1;color:hsl(var(--pf) / var(--un-text-opacity))}.link-secondary:hover{--un-text-opacity:1;color:hsl(var(--sf) / var(--un-text-opacity))}.link-accent:hover{--un-text-opacity:1;color:hsl(var(--af) / var(--un-text-opacity))}.link-neutral:hover{--un-text-opacity:1;color:hsl(var(--nf) / var(--un-text-opacity))}.link-success:hover{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info:hover{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning:hover{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error:hover{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link{cursor:pointer;text-decoration-line:underline}.link-hover{text-decoration-line:none}.link-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.link-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.link-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.link-neutral{--un-text-opacity:1;color:hsl(var(--n) / var(--un-text-opacity))}.link-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.link-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.link-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.link-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.link:focus{outline:2px solid transparent;outline-offset:2px}.link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.label a:hover{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.label{display:flex;-webkit-user-select:none;-moz-user-select:none;user-select:none;align-items:center;justify-content:space-between;padding-left:0.25rem;padding-right:0.25rem;padding-top:0.5rem;padding-bottom:0.5rem}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(.active):hover,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(.active):hover{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu{display:flex;flex-direction:column;flex-wrap:wrap;font-size:0.875rem;line-height:1.25rem;padding:0.5rem}.menu:where(li ul){position:relative;white-space:nowrap;margin-left:1rem;padding-left:0.5rem}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){display:grid;grid-auto-flow:column;align-content:flex-start;align-items:center;gap:0.5rem;grid-auto-columns:minmax(auto,max-content) auto max-content;-webkit-user-select:none;-moz-user-select:none;user-select:none}.menu li.disabled{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none;color:hsl(var(--bc) / 0.3)}.menu:where(li>.menu-dropdown:not(.menu-dropdown-show)){display:none}:where(.menu li){position:relative;display:flex;flex-shrink:0;flex-direction:column;flex-wrap:wrap;align-items:stretch}:where(.menu li) .badge{justify-self:end}:where(.menu li:empty){background-color:hsl(var(--bc) / 0.1);margin:0.5rem 1rem;height:1px}.menu:where(li ul):before{position:absolute;bottom:0.75rem;left:0px;top:0.75rem;width:1px;background-color:hsl(var(--bc) / 0.1);content:""}.menu:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;text-align:left;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms;border-radius:var(--rounded-btn,0.5rem);text-wrap:balance}:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>*:not(ul):not(details):not(.menu-title)):is(summary):not(.active):focus-visible,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active).focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):not(summary):not(.active):focus,:where(.menu li:not(.menu-title):not(.disabled)>details>summary:not(.menu-title)):is(summary):not(.active):focus-visible{cursor:pointer;background-color:hsl(var(--bc) / 0.1);--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));outline:2px solid transparent;outline-offset:2px}.menu li>*:not(ul):not(.menu-title):not(details):active,.menu li>*:not(ul):not(.menu-title):not(details).active,.menu li>details>summary:active{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.menu:where(li>details>summary)::-webkit-details-marker{display:none}.menu:where(li>details>summary):after,.menu:where(li>.menu-dropdown-toggle):after{justify-self:end;display:block;margin-top:-0.5rem;height:0.5rem;width:0.5rem;transform:rotate(45deg);transition-property:transform,margin-top;transition-duration:0.3s;transition-timing-function:cubic-bezier(0.4,0,0.2,1);content:"";transform-origin:75% 75%;box-shadow:2px 2px;pointer-events:none}.menu:where(li>details[open]>summary):after,.menu:where(li>.menu-dropdown-toggle.menu-dropdown-show):after{transform:rotate(225deg);margin-top:0}.tab:hover{--un-text-opacity:1}.tab[disabled],.tab[disabled]:hover{cursor:not-allowed;color:hsl(var(--bc) / var(--un-text-opacity));--un-text-opacity:0.2}.tab{position:relative;display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;height:2rem;font-size:0.875rem;line-height:1.25rem;line-height:2;--tab-padding:1rem;--un-text-opacity:0.5;--tab-color:hsl(var(--bc) / var(--un-text-opacity,1));--tab-bg:hsl(var(--b1) / var(--un-bg-opacity,1));--tab-border-color:hsl(var(--b3) / var(--un-bg-opacity,1));color:var(--tab-color);padding-left:var(--tab-padding,1rem);padding-right:var(--tab-padding,1rem)}.tab.tab-active:not(.tab-disabled):not([disabled]){border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:1;--un-text-opacity:1}.tab:focus{outline:2px solid transparent;outline-offset:2px}.tab:focus-visible{outline:2px solid currentColor;outline-offset:-3px}.tab:focus-visible.tab-lifted{border-bottom-right-radius:var(--tab-radius,0.5rem);border-bottom-left-radius:var(--tab-radius,0.5rem)}.btn-circle{height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-xs){height:1.5rem;width:1.5rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-sm){height:2rem;width:2rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-md){height:3rem;width:3rem;border-radius:9999px;padding:0px}.btn-circle:where(.btn-lg){height:4rem;width:4rem;border-radius:9999px;padding:0px}.card{position:relative;display:flex;flex-direction:column;border-radius:var(--rounded-box,1rem)}.card:focus{outline:2px solid transparent;outline-offset:2px}.card figure{display:flex;align-items:center;justify-content:center}.card.image-full{display:grid}.card.image-full:before{position:relative;content:"";z-index:10;--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));opacity:0.75;border-radius:var(--rounded-box,1rem)}.card.image-full:before,.card.image-full>*{grid-column-start:1;grid-row-start:1}.card.image-full>figure img{height:100%;-o-object-fit:cover;object-fit:cover}.card.image-full>.card-body{position:relative;z-index:20;--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.card:where(figure:first-child){overflow:hidden;border-start-start-radius:inherit;border-start-end-radius:inherit;border-end-start-radius:unset;border-end-end-radius:unset}.card:where(figure:last-child){overflow:hidden;border-start-start-radius:unset;border-start-end-radius:unset;border-end-start-radius:inherit;border-end-end-radius:inherit}.card:focus-visible{outline:2px solid currentColor;outline-offset:2px}.card.bordered{border-width:1px;--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.card.compact .card-body{padding:1rem;font-size:0.875rem;line-height:1.25rem}.card.image-full:where(figure){overflow:hidden;border-radius:inherit}.chat{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));-moz-column-gap:0.75rem;column-gap:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem}.checkbox{flex-shrink:0;--chkbg:var(--bc);--chkfg:var(--b1);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;border-radius:var(--rounded-btn,0.5rem)}.checkbox:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.checkbox:checked,.checkbox[checked="true"],.checkbox[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(-45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(-45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( 45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(-45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.checkbox:indeterminate{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));background-repeat:no-repeat;animation:checkmark var(--animation-input,0.2s) ease-out;background-image:linear-gradient(90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient(-90deg,transparent 80%,hsl(var(--chkbg)) 80%),linear-gradient( 0deg,hsl(var(--chkbg)) 43%,hsl(var(--chkfg)) 43%,hsl(var(--chkfg)) 57%,hsl(var(--chkbg)) 57% )}.checkbox:disabled{cursor:not-allowed;border-color:transparent;--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));opacity:0.2}[dir="rtl"] .checkbox:checked,[dir="rtl"] .checkbox[checked="true"],[dir="rtl"] .checkbox[aria-checked="true"]{background-image:linear-gradient(45deg,transparent 65%,hsl(var(--chkbg)) 65.99%),linear-gradient(-45deg,transparent 75%,hsl(var(--chkbg)) 75.99%),linear-gradient(45deg,hsl(var(--chkbg)) 40%,transparent 40.99%),linear-gradient( -45deg,hsl(var(--chkbg)) 30%,hsl(var(--chkfg)) 30.99%,hsl(var(--chkfg)) 40%,transparent 40.99% ),linear-gradient(45deg,hsl(var(--chkfg)) 50%,hsl(var(--chkbg)) 50.99%)}.collapse:not(td):not(tr):not(colgroup){visibility:visible}.collapse{position:relative;display:grid;overflow:hidden;grid-template-rows:auto 0fr;transition:grid-template-rows 0.2s;width:100%;border-radius:var(--rounded-box,1rem)}.collapse>input[type="checkbox"],.collapse>input[type="radio"]{-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0}.collapse[open],.collapse-open,.collapse:focus:not(.collapse-close){grid-template-rows:auto 1fr}.collapse:not(.collapse-close):has(>input[type="checkbox"]:checked),.collapse:not(.collapse-close):has(>input[type="radio"]:checked){grid-template-rows:auto 1fr}.collapse[open]>.collapse-content,.collapse-open>.collapse-content,.collapse:focus:not(.collapse-close)>.collapse-content,.collapse:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-content,.collapse:not(.collapse-close)>input[type="radio"]:checked~.collapse-content{visibility:visible;min-height:-moz-fit-content;min-height:fit-content}.collapse:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:has(.collapse-title:focus-visible),.collapse:has(>input[type="checkbox"]:focus-visible),.collapse:has(>input[type="radio"]:focus-visible){outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.collapse:not(.collapse-open):not(.collapse-close)>input[type="checkbox"],.collapse:not(.collapse-open):not(.collapse-close)>input[type="radio"]:not(:checked),.collapse:not(.collapse-open):not(.collapse-close)>.collapse-title{cursor:pointer}.collapse:focus:not(.collapse-open):not(.collapse-close):not(.collapse[open])>.collapse-title{cursor:unset}:where(.collapse>input[type="checkbox"]),:where(.collapse>input[type="radio"]){z-index:1}.collapse[open]>:where(.collapse-content),.collapse-open>:where(.collapse-content),.collapse:focus:not(.collapse-close)>:where(.collapse-content),.collapse:not(.collapse-close)>:where(input[type="checkbox"]:checked~.collapse-content),.collapse:not(.collapse-close)>:where(input[type="radio"]:checked~.collapse-content){padding-bottom:1rem;transition:padding 0.2s ease-out,background-color 0.2s ease-out}.collapse[open].collapse-arrow>.collapse-title:after,.collapse-open.collapse-arrow>.collapse-title:after,.collapse-arrow:focus:not(.collapse-close)>.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-arrow:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{--un-translate-y:-50%;--un-rotate:225deg;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}[dir="rtl"] .collapse[open].collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-open.collapse-arrow>.collapse-title:after,[dir="rtl"] .collapse-arrow:focus:not(.collapse-close) .collapse-title:after,[dir="rtl"] .collapse-arrow:not(.collapse-close) input[type="checkbox"]:checked~.collapse-title:after{--un-rotate:135deg}.collapse[open].collapse-plus>.collapse-title:after,.collapse-open.collapse-plus>.collapse-title:after,.collapse-plus:focus:not(.collapse-close)>.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="checkbox"]:checked~.collapse-title:after,.collapse-plus:not(.collapse-close)>input[type="radio"]:checked~.collapse-title:after{content:"−"}:root .countdown{line-height:1em}.countdown{display:inline-flex}.countdown>*{height:1em;display:inline-block;overflow-y:hidden}.countdown>*:before{position:relative;content:"00\A 01\A 02\A 03\A 04\A 05\A 06\A 07\A 08\A 09\A 10\A 11\A 12\A 13\A 14\A 15\A 16\A 17\A 18\A 19\A 20\A 21\A 22\A 23\A 24\A 25\A 26\A 27\A 28\A 29\A 30\A 31\A 32\A 33\A 34\A 35\A 36\A 37\A 38\A 39\A 40\A 41\A 42\A 43\A 44\A 45\A 46\A 47\A 48\A 49\A 50\A 51\A 52\A 53\A 54\A 55\A 56\A 57\A 58\A 59\A 60\A 61\A 62\A 63\A 64\A 65\A 66\A 67\A 68\A 69\A 70\A 71\A 72\A 73\A 74\A 75\A 76\A 77\A 78\A 79\A 80\A 81\A 82\A 83\A 84\A 85\A 86\A 87\A 88\A 89\A 90\A 91\A 92\A 93\A 94\A 95\A 96\A 97\A 98\A 99\A";white-space:pre;top:calc(var(--value) * -1em);text-align:center;transition:all 1s cubic-bezier(1,0,0,1)}.divider{display:flex;flex-direction:row;align-items:center;align-self:stretch;margin-top:1rem;margin-bottom:1rem;height:1rem;white-space:nowrap}.divider:before,.divider:after{content:"";flex-grow:1;height:0.125rem;width:100%}.divider:before{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:after{background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.1}.divider:not(:empty){gap:1rem}.dropdown{position:relative;display:inline-block}.dropdown>*:not(summary):focus{outline:2px solid transparent;outline-offset:2px}.dropdown .dropdown-content{position:absolute}.dropdown:is(:not(details)) .dropdown-content{visibility:hidden;opacity:0;transform-origin:top;--un-scale-x:.95;--un-scale-y:.95;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-timing-function:cubic-bezier(0,0,0.2,1);transition-duration:200ms}.dropdown.dropdown-open .dropdown-content,.dropdown:not(.dropdown-hover):focus .dropdown-content,.dropdown:focus-within .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{visibility:visible;opacity:1}.dropdown.dropdown-hover:hover .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown:is(details) summary::-webkit-details-marker{display:none}.dropdown.dropdown-open .dropdown-content,.dropdown:focus .dropdown-content,.dropdown:focus-within .dropdown-content{--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.dropdown-end .dropdown-content{right:0px}.dropdown-end.dropdown-right .dropdown-content{bottom:0px;top:auto}.dropdown-end.dropdown-left .dropdown-content{bottom:0px;top:auto}.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity));outline-color:hsl(var(--p) / 1)}.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity))}.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-secondary{--un-border-opacity:1;border-color:hsl(var(--s) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity));outline-color:hsl(var(--s) / 1)}.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity))}.btn-neutral:hover{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-neutral{--un-border-opacity:1;border-color:hsl(var(--n) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity));outline-color:hsl(var(--n) / 1)}.btn-neutral.btn-active{--un-border-opacity:1;border-color:hsl(var(--nf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--nf) / var(--un-bg-opacity))}.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-error{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity));outline-color:hsl(var(--er) / 1)}.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.btn-ghost:hover{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-ghost{border-width:1px;border-color:transparent;background-color:transparent;color:currentColor;--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow);outline-color:currentColor}.btn-ghost.btn-active{--un-border-opacity:0;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.2}.btn-outline:hover{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary:hover{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary:hover{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent:hover{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success:hover{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info:hover{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning:hover{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error:hover{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.btn-outline{border-color:currentColor;background-color:transparent;--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));--un-shadow:0 0 #0000;--un-shadow-colored:0 0 #0000;box-shadow:var(--un-ring-offset-shadow,0 0 #0000),var(--un-ring-shadow,0 0 #0000),var(--un-shadow)}.btn-outline.btn-active{--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--b1) / var(--un-text-opacity))}.btn-outline.btn-primary{--un-text-opacity:1;color:hsl(var(--p) / var(--un-text-opacity))}.btn-outline.btn-primary.btn-active{--un-border-opacity:1;border-color:hsl(var(--pf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--pf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.btn-outline.btn-secondary{--un-text-opacity:1;color:hsl(var(--s) / var(--un-text-opacity))}.btn-outline.btn-secondary.btn-active{--un-border-opacity:1;border-color:hsl(var(--sf) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--sf) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.btn-outline.btn-accent{--un-text-opacity:1;color:hsl(var(--a) / var(--un-text-opacity))}.btn-outline.btn-accent.btn-active{--un-border-opacity:1;border-color:hsl(var(--af) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--af) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.btn-outline.btn-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.btn-outline.btn-success.btn-active{--un-border-opacity:1;border-color:hsl(var(--su) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.btn-outline.btn-info{--un-text-opacity:1;color:hsl(var(--in) / var(--un-text-opacity))}.btn-outline.btn-info.btn-active{--un-border-opacity:1;border-color:hsl(var(--in) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.btn-outline.btn-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.btn-outline.btn-warning.btn-active{--un-border-opacity:1;border-color:hsl(var(--wa) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.btn-outline.btn-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.btn-outline.btn-error.btn-active{--un-border-opacity:1;border-color:hsl(var(--er) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.footer{display:grid;width:100%;grid-auto-flow:row;place-items:start;-moz-column-gap:1rem;column-gap:1rem;row-gap:2.5rem;font-size:0.875rem;line-height:1.25rem}.footer>*{display:grid;place-items:start;gap:0.5rem}.footer{grid-auto-flow:column}.hero{display:grid;width:100%;place-items:center;background-size:cover;background-position:center}.hero>*{grid-column-start:1;grid-row-start:1}.indicator{position:relative;display:inline-flex;width:-moz-max-content;width:max-content}.indicator:where(.indicator-item){z-index:1;position:absolute;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));white-space:nowrap}.indicator:where(.indicator-item){bottom:auto;left:auto;right:0px;top:0px;--un-translate-y:-50%;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-start){left:0px;right:auto;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-end){left:auto;right:0px;--un-translate-x:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-bottom){bottom:0px;top:auto;--un-translate-y:50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-middle){bottom:50%;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.indicator:where(.indicator-item.indicator-top){bottom:auto;top:0px;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.input{flex-shrink:1;height:3rem;padding-left:1rem;padding-right:1rem;font-size:0.875rem;font-size:1rem;line-height:1.25rem;line-height:2;line-height:1.5rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.input input:focus{outline:2px solid transparent;outline-offset:2px}.input[list]::-webkit-calendar-picker-indicator{line-height:1em}.input:focus,.input:focus-within{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.kbd{display:inline-flex;align-items:center;justify-content:center;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;--un-bg-opacity:1;background-color:hsl(var(--b2) / var(--un-bg-opacity));padding-left:0.5rem;padding-right:0.5rem;border-radius:var(--rounded-btn,0.5rem);border-bottom-width:2px;min-height:2.2em;min-width:2.2em}.modal{pointer-events:none;position:fixed;inset:0px;margin:0px;display:grid;height:100%;max-height:none;width:100%;max-width:none;justify-items:center;padding:0px;opacity:0;overscroll-behavior:contain;overscroll-behavior:contain;z-index:999;background-color:transparent;color:inherit;transition-duration:200ms;transition-timing-function:cubic-bezier(0,0,0.2,1);transition-property:transform,opacity,visibility;overflow-y:hidden}:where(.modal){align-items:center}.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open]{pointer-events:auto;visibility:visible;opacity:1}:root:has(:is(.modal-open,.modal:target,.modal-toggle:checked+.modal,.modal[open])){overflow:hidden}.modal:not(dialog:not(.modal-open)),.modal::backdrop{background-color:rgba(0,0,0,0.3);animation:modal-pop 0.2s ease-out}.modal-open .modal-box,.modal-toggle:checked+.modal .modal-box,.modal:target .modal-box,.modal[open] .modal-box{--un-translate-y:0px;--un-scale-x:1;--un-scale-y:1;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.progress{position:relative;width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;overflow:hidden;height:0.5rem;background-color:hsl(var(--bc) / 0.2);border-radius:var(--rounded-box,1rem)}.progress::-moz-progress-bar{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate{--progress-color:hsl(var(--bc));background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.progress::-webkit-progress-bar{background-color:transparent;border-radius:var(--rounded-box,1rem)}.progress::-webkit-progress-value{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem)}.progress:indeterminate::-moz-progress-bar{background-color:transparent;background-image:repeating-linear-gradient( 90deg,var(--progress-color) -1%,var(--progress-color) 10%,transparent 10%,transparent 90% );background-size:200%;background-position-x:15%;animation:progress-loading 5s ease-in-out infinite}.radio{flex-shrink:0;--chkbg:var(--bc);height:1.5rem;width:1.5rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:9999px;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2}.radio:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 1)}.radio:checked,.radio[aria-checked="true"]{--un-bg-opacity:1;background-color:hsl(var(--bc) / var(--un-bg-opacity));animation:radiomark var(--animation-input,0.2s) ease-out;box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}.radio:disabled{cursor:not-allowed;opacity:0.2}.range{height:1.5rem;width:100%;cursor:pointer;-moz-appearance:none;appearance:none;-webkit-appearance:none;--range-shdw:var(--bc);overflow:hidden;background-color:transparent;border-radius:var(--rounded-box,1rem)}.range:focus{outline:none}.range:focus-visible::-webkit-slider-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range:focus-visible::-moz-range-thumb{--focus-shadow:0 0 0 6px hsl(var(--b1)) inset,0 0 0 2rem hsl(var(--range-shdw)) inset}.range::-webkit-slider-runnable-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-moz-range-track{height:0.5rem;width:100%;background-color:hsl(var(--bc) / 0.1);border-radius:var(--rounded-box,1rem)}.range::-webkit-slider-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);appearance:none;-webkit-appearance:none;top:50%;color:hsl(var(--range-shdw));transform:translateY(-50%);--filler-size:100rem;--filler-offset:0.6rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.range::-moz-range-thumb{position:relative;height:1.5rem;width:1.5rem;border-style:none;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-box,1rem);top:50%;color:hsl(var(--range-shdw));--filler-size:100rem;--filler-offset:0.5rem;box-shadow:0 0 0 3px hsl(var(--range-shdw)) inset,var(--focus-shadow,0 0),calc(var(--filler-size) * -1 - var(--filler-offset)) 0 0 var(--filler-size)}.select{display:inline-flex;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:3rem;padding-left:1rem;padding-right:2.5rem;padding-right:2.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;min-height:3rem;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem);background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position:calc(100% - 20px) calc(1px+50%),calc(100% - 16.1px) calc(1px+50%);background-size:4px 4px,4px 4px;background-repeat:no-repeat}.select[multiple]{height:auto}.select:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}[dir="rtl"] .select{background-position:calc(0%+12px) calc(1px+50%),calc(0%+16px) calc(1px+50%)}.stack{display:inline-grid;place-items:center;align-items:flex-end}.stack>*{grid-column-start:1;grid-row-start:1;transform:translateY(10%) scale(0.9);z-index:1;width:100%;opacity:0.6}.stack>*:nth-child(2){transform:translateY(5%) scale(0.95);z-index:2;opacity:0.8}.stack>*:nth-child(1){transform:translateY(0) scale(1);z-index:3;opacity:1}.stats{display:inline-grid;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));border-radius:var(--rounded-box,1rem)}:where(.stats){grid-auto-flow:column;overflow-x:auto}:where(.stats)>:not([hidden])~:not([hidden]){--un-divide-x-reverse:0;border-right-width:calc(1px * var(--un-divide-x-reverse));border-left-width:calc(1px * calc(1 - var(--un-divide-x-reverse)));--un-divide-y-reverse:0;border-top-width:calc(0px * calc(1 - var(--un-divide-y-reverse)));border-bottom-width:calc(0px * var(--un-divide-y-reverse))}.stat{display:inline-grid;width:100%;grid-template-columns:repeat(1,1fr);-moz-column-gap:1rem;column-gap:1rem;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.1;padding-left:1.5rem;padding-right:1.5rem;padding-top:1rem;padding-bottom:1rem}.steps{display:inline-grid;grid-auto-flow:column;overflow:hidden;overflow-x:auto;counter-reset:step;grid-auto-columns:1fr}.steps .step{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));grid-template-columns:auto;grid-template-rows:repeat(2,minmax(0,1fr));grid-template-rows:40px 1fr;place-items:center;text-align:center;min-width:4rem}.steps .step:before{top:0px;grid-column-start:1;grid-row-start:1;height:0.5rem;width:100%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y));--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity));content:"";margin-left:-100%}.steps .step:after{content:counter(step);counter-increment:step;z-index:1;position:relative;grid-column-start:1;grid-row-start:1;display:grid;height:2rem;width:2rem;place-items:center;place-self:center;border-radius:9999px;--un-bg-opacity:1;background-color:hsl(var(--b3) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.steps .step:first-child:before{content:none}.steps .step[data-content]:after{content:attr(data-content)}.steps .step-neutral+.step-neutral:before,.steps .step-neutral:after{--un-bg-opacity:1;background-color:hsl(var(--n) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--nc) / var(--un-text-opacity))}.steps .step-primary+.step-primary:before,.steps .step-primary:after{--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.steps .step-secondary+.step-secondary:before,.steps .step-secondary:after{--un-bg-opacity:1;background-color:hsl(var(--s) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--sc) / var(--un-text-opacity))}.steps .step-accent+.step-accent:before,.steps .step-accent:after{--un-bg-opacity:1;background-color:hsl(var(--a) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.steps .step-info+.step-info:before{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity))}.steps .step-info:after{--un-bg-opacity:1;background-color:hsl(var(--in) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--inc) / var(--un-text-opacity))}.steps .step-success+.step-success:before{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity))}.steps .step-success:after{--un-bg-opacity:1;background-color:hsl(var(--su) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--suc) / var(--un-text-opacity))}.steps .step-warning+.step-warning:before{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity))}.steps .step-warning:after{--un-bg-opacity:1;background-color:hsl(var(--wa) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--wac) / var(--un-text-opacity))}.steps .step-error+.step-error:before{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity))}.steps .step-error:after{--un-bg-opacity:1;background-color:hsl(var(--er) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--erc) / var(--un-text-opacity))}.tabs{display:flex;flex-wrap:wrap;align-items:flex-end}.textarea{flex-shrink:1;min-height:3rem;padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;line-height:2;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0;--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity));border-radius:var(--rounded-btn,0.5rem)}.textarea:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toast{position:fixed;display:flex;min-width:-moz-fit-content;min-width:fit-content;flex-direction:column;white-space:nowrap;gap:0.5rem;padding:1rem}.toast>*{animation:toast-pop 0.25s ease-out}:where(.toast){bottom:0px;left:auto;right:0px;top:auto;--un-translate-x:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-start){left:0px;right:auto;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-center){left:50%;right:50%;--un-translate-x:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-end){left:auto;right:0px;--un-translate-x:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-bottom){bottom:0px;top:auto;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-middle){bottom:auto;top:50%;--un-translate-y:-50%;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toast:where(.toast-top){bottom:auto;top:0px;--un-translate-y:0px;transform:translate(var(--un-translate-x),var(--un-translate-y)) rotate(var(--un-rotate)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y))}.toggle{flex-shrink:0;--tglbg:hsl(var(--b1));--handleoffset:1.5rem;--handleoffsetcalculator:calc(var(--handleoffset) * -1);--togglehandleborder:0 0;height:1.5rem;width:3rem;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-width:1px;border-color:hsl(var(--bc) / var(--un-border-opacity));--un-border-opacity:0.2;background-color:hsl(var(--bc) / var(--un-bg-opacity));--un-bg-opacity:0.5;border-radius:var(--rounded-badge,1.9rem);transition:background,box-shadow var(--animation-input,0.2s) ease-out;box-shadow:var(--handleoffsetcalculator) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset,var(--togglehandleborder)}[dir="rtl"] .toggle{--handleoffsetcalculator:calc(var(--handleoffset) * 1)}.toggle:focus-visible{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--bc) / 0.2)}.toggle:checked,.toggle[checked="true"],.toggle[aria-checked="true"]{--handleoffsetcalculator:var(--handleoffset);--un-border-opacity:1;--un-bg-opacity:1}[dir="rtl"] .toggle:checked,[dir="rtl"] .toggle[checked="true"],[dir="rtl"] .toggle[aria-checked="true"]{--handleoffsetcalculator:calc(var(--handleoffset) * -1)}.toggle:indeterminate{--un-border-opacity:1;--un-bg-opacity:1;box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}[dir="rtl"] .toggle:indeterminate{box-shadow:calc(var(--handleoffset) / 2) 0 0 2px var(--tglbg) inset,calc(var(--handleoffset) / -2) 0 0 2px var(--tglbg) inset,0 0 0 2px var(--tglbg) inset}.toggle:disabled{cursor:not-allowed;--un-border-opacity:1;border-color:hsl(var(--bc) / var(--un-border-opacity));background-color:transparent;opacity:0.3;--togglehandleborder:0 0 0 3px hsl(var(--bc)) inset,var(--handleoffsetcalculator) 0 0 3px hsl(var(--bc)) inset}.badge-primary{--un-border-opacity:1;border-color:hsl(var(--p) / var(--un-border-opacity));--un-bg-opacity:1;background-color:hsl(var(--p) / var(--un-bg-opacity));--un-text-opacity:1;color:hsl(var(--pc) / var(--un-text-opacity))}.input-bordered{--un-border-opacity:0.2}.loading{pointer-events:none;display:inline-block;aspect-ratio:1 / 1;width:1.5rem;background-color:currentColor;-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E")}.menu-title{padding-left:1rem;padding-right:1rem;padding-top:0.5rem;padding-bottom:0.5rem;font-size:0.875rem;line-height:1.25rem;font-weight:700;color:hsl(var(--bc) / 0.4)}.rounded-box{border-radius:var(--rounded-box,1rem)}.badge-sm{height:1rem;font-size:0.75rem;line-height:1rem;padding-left:0.438rem;padding-right:0.438rem}.btn-xs{height:1.5rem;padding-left:0.5rem;padding-right:0.5rem;min-height:1.5rem;font-size:0.75rem}.btn-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;min-height:2rem;font-size:0.875rem}.input-sm{height:2rem;padding-left:0.75rem;padding-right:0.75rem;font-size:0.875rem;line-height:2rem}.toggle-sm{--handleoffset:0.75rem;height:1.25rem;width:2rem}.menu-sm:where(li:not(.menu-title)>*:not(ul):not(details):not(.menu-title)),.menu-sm:where(li:not(.menu-title)>details>summary:not(.menu-title)){padding-left:0.75rem;padding-right:0.75rem;padding-top:0.25rem;padding-bottom:0.25rem;font-size:0.875rem;line-height:1.25rem;border-radius:var(--rounded-btn,0.5rem)}.menu-sm .menu-title{padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem}@keyframes button-pop{0%{transform:scale(var(--btn-focus-scale,0.98))}40%{transform:scale(1.02)}100%{transform:scale(1)}}@keyframes checkmark{0%{background-position-y:5px}50%{background-position-y:-2px}100%{background-position-y:0}}@keyframes modal-pop{0%{opacity:0}}@keyframes progress-loading{50%{background-position-x:-115%}}@keyframes radiomark{0%{box-shadow:0 0 0 12px hsl(var(--b1)) inset,0 0 0 12px hsl(var(--b1)) inset}50%{box-shadow:0 0 0 3px hsl(var(--b1)) inset,0 0 0 3px hsl(var(--b1)) inset}100%{box-shadow:0 0 0 4px hsl(var(--b1)) inset,0 0 0 4px hsl(var(--b1)) inset}}@keyframes rating-pop{0%{transform:translateY(-0.125em)}40%{transform:translateY(-0.125em)}100%{transform:translateY(0)}}@keyframes toast-pop{0%{transform:scale(0.9);opacity:0}100%{transform:scale(1);opacity:1}}:root{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}@media (prefers-color-scheme:dark){:root{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}}[data-theme=light]{color-scheme:light;--pf:259 94% 44%;--sf:314 100% 40%;--af:174 75% 39%;--nf:214 20% 14%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:259 94% 51%;--pc:259 96% 91%;--s:314 100% 47%;--sc:314 100% 91%;--a:174 75% 46%;--ac:174 75% 11%;--n:214 20% 21%;--nc:212 19% 87%;--b1:0 0% 100%;--b2:0 0% 95%;--b3:180 2% 90%;--bc:215 28% 17%}[data-theme=dark]{color-scheme:dark;--pf:262 80% 43%;--sf:316 70% 43%;--af:175 70% 34%;--in:198 93% 60%;--su:158 64% 52%;--wa:43 96% 56%;--er:0 91% 71%;--inc:198 100% 12%;--suc:158 100% 10%;--wac:43 100% 11%;--erc:0 100% 14%;--rounded-box:1rem;--rounded-btn:0.5rem;--rounded-badge:1.9rem;--animation-btn:0.25s;--animation-input:.2s;--btn-text-case:uppercase;--btn-focus-scale:0.95;--border-btn:1px;--tab-border:1px;--tab-radius:0.5rem;--p:262 80% 50%;--pc:0 0% 100%;--s:316 70% 50%;--sc:0 0% 100%;--a:175 70% 41%;--ac:0 0% 100%;--n:213 18% 20%;--nf:212 17% 17%;--nc:220 13% 69%;--b1:212 18% 14%;--b2:213 18% 12%;--b3:213 18% 10%;--bc:220 13% 69%}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.absolute,[absolute=""]{position:absolute}.fixed{position:fixed}.relative,[relative=""]{position:relative}.root-relative:root{position:relative}.static,[static=""]{position:static}.inset-0{inset:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-0,[right-0=""]{right:0}.right-4{right:1rem}.top-0{top:0}.top-4{top:1rem}.top-full{top:100%}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3}.z-\[1\]{z-index:1}.z-\[110\]{z-index:110}.z-\[80\]{z-index:80}.z-\[85\]{z-index:85}.z-10,[z-10=""]{z-index:10}.z-100{z-index:100}.z-120{z-index:120}.z-30{z-index:30}.z-50{z-index:50}.grid,[grid=""]{display:grid}.inline-grid{display:inline-grid}.col-span-1{grid-column:span 1/span 1}.grid-cols-1,[grid-cols-1=""]{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2,[grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-rows-2,[grid-rows-2=""]{grid-template-rows:repeat(2,minmax(0,1fr))}[rows~="\33 "]{grid-template-rows:repeat(3,minmax(0,1fr))}[rows~="\34 "]{grid-template-rows:repeat(4,minmax(0,1fr))}[rows~="\35 "]{grid-template-rows:repeat(5,minmax(0,1fr))}[rows~="\36 "]{grid-template-rows:repeat(6,minmax(0,1fr))}.m-1{margin:0.25rem}.m-2{margin:0.5rem}.m-3{margin:0.75rem}.m-4{margin:1rem}.m-6{margin:1.5rem}.m-8{margin:2rem}[m~="\30 \.45"]{margin:0.1125rem}[m~="\30 \.65"]{margin:0.1625rem}.-mx-1\.5{margin-left:-0.375rem;margin-right:-0.375rem}.-my-1\.5{margin-top:-0.375rem;margin-bottom:-0.375rem}.mx-2{margin-left:0.5rem;margin-right:0.5rem}.my{margin-top:1rem;margin-bottom:1rem}.my-0{margin-top:0;margin-bottom:0}.my-auto{margin-top:auto;margin-bottom:auto}.-ml-1{margin-left:-0.25rem}.mb-1{margin-bottom:0.25rem}.mb-12,[mb-12=""]{margin-bottom:3rem}.mb-2{margin-bottom:0.5rem}.mb-3,[mb-3=""]{margin-bottom:0.75rem}.mb-4,[mb-4=""]{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8,[mb-8=""]{margin-bottom:2rem}.mb-ds-12{margin-bottom:var(--space-12,3rem)}.mb-ds-4,[mb-ds-4=""]{margin-bottom:var(--space-4,1rem)}.mb-ds-6{margin-bottom:var(--space-6,1.5rem)}.mb-ds-8{margin-bottom:var(--space-8,2rem)}.me,[me=""]{margin-inline-end:1rem}.ml-1,[ml-1=""]{margin-left:0.25rem}.ml-2{margin-left:0.5rem}.ml-3,[ml-3=""]{margin-left:0.75rem}.ml-4,[ml-4=""]{margin-left:1rem}.ml-auto,[ml-auto=""]{margin-left:auto}.mr-1{margin-right:0.25rem}.mr-2{margin-right:0.5rem}.mr-3,[mr-3=""]{margin-right:0.75rem}.mr-4{margin-right:1rem}.ms,[ms=""]{margin-inline-start:1rem}.mt-1,[mt-1=""]{margin-top:0.25rem}.mt-10{margin-top:2.5rem}.mt-12,[mt-12=""]{margin-top:3rem}.mt-16{margin-top:4rem}.mt-2,[mt-2=""]{margin-top:0.5rem}.mt-3{margin-top:0.75rem}.mt-4{margin-top:1rem}.mt-6,[mt-6=""]{margin-top:1.5rem}.mt-8,[mt-8=""]{margin-top:2rem}.mt-auto{margin-top:auto}.mt-ds-3{margin-top:var(--space-3,0.75rem)}.mt-ds-6,[mt-ds-6=""]{margin-top:var(--space-6,1.5rem)}.mt-ds-8,[mt-ds-8=""]{margin-top:var(--space-8,2rem)}.inline{display:inline}.block,.dark .dark\:block,[block=""]{display:block}.empty\:block:empty{display:block}.inline-block,[inline-block=""]{display:inline-block}.contents,[contents=""]{display:contents}.dark .dark\:hidden,.hidden,[hidden=""]{display:none}.h-\[\{\}px\]{height:{}px}.h-10{height:2.5rem}.h-12,[h-12=""]{height:3rem}.h-15{height:3.75rem}.h-16{height:4rem}.h-2,.h2{height:0.5rem}.h-28,[h-28=""]{height:7rem}.h-3,.h3,[h3=""]{height:0.75rem}.h-32{height:8rem}.h-4,.h4,[h-4=""]{height:1rem}.h-5,.h5,[h-5=""],[h5=""]{height:1.25rem}.h-6,.h6,[h-6=""]{height:1.5rem}.h-64,[h-64=""]{height:16rem}.h-8,[h-8=""]{height:2rem}.h-9{height:2.25rem}.h-full,[h-full=""]{height:100%}.h1{height:0.25rem}.max-w-2xl,[max-w-2xl=""]{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl,[max-w-4xl=""]{max-width:56rem}.max-w-6xl,[max-w-6xl=""]{max-width:72rem}.max-w-7xl,[max-w-7xl=""]{max-width:80rem}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-none,[max-w-none=""]{max-width:none}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.min-h-\[34px\]{min-height:34px}.min-h-screen,[min-h-screen=""]{min-height:100vh}.min-w-\[34px\]{min-width:34px}.min-w-0{min-width:0}.min-w-32,[min-w-32=""]{min-width:8rem}.w-\[\{\}px\]{width:{}px}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-12,[w-12=""]{width:3rem}.w-16{width:4rem}.w-24{width:6rem}.w-28,[w-28=""]{width:7rem}.w-3,[w-3=""]{width:0.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4,[w-4=""]{width:1rem}.w-48,[w-48=""]{width:12rem}.w-5,[w-5=""]{width:1.25rem}.w-52{width:13rem}.w-6,[w-6=""]{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-auto{width:auto}.w-full,[w-full=""]{width:100%}.w-min,[w-min=""]{width:min-content}[w-1=""]{width:0.25rem}[block~="a"]{block-size:auto}.flex,[flex=""]{display:flex}.inline-flex,[inline-flex=""]{display:inline-flex}.flex-1,[flex-1=""]{flex:1 1 0%}.flex-shrink{flex-shrink:1}.flex-shrink-0,.shrink-0,[flex-shrink-0=""]{flex-shrink:0}.flex-grow,[flex-grow=""]{flex-grow:1}.flex-col,[flex-col=""]{flex-direction:column}.flex-wrap,[flex-wrap=""]{flex-wrap:wrap}.table,[table=""]{display:table}.border-collapse{border-collapse:collapse}.hover\:scale-105:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}[hover\:scale-105=""]:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.animate-pulse{animation:pulse 2s cubic-bezier(0.4,0,.6,1) infinite}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.cursor-not-allowed,[cursor-not-allowed=""]{cursor:not-allowed}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.select-none{-webkit-user-select:none;user-select:none}.list-disc{list-style-type:disc}.list-inside{list-style-position:inside}.list-none{list-style-type:none}.place-items-center{place-items:center}.items-start,[items-start=""]{align-items:flex-start}.items-end{align-items:flex-end}.items-center,[items-center=""]{align-items:center}.items-stretch,[items-stretch=""]{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center,[justify-center=""]{justify-content:center}.justify-between,[justify-between=""]{justify-content:space-between}.gap-1,[gap-1=""]{gap:0.25rem}.gap-12{gap:3rem}.gap-2,[gap-2=""]{gap:0.5rem}.gap-3,[gap-3=""]{gap:0.75rem}.gap-4,[gap-4=""]{gap:1rem}.gap-6,[gap-6=""]{gap:1.5rem}.gap-8{gap:2rem}.gap-ds-2{gap:var(--space-2,0.5rem)}.gap-ds-4{gap:var(--space-4,1rem)}.gap-ds-6{gap:var(--space-6,1.5rem)}.gap-ds-8{gap:var(--space-8,2rem)}.gap-x-6{column-gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]),[space-x-1=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.25rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.25rem * var(--un-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]),[space-x-2=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.space-x-3>:not([hidden])~:not([hidden]),[space-x-3=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.space-x-4>:not([hidden])~:not([hidden]),[space-x-4=""]>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1rem * var(--un-space-x-reverse))}.space-x-6>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(1.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(1.5rem * var(--un-space-x-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.25rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.25rem * var(--un-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]),[space-y-2=""]>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.5rem * var(--un-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(0.75rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(0.75rem * var(--un-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1rem * var(--un-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1.5rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1.5rem * var(--un-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(2rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(2rem * var(--un-space-y-reverse))}.space-y-ds-2>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-2,0.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-2,0.5rem) * var(--un-space-y-reverse))}.space-y-ds-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-4,1rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-4,1rem) * var(--un-space-y-reverse))}.space-y-ds-6>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-6,1.5rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-6,1.5rem) * var(--un-space-y-reverse))}.space-y-ds-8>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(var(--space-8,2rem) * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(var(--space-8,2rem) * var(--un-space-y-reverse))}.overflow-hidden,[overflow-hidden=""]{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.b,.border,[b=""],[border=""]{border-width:1px}.border-2,[border-2=""]{border-width:2px}.last\:border-0:last-child{border-width:0px}.border-b,[border-b=""]{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-r{border-right-width:1px}.border-t,[border-t=""]{border-top-width:1px}.border-t-2{border-top-width:2px}.last\:border-b-0:last-child{border-bottom-width:0px}[last\:border-b-0=""]:last-child{border-bottom-width:0px}.border-base-200{--un-border-opacity:1;border-color:hsl(var(--b2) / var(--un-border-opacity))}.border-base-300,[border-base-300=""]{--un-border-opacity:1;border-color:hsl(var(--b3) / var(--un-border-opacity))}.border-base-300\/50{border-color:hsl(var(--b3) / 0.5)}.border-base-content\/20{border-color:hsl(var(--bc) / 0.2)}.border-blue-200{--un-border-opacity:1;border-color:rgb(191 219 254 / var(--un-border-opacity))}.border-blue-500,[border-blue-500=""]{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.border-blue-500\/20{border-color:rgb(59 130 246 / 0.2)}.border-blue-600{--un-border-opacity:1;border-color:rgb(37 99 235 / var(--un-border-opacity))}.border-error\/20{border-color:hsl(var(--er) / 0.2)}.border-gray-100{--un-border-opacity:1;border-color:rgb(243 244 246 / var(--un-border-opacity))}.border-gray-200,[border-gray-200=""]{--un-border-opacity:1;border-color:rgb(229 231 235 / var(--un-border-opacity))}.border-gray-300{--un-border-opacity:1;border-color:rgb(209 213 219 / var(--un-border-opacity))}.border-gray-400{--un-border-opacity:1;border-color:rgb(156 163 175 / var(--un-border-opacity))}.border-gray-500,[border-gray-500=""]{--un-border-opacity:1;border-color:rgb(107 114 128 / var(--un-border-opacity))}.border-green-200{--un-border-opacity:1;border-color:rgb(187 247 208 / var(--un-border-opacity))}.border-green-300{--un-border-opacity:1;border-color:rgb(134 239 172 / var(--un-border-opacity))}.border-green-400{--un-border-opacity:1;border-color:rgb(74 222 128 / var(--un-border-opacity))}.border-green-500,[border-green-500=""]{--un-border-opacity:1;border-color:rgb(34 197 94 / var(--un-border-opacity))}.border-purple-500,[border-purple-500=""]{--un-border-opacity:1;border-color:rgb(168 85 247 / var(--un-border-opacity))}.border-red-200,[border-red-200=""]{--un-border-opacity:1;border-color:rgb(254 202 202 / var(--un-border-opacity))}.border-red-300{--un-border-opacity:1;border-color:rgb(252 165 165 / var(--un-border-opacity))}.border-red-400{--un-border-opacity:1;border-color:rgb(248 113 113 / var(--un-border-opacity))}.border-transparent{border-color:transparent}.border-yellow-200{--un-border-opacity:1;border-color:rgb(254 240 138 / var(--un-border-opacity))}.border-yellow-500,[border-yellow-500=""]{--un-border-opacity:1;border-color:rgb(234 179 8 / var(--un-border-opacity))}.dark .dark\:border-gray-700{--un-border-opacity:1;border-color:rgb(55 65 81 / var(--un-border-opacity))}.hover\:border-blue-400:hover{--un-border-opacity:1;border-color:rgb(96 165 250 / var(--un-border-opacity))}.focus\:border-blue-500:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.focus\:border-transparent:focus{border-color:transparent}[focus\:border-blue-500=""]:focus{--un-border-opacity:1;border-color:rgb(59 130 246 / var(--un-border-opacity))}.rounded-md,[rounded-md=""]{border-radius:0.375rem}.rounded-l-md{border-top-left-radius:0.375rem;border-bottom-left-radius:0.375rem}.rounded-r-md,[rounded-r-md=""]{border-top-right-radius:0.375rem;border-bottom-right-radius:0.375rem}.border-dashed,[border-dashed=""]{border-style:dashed}.border-solid{border-style:solid}.bg-base-100{--un-bg-opacity:1;background-color:hsl(var(--b1) / var(--un-bg-opacity))}.bg-black{--un-bg-opacity:1;background-color:rgb(0 0 0 / var(--un-bg-opacity))}.bg-blue-100{--un-bg-opacity:1;background-color:rgb(219 234 254 / var(--un-bg-opacity))}.bg-blue-50,[bg-blue-50=""]{--un-bg-opacity:1;background-color:rgb(239 246 255 / var(--un-bg-opacity))}.bg-blue-500{--un-bg-opacity:1;background-color:rgb(59 130 246 / var(--un-bg-opacity))}.bg-blue-500\/10{background-color:rgb(59 130 246 / 0.1)}.bg-blue-600,[bg-blue-600=""]{--un-bg-opacity:1;background-color:rgb(37 99 235 / var(--un-bg-opacity))}.bg-ds-brand-primary{background-color:var(--color-brand-primary,#3b82f6)}.bg-gray-100,[bg-gray-100=""]{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.bg-gray-200,[bg-gray-200=""]{--un-bg-opacity:1;background-color:rgb(229 231 235 / var(--un-bg-opacity))}.bg-gray-50,[bg-gray-50=""]{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.bg-gray-600,.dark .dark\:bg-gray-600{--un-bg-opacity:1;background-color:rgb(75 85 99 / var(--un-bg-opacity))}.bg-gray-800,.dark .dark\:bg-gray-800{--un-bg-opacity:1;background-color:rgb(31 41 55 / var(--un-bg-opacity))}.bg-gray-900,.dark [dark\:bg-gray-900=""],[bg-gray-900=""]{--un-bg-opacity:1;background-color:rgb(17 24 39 / var(--un-bg-opacity))}.bg-green-100,[bg-green-100=""]{--un-bg-opacity:1;background-color:rgb(220 252 231 / var(--un-bg-opacity))}.bg-green-50,[bg-green-50=""]{--un-bg-opacity:1;background-color:rgb(240 253 244 / var(--un-bg-opacity))}.bg-green-500{--un-bg-opacity:1;background-color:rgb(34 197 94 / var(--un-bg-opacity))}.bg-indigo-100,[bg-indigo-100=""]{--un-bg-opacity:1;background-color:rgb(224 231 255 / var(--un-bg-opacity))}.bg-indigo-600{--un-bg-opacity:1;background-color:rgb(79 70 229 / var(--un-bg-opacity))}.bg-orange-500{--un-bg-opacity:1;background-color:rgb(249 115 22 / var(--un-bg-opacity))}.bg-primary{background-color:var(--c-primary)}.bg-purple-50,[bg-purple-50=""]{--un-bg-opacity:1;background-color:rgb(250 245 255 / var(--un-bg-opacity))}.bg-red-100{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.bg-red-50,[bg-red-50=""]{--un-bg-opacity:1;background-color:rgb(254 242 242 / var(--un-bg-opacity))}.bg-red-500{--un-bg-opacity:1;background-color:rgb(239 68 68 / var(--un-bg-opacity))}.bg-slate-700{--un-bg-opacity:1;background-color:rgb(51 65 85 / var(--un-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{--un-bg-opacity:1;background-color:rgb(255 255 255 / var(--un-bg-opacity))}.bg-yellow-100,[bg-yellow-100=""]{--un-bg-opacity:1;background-color:rgb(254 249 195 / var(--un-bg-opacity))}.bg-yellow-50,[bg-yellow-50=""]{--un-bg-opacity:1;background-color:rgb(254 252 232 / var(--un-bg-opacity))}.bg-yellow-500{--un-bg-opacity:1;background-color:rgb(234 179 8 / var(--un-bg-opacity))}.dark .dark\:bg-blue-900\/20{background-color:rgb(30 58 138 / 0.2)}.dark .dark\:bg-gray-700{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.dark .dark\:bg-gray-900\/50{background-color:rgb(17 24 39 / 0.5)}.dark .dark\:bg-green-900\/20{background-color:rgb(20 83 45 / 0.2)}.dark .dark\:bg-purple-900\/20{background-color:rgb(88 28 135 / 0.2)}.dark .dark\:bg-slate-400{--un-bg-opacity:1;background-color:rgb(148 163 184 / var(--un-bg-opacity))}.dark .dark\:bg-slate-900{--un-bg-opacity:1;background-color:rgb(15 23 42 / var(--un-bg-opacity))}.dark .dark\:bg-yellow-900\/20{background-color:rgb(113 63 18 / 0.2)}.dark [dark\:bg-blue-900=""]{--un-bg-opacity:1;background-color:rgb(30 58 138 / var(--un-bg-opacity))}.dark [dark\:bg-green-900=""]{--un-bg-opacity:1;background-color:rgb(20 83 45 / var(--un-bg-opacity))}.dark [dark\:bg-purple-900=""]{--un-bg-opacity:1;background-color:rgb(88 28 135 / var(--un-bg-opacity))}.dark [dark\:bg-yellow-900=""]{--un-bg-opacity:1;background-color:rgb(113 63 18 / var(--un-bg-opacity))}.dark .dark\:hover\:bg-gray-500:hover{--un-bg-opacity:1;background-color:rgb(107 114 128 / var(--un-bg-opacity))}.hover\:bg-base-content\/5:hover{background-color:hsl(var(--bc) / 0.05)}.hover\:bg-blue-700:hover{--un-bg-opacity:1;background-color:rgb(29 78 216 / var(--un-bg-opacity))}.hover\:bg-ds-brand-primary:hover{background-color:var(--color-brand-primary,#3b82f6)}.hover\:bg-gray-100:hover{--un-bg-opacity:1;background-color:rgb(243 244 246 / var(--un-bg-opacity))}.hover\:bg-gray-50:hover{--un-bg-opacity:1;background-color:rgb(249 250 251 / var(--un-bg-opacity))}.hover\:bg-gray-700:hover{--un-bg-opacity:1;background-color:rgb(55 65 81 / var(--un-bg-opacity))}.hover\:bg-primary\/80:hover{background-color:var(--c-primary)}.hover\:bg-red-100:hover{--un-bg-opacity:1;background-color:rgb(254 226 226 / var(--un-bg-opacity))}.hover\:bg-red-200:hover{--un-bg-opacity:1;background-color:rgb(254 202 202 / var(--un-bg-opacity))}.dark .dark\:from-gray-900{--un-gradient-from-position:0%;--un-gradient-from:rgb(17 24 39 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(17 24 39 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-blue-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(239 246 255 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(239 246 255 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.from-slate-50{--un-gradient-from-position:0%;--un-gradient-from:rgb(248 250 252 / var(--un-from-opacity,1)) var(--un-gradient-from-position);--un-gradient-to-position:100%;--un-gradient-to:rgb(248 250 252 / 0) var(--un-gradient-to-position);--un-gradient-stops:var(--un-gradient-from),var(--un-gradient-to)}.dark .dark\:to-blue-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(30 58 138 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.dark .dark\:to-slate-900{--un-gradient-to-position:100%;--un-gradient-to:rgb(15 23 42 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-gray-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(243 244 246 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.to-indigo-100{--un-gradient-to-position:100%;--un-gradient-to:rgb(224 231 255 / var(--un-to-opacity,1)) var(--un-gradient-to-position)}.bg-gradient-to-br{--un-gradient-shape:to bottom right in oklch;--un-gradient:var(--un-gradient-shape),var(--un-gradient-stops);background-image:linear-gradient(var(--un-gradient))}[stroke-width~="\31 \.5"]{stroke-width:1.5px}[stroke-width~="\32 "]{stroke-width:2px}[stroke-width~="\34 "]{stroke-width:4px}.object-cover{object-fit:cover}.p-1{padding:0.25rem}.p-1\.5{padding:0.375rem}.p-2,[p-2=""]{padding:0.5rem}.p-3{padding:0.75rem}.p-4,[p-4=""]{padding:1rem}.p-6,[p-6=""]{padding:1.5rem}.p-8{padding:2rem}.p-ds-2{padding:var(--space-2,0.5rem)}.p-ds-4,[p-ds-4=""]{padding:var(--space-4,1rem)}.p-ds-6,[p-ds-6=""]{padding:var(--space-6,1.5rem)}.p-ds-8{padding:var(--space-8,2rem)}.px-1{padding-left:0.25rem;padding-right:0.25rem}.px-2,[px-2=""]{padding-left:0.5rem;padding-right:0.5rem}.px-2\.5{padding-left:0.625rem;padding-right:0.625rem}.px-3,[px-3=""]{padding-left:0.75rem;padding-right:0.75rem}.px-4,[px-4=""]{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-ds-4{padding-left:var(--space-4,1rem);padding-right:var(--space-4,1rem)}.px-ds-8{padding-left:var(--space-8,2rem);padding-right:var(--space-8,2rem)}.py,.py-4{padding-top:1rem;padding-bottom:1rem}.py-0\.5{padding-top:0.125rem;padding-bottom:0.125rem}.py-1,[py-1=""]{padding-top:0.25rem;padding-bottom:0.25rem}.py-12,[py-12=""]{padding-top:3rem;padding-bottom:3rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2,[py-2=""]{padding-top:0.5rem;padding-bottom:0.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-3{padding-top:0.75rem;padding-bottom:0.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6,[py-6=""]{padding-top:1.5rem;padding-bottom:1.5rem}.py-8,[py-8=""]{padding-top:2rem;padding-bottom:2rem}.py-ds-12{padding-top:var(--space-12,3rem);padding-bottom:var(--space-12,3rem)}.py-ds-2{padding-top:var(--space-2,0.5rem);padding-bottom:var(--space-2,0.5rem)}.py-ds-4,[py-ds-4=""]{padding-top:var(--space-4,1rem);padding-bottom:var(--space-4,1rem)}.py-ds-6{padding-top:var(--space-6,1.5rem);padding-bottom:var(--space-6,1.5rem)}.py-ds-8{padding-top:var(--space-8,2rem);padding-bottom:var(--space-8,2rem)}.pb{padding-bottom:1rem}.pb-3,[pb-3=""]{padding-bottom:0.75rem}.pl{padding-left:1rem}.pl-3{padding-left:0.75rem}.pr-10,[pr-10=""]{padding-right:2.5rem}.pr-3{padding-right:0.75rem}.ps{padding-inline-start:1rem}.ps1{padding-inline-start:0.25rem}.pt{padding-top:1rem}.pt-2{padding-top:0.5rem}.pt-3,[pt-3=""]{padding-top:0.75rem}.pt-6{padding-top:1.5rem}.pt-ds-3{padding-top:var(--space-3,0.75rem)}.pt-ds-4{padding-top:var(--space-4,1rem)}.pt-ds-6{padding-top:var(--space-6,1.5rem)}.last\:pb-0:last-child{padding-bottom:0}.text-end,[text-end=""]{text-align:end}.text-balance,[text-balance=""]{text-wrap:balance}.align-middle{vertical-align:middle}.text-\[15px\]{font-size:15px}.text-2xl,[text-2xl=""]{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl,[text-4xl=""]{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm,[text-sm=""]{font-size:0.875rem;line-height:1.25rem}.text-xl,[text-xl=""]{font-size:1.25rem;line-height:1.75rem}.text-xs,[text-xs=""]{font-size:0.75rem;line-height:1rem}.dark .dark\:text-blue-400,.dark [dark\:text-blue-400=""],.text-blue-400{--un-text-opacity:1;color:rgb(96 165 250 / var(--un-text-opacity))}.dark .dark\:text-gray-300,.text-gray-300,[text-gray-300=""]{--un-text-opacity:1;color:rgb(209 213 219 / var(--un-text-opacity))}.dark .dark\:text-gray-400,.text-gray-400,[text-gray-400=""]{--un-text-opacity:1;color:rgb(156 163 175 / var(--un-text-opacity))}.dark .dark\:text-green-400,.dark [dark\:text-green-400=""]{--un-text-opacity:1;color:rgb(74 222 128 / var(--un-text-opacity))}.dark .dark\:text-purple-400,.dark [dark\:text-purple-400=""]{--un-text-opacity:1;color:rgb(192 132 252 / var(--un-text-opacity))}.dark .dark\:text-slate-900{--un-text-opacity:1;color:rgb(15 23 42 / var(--un-text-opacity))}.dark .dark\:text-yellow-400,.dark [dark\:text-yellow-400=""]{--un-text-opacity:1;color:rgb(250 204 21 / var(--un-text-opacity))}.text-base-content,[text-base-content=""]{--un-text-opacity:1;color:hsl(var(--bc) / var(--un-text-opacity))}.text-base-content\/50{color:hsl(var(--bc) / 0.5)}.text-base-content\/60{color:hsl(var(--bc) / 0.6)}.text-base-content\/70{color:hsl(var(--bc) / 0.7)}.text-black{--un-text-opacity:1;color:rgb(0 0 0 / var(--un-text-opacity))}.text-blue-100,[text-blue-100=""]{--un-text-opacity:1;color:rgb(219 234 254 / var(--un-text-opacity))}.text-blue-200{--un-text-opacity:1;color:rgb(191 219 254 / var(--un-text-opacity))}.text-blue-500,[text-blue-500=""]{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.text-blue-600,[text-blue-600=""]{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.text-blue-700,[text-blue-700=""]{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.text-blue-800{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.text-error{--un-text-opacity:1;color:hsl(var(--er) / var(--un-text-opacity))}.text-gray-500{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.text-gray-600{--un-text-opacity:1;color:rgb(75 85 99 / var(--un-text-opacity))}.text-gray-700,[text-gray-700=""]{--un-text-opacity:1;color:rgb(55 65 81 / var(--un-text-opacity))}.text-gray-900,[text-gray-900=""]{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.text-green-500{--un-text-opacity:1;color:rgb(34 197 94 / var(--un-text-opacity))}.text-green-600,[text-green-600=""]{--un-text-opacity:1;color:rgb(22 163 74 / var(--un-text-opacity))}.text-green-700,[text-green-700=""]{--un-text-opacity:1;color:rgb(21 128 61 / var(--un-text-opacity))}.text-green-800{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.text-indigo-500{--un-text-opacity:1;color:rgb(99 102 241 / var(--un-text-opacity))}.text-indigo-700{--un-text-opacity:1;color:rgb(67 56 202 / var(--un-text-opacity))}.text-orange-600{--un-text-opacity:1;color:rgb(234 88 12 / var(--un-text-opacity))}.text-purple-700,[text-purple-700=""]{--un-text-opacity:1;color:rgb(126 34 206 / var(--un-text-opacity))}.text-red-400{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity))}.text-red-500{--un-text-opacity:1;color:rgb(239 68 68 / var(--un-text-opacity))}.text-red-600,[text-red-600=""]{--un-text-opacity:1;color:rgb(220 38 38 / var(--un-text-opacity))}.text-red-700{--un-text-opacity:1;color:rgb(185 28 28 / var(--un-text-opacity))}.text-success{--un-text-opacity:1;color:hsl(var(--su) / var(--un-text-opacity))}.text-warning{--un-text-opacity:1;color:hsl(var(--wa) / var(--un-text-opacity))}.dark .dark\:color-white,.text-white,[text-white=""]{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.text-yellow-600,[text-yellow-600=""]{--un-text-opacity:1;color:rgb(202 138 4 / var(--un-text-opacity))}.text-yellow-700,[text-yellow-700=""]{--un-text-opacity:1;color:rgb(161 98 7 / var(--un-text-opacity))}.text-yellow-800{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}.dark .dark\:hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-accent-content:hover{--un-text-opacity:1;color:hsl(var(--ac) / var(--un-text-opacity))}.hover\:text-blue-500:hover{--un-text-opacity:1;color:rgb(59 130 246 / var(--un-text-opacity))}.hover\:text-blue-600:hover{--un-text-opacity:1;color:rgb(37 99 235 / var(--un-text-opacity))}.hover\:text-blue-700:hover{--un-text-opacity:1;color:rgb(29 78 216 / var(--un-text-opacity))}.hover\:text-blue-800:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}.hover\:text-gray-500:hover{--un-text-opacity:1;color:rgb(107 114 128 / var(--un-text-opacity))}.hover\:text-gray-900:hover{--un-text-opacity:1;color:rgb(17 24 39 / var(--un-text-opacity))}.hover\:text-green-800:hover{--un-text-opacity:1;color:rgb(22 101 52 / var(--un-text-opacity))}.hover\:text-primary:hover{color:var(--c-primary)}.hover\:text-red-800:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.hover\:text-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity))}.hover\:text-yellow-800:hover{--un-text-opacity:1;color:rgb(133 77 14 / var(--un-text-opacity))}[hover\:text-blue-800=""]:hover{--un-text-opacity:1;color:rgb(30 64 175 / var(--un-text-opacity))}[hover\:text-primary=""]:hover{color:var(--c-primary)}[hover\:text-red-800=""]:hover{--un-text-opacity:1;color:rgb(153 27 27 / var(--un-text-opacity))}.text-opacity-70{--un-text-opacity:0.7}.font-bold,[font-bold=""]{font-weight:700}.font-medium,[font-medium=""]{font-weight:500}.font-semibold,[font-semibold=""]{font-weight:600}.leading-6{line-height:1.5rem}.leading-7,[leading-7=""]{line-height:1.75rem}.leading-8{line-height:2rem}.leading-relaxed{line-height:1.625}.tracking-tight,[tracking-tight=""]{letter-spacing:-0.025em}.tracking-widest{letter-spacing:0.1em}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.uppercase,[uppercase=""]{text-transform:uppercase}.lowercase,[lowercase=""]{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.underline{text-decoration-line:underline}.hover\:underline:hover{text-decoration-line:underline}.no-underline,[no-underline=""]{text-decoration:none}.opacity-0,[opacity-0=""]{opacity:0}.opacity-100,[opacity-100=""]{opacity:1}.opacity-25{opacity:0.25}.opacity-50{opacity:0.5}.opacity-60{opacity:0.6}.opacity-75{opacity:0.75}.disabled\:opacity-40:disabled{opacity:0.4}.disabled\:opacity-50:disabled{opacity:0.5}.shadow-2xl{--un-shadow:var(--un-shadow-inset) 0 25px 50px -12px var(--un-shadow-color,rgb(0 0 0 / 0.25));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.shadow-md{--un-shadow:var(--un-shadow-inset) 0 4px 6px -1px var(--un-shadow-color,rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 2px 4px -2px var(--un-shadow-color,rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.outline{outline-style:solid}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}[focus\:outline-none=""]:focus{outline:2px solid transparent;outline-offset:2px}.ring-2,[ring-2=""]{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-2:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}[focus\:ring-2=""]:focus{--un-ring-width:2px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width)+var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow),var(--un-ring-shadow),var(--un-shadow)}.focus\:ring-offset-2:focus{--un-ring-offset-width:2px}.ring-blue-500{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-blue-500:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-red-600:focus{--un-ring-opacity:1;--un-ring-color:rgb(220 38 38 / var(--un-ring-opacity))}.focus\:ring-white:focus{--un-ring-opacity:1;--un-ring-color:rgb(255 255 255 / var(--un-ring-opacity))}[focus\:ring-blue-500=""]:focus{--un-ring-opacity:1;--un-ring-color:rgb(59 130 246 / var(--un-ring-opacity))}.focus\:ring-offset-red-100:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 226 226 / var(--un-ring-offset-opacity))}.focus\:ring-offset-red-50:focus{--un-ring-offset-opacity:1;--un-ring-offset-color:rgb(254 242 242 / var(--un-ring-offset-opacity))}.focus\:ring-inset:focus{--un-ring-inset:inset}[focus\:ring-inset=""]:focus{--un-ring-inset:inset}.backdrop-blur-sm{--un-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia)}.filter,[filter=""]{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-all,[transition-all=""]{transition-property:all;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.transition-transform,[transition-transform=""]{transition-property:transform;transition-timing-function:cubic-bezier(0.4,0,0.2,1);transition-duration:150ms}.duration-150{transition-duration:150ms}.duration-200,[duration-200=""]{transition-duration:200ms}.duration-300,[duration-300=""]{transition-duration:300ms}.ease,.ease-in-out{transition-timing-function:cubic-bezier(0.4,0,0.2,1)}.ease-in{transition-timing-function:cubic-bezier(0.4,0,1,1)}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:ml-3,[sm\:ml-3=""]{margin-left:0.75rem}.sm\:mr-6{margin-right:1.5rem}.sm\:mt-1,[sm\:mt-1=""]{margin-top:0.25rem}.sm\:flex-row,[sm\:flex-row=""]{flex-direction:row}.sm\:flex-wrap{flex-wrap:wrap}.sm\:items-center{align-items:center}.sm\:truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm\:p-6{padding:1.5rem}.sm\:px-6,[sm\:px-6=""]{padding-left:1.5rem;padding-right:1.5rem}.sm\:text-6xl{font-size:3.75rem;line-height:1}.sm\:leading-9,[sm\:leading-9=""]{line-height:2.25rem}}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:grid-cols-2,[md\:grid-cols-2=""]{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3,[md\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4,[md\:grid-cols-4=""]{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:ml-2{margin-left:0.5rem}.md\:ml-4{margin-left:1rem}.md\:ml-6{margin-left:1.5rem}.md\:mt-0,[md\:mt-0=""]{margin-top:0}.md\:hidden,[md\:hidden=""]{display:none}.md\:flex,[md\:flex=""]{display:flex}.md\:items-center,[md\:items-center=""]{align-items:center}.md\:justify-between,[md\:justify-between=""]{justify-content:space-between}.md\:space-x-2>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.5rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.5rem * var(--un-space-x-reverse))}.md\:space-x-3>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(0.75rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(0.75rem * var(--un-space-x-reverse))}.md\:space-x-8>:not([hidden])~:not([hidden]){--un-space-x-reverse:0;margin-left:calc(2rem * calc(1 - var(--un-space-x-reverse)));margin-right:calc(2rem * var(--un-space-x-reverse))}.md\:p-12{padding:3rem}.md\:text-5xl{font-size:3rem;line-height:1}}@media (min-width:1024px){.lg\:static{position:static}.lg\:inset-0{inset:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3,[lg\:grid-cols-3=""]{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:ml-64{margin-left:16rem}.lg\:mt-0{margin-top:0}.lg\:block{display:block}.lg\:hidden{display:none}.lg\:h-20{height:5rem}.lg\:max-w-6xl,[lg\:max-w-6xl=""]{max-width:72rem}.lg\:w-1\/3{width:33.3333333333%}.lg\:w-40{width:10rem}[lg\:w-1=""]{width:0.25rem}.lg\:translate-x-0{--un-translate-x:0;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z))}.lg\:border-t,[lg\:border-t=""]{border-top-width:1px}.lg\:px-8,[lg\:px-8=""]{padding-left:2rem;padding-right:2rem}.lg\:pl-8,[lg\:pl-8=""]{padding-left:2rem}}*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif;line-height:1.6;color:#333;background-color:#f8f9fa}.container{max-width:1200px;margin:0 auto;padding:0 20px}h1,h2,h3,h4,h5,h6{margin-bottom:1rem;color:#2c3e50}h1{font-size:2.5rem;font-weight:700}h2{font-size:2rem;font-weight:600}h3{font-size:1.5rem;font-weight:500}p{margin-bottom:1rem}.btn-icon{padding:2px 4px !important}.btn{display:inline-block;font-size:1rem;font-weight:500;text-decoration:none;border:none;border-radius:6px;cursor:pointer;transition:all 0.3s ease}.btn-primary{background-color:#3498db;color:white}.btn-primary:hover{background-color:#2980b9;transform:translateY(-2px)}.btn-secondary{background-color:#6c757d;color:white}.btn-secondary:hover{background-color:#5a6268}.btn-success{background-color:#28a745;color:white}.btn-success:hover{background-color:#218838}.card{background:white;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.1);padding:20px;margin-bottom:20px;transition:transform 0.3s ease,box-shadow 0.3s ease}.card:hover{transform:translateY(-5px);box-shadow:0 4px 20px rgba(0,0,0,0.15)}.card-title{color:#2c3e50;margin-bottom:10px}.card-text{color:#666;line-height:1.5}.navbar{background-color:#2c3e50;padding:1rem 0;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.navbar-brand{color:white;font-size:1.5rem;font-weight:700;text-decoration:none}.navbar-nav{display:flex;list-style:none;gap:2rem;margin-left:auto}.nav-link{color:#ecf0f1;text-decoration:none;transition:color 0.3s ease}.nav-link:hover{color:#3498db}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col{flex:1;padding:0 15px}.col-1{flex:0 0 8.333333%}.col-2{flex:0 0 16.666667%}.col-3{flex:0 0 25%}.col-4{flex:0 0 33.333333%}.col-6{flex:0 0 50%}.col-8{flex:0 0 66.666667%}.col-12{flex:0 0 100%}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.mt-1{margin-top:0.25rem}.mt-2{margin-top:0.5rem}.mt-3{margin-top:1rem}.mt-4{margin-top:1.5rem}.mt-5{margin-top:3rem}.mb-1{margin-bottom:0.25rem}.mb-2{margin-bottom:0.5rem}.mb-3{margin-bottom:1rem}.mb-4{margin-bottom:1.5rem}.mb-5{margin-bottom:3rem}.p-1{padding:0.25rem}.p-2{padding:0.5rem}.p-3{padding:1rem}.p-4{padding:1.5rem}.p-5{padding:3rem}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:6px}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeaa7}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.form-group{margin-bottom:1rem}.form-label{display:block;margin-bottom:0.5rem;font-weight:500;color:#333}.form-control{display:block;width:100%;padding:0.75rem;font-size:1rem;border:1px solid #ced4da;border-radius:6px;transition:border-color 0.3s ease,box-shadow 0.3s ease}.form-control:focus{outline:none;border-color:#3498db;box-shadow:0 0 0 3px rgba(52,152,219,0.1)}@media (max-width:768px){.container{padding:0 15px}.row{flex-direction:column}.col{flex:1;margin-bottom:1rem}.navbar-nav{flex-direction:column;gap:1rem}h1{font-size:2rem}h2{font-size:1.5rem}}.fade-in{animation:fadeIn 0.5s ease-in}@keyframes fadeIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.slide-up{animation:slideUp 0.6s ease-out}@keyframes slideUp{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}.static-file-badge{position:fixed;bottom:20px;right:20px;background-color:#28a745;color:white;padding:8px 12px;border-radius:20px;font-size:0.8rem;box-shadow:0 2px 10px rgba(0,0,0,0.2);z-index:1000}.static-file-badge:before{content:"📁 "}.contact-page-tailwind-style .tailwind-form-style{}.contact-page-tailwind-style .tailwind-form-style .unified-contact-form{background:transparent;border:none;padding:0;margin:0}.contact-page-tailwind-style .tailwind-form-style .form-header{display:none}.contact-page-tailwind-style .tailwind-form-style form{display:flex;flex-direction:column;gap:1.5rem}.contact-page-tailwind-style .tailwind-form-style .unified-form-field{margin-bottom:0}.contact-page-tailwind-style .tailwind-form-style label{display:block;font-size:0.875rem;font-weight:600;color:rgb(17 24 39);margin-bottom:0.5rem}.dark .contact-page-tailwind-style .tailwind-form-style label{color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input,.contact-page-tailwind-style .tailwind-form-style textarea{width:100%;padding:0.75rem 1rem;border:2px solid rgb(229 231 235);border-radius:0.5rem;background-color:rgb(255 255 255);color:rgb(17 24 39);outline:none;transition:all 0.2s ease-in-out;font-size:1rem}.dark .contact-page-tailwind-style .tailwind-form-style input,.dark .contact-page-tailwind-style .tailwind-form-style textarea{border-color:rgb(75 85 99);background-color:rgb(55 65 81);color:rgb(249 250 251)}.contact-page-tailwind-style .tailwind-form-style input:hover,.contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(209 213 219)}.dark .contact-page-tailwind-style .tailwind-form-style input:hover,.dark .contact-page-tailwind-style .tailwind-form-style textarea:hover{border-color:rgb(107 114 128)}.contact-page-tailwind-style .tailwind-form-style input:focus,.contact-page-tailwind-style .tailwind-form-style textarea:focus{border-color:rgb(59 130 246);box-shadow:0 0 0 2px rgb(59 130 246 / 0.2)}.contact-page-tailwind-style .tailwind-form-style textarea{resize:vertical;min-height:6rem}.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{display:grid;grid-template-columns:1fr;gap:1.5rem}@media (min-width:768px){.contact-page-tailwind-style .tailwind-form-style .field-group-horizontal{grid-template-columns:1fr 1fr}}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]{background:linear-gradient(to right,rgb(37 99 235),rgb(147 51 234));color:white;padding:0.75rem 2rem;border-radius:0.5rem;font-weight:600;font-size:1.125rem;box-shadow:0 10px 15px -3px rgb(0 0 0 / 0.1),0 4px 6px -2px rgb(0 0 0 / 0.05);transition:all 0.3s ease;border:none;cursor:pointer;min-width:200px}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:hover{background:linear-gradient(to right,rgb(29 78 216),rgb(126 34 206));box-shadow:0 20px 25px -5px rgb(0 0 0 / 0.1),0 8px 10px -6px rgb(0 0 0 / 0.1);transform:scale(1.05)}.contact-page-tailwind-style .tailwind-form-style button[type="submit"]:focus{outline:2px solid transparent;outline-offset:2px;box-shadow:0 0 0 2px rgb(59 130 246)}.contact-page-tailwind-style .tailwind-form-style .submit-button-container{text-align:center;padding-top:1rem}.contact-page-tailwind-style .tailwind-form-style .field-error{color:rgb(239 68 68);font-size:0.875rem;margin-top:0.25rem}.contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(220 252 231);border:1px solid rgb(34 197 94);color:rgb(21 128 61);padding:1rem;border-radius:0.5rem;margin-bottom:1rem}.dark .contact-page-tailwind-style .tailwind-form-style .form-success{background-color:rgb(21 128 61 / 0.2);border-color:rgb(34 197 94);color:rgb(187 247 208)}.contact-page-tailwind-style .tailwind-form-style .form-submitting button[type="submit"]{opacity:0.7;cursor:not-allowed}
\ No newline at end of file
diff --git a/site/site/_public/styles/custom.css b/site/site/_public/styles/custom.css
new file mode 100644
index 0000000..13dbecc
--- /dev/null
+++ b/site/site/_public/styles/custom.css
@@ -0,0 +1,780 @@
+/* Custom CSS file for static file serving example */
+
+/* Reset and base styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ line-height: 1.6;
+ color: #333;
+ background-color: #f8f9fa;
+}
+
+/* Container */
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+/* Typography */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-bottom: 1rem;
+ color: #2c3e50;
+}
+
+h1 {
+ font-size: 2.5rem;
+ font-weight: 700;
+}
+
+h2 {
+ font-size: 2rem;
+ font-weight: 600;
+}
+
+h3 {
+ font-size: 1.5rem;
+ font-weight: 500;
+}
+
+p {
+ margin-bottom: 1rem;
+}
+
+/* Buttons */
+.btn-icon {
+ padding: 2px 4px !important;
+}
+
+.btn {
+ display: inline-block;
+ /* padding: 12px 24px; */
+ font-size: 1rem;
+ font-weight: 500;
+ text-decoration: none;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.btn-primary {
+ background-color: #3498db;
+ color: white;
+}
+
+.btn-primary:hover {
+ background-color: #2980b9;
+ transform: translateY(-2px);
+}
+
+.btn-secondary {
+ background-color: #6c757d;
+ color: white;
+}
+
+.btn-secondary:hover {
+ background-color: #5a6268;
+}
+
+.btn-success {
+ background-color: #28a745;
+ color: white;
+}
+
+.btn-success:hover {
+ background-color: #218838;
+}
+
+/* Cards */
+.card {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ padding: 20px;
+ margin-bottom: 20px;
+ transition:
+ transform 0.3s ease,
+ box-shadow 0.3s ease;
+}
+
+.card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.card-title {
+ color: #2c3e50;
+ margin-bottom: 10px;
+}
+
+.card-text {
+ color: #666;
+ line-height: 1.5;
+}
+
+/* Navigation */
+.navbar {
+ background-color: #2c3e50;
+ padding: 1rem 0;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.navbar-brand {
+ color: white;
+ font-size: 1.5rem;
+ font-weight: 700;
+ text-decoration: none;
+}
+
+.navbar-nav {
+ display: flex;
+ list-style: none;
+ gap: 2rem;
+ margin-left: auto;
+}
+
+.nav-link {
+ color: #ecf0f1;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+.nav-link:hover {
+ color: #3498db;
+}
+
+/* Grid system */
+.row {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 -15px;
+}
+
+.col {
+ flex: 1;
+ padding: 0 15px;
+}
+
+.col-1 {
+ flex: 0 0 8.333333%;
+}
+.col-2 {
+ flex: 0 0 16.666667%;
+}
+.col-3 {
+ flex: 0 0 25%;
+}
+.col-4 {
+ flex: 0 0 33.333333%;
+}
+.col-6 {
+ flex: 0 0 50%;
+}
+.col-8 {
+ flex: 0 0 66.666667%;
+}
+.col-12 {
+ flex: 0 0 100%;
+}
+
+/* Utilities */
+.text-center {
+ text-align: center;
+}
+
+.text-left {
+ text-align: left;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.mt-1 {
+ margin-top: 0.25rem;
+}
+.mt-2 {
+ margin-top: 0.5rem;
+}
+.mt-3 {
+ margin-top: 1rem;
+}
+.mt-4 {
+ margin-top: 1.5rem;
+}
+.mt-5 {
+ margin-top: 3rem;
+}
+
+.mb-1 {
+ margin-bottom: 0.25rem;
+}
+.mb-2 {
+ margin-bottom: 0.5rem;
+}
+.mb-3 {
+ margin-bottom: 1rem;
+}
+.mb-4 {
+ margin-bottom: 1.5rem;
+}
+.mb-5 {
+ margin-bottom: 3rem;
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+.p-2 {
+ padding: 0.5rem;
+}
+.p-3 {
+ padding: 1rem;
+}
+.p-4 {
+ padding: 1.5rem;
+}
+.p-5 {
+ padding: 3rem;
+}
+
+/* Alerts */
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 6px;
+}
+
+.alert-info {
+ color: #0c5460;
+ background-color: #d1ecf1;
+ border-color: #bee5eb;
+}
+
+.alert-success {
+ color: #155724;
+ background-color: #d4edda;
+ border-color: #c3e6cb;
+}
+
+.alert-warning {
+ color: #856404;
+ background-color: #fff3cd;
+ border-color: #ffeaa7;
+}
+
+.alert-danger {
+ color: #721c24;
+ background-color: #f8d7da;
+ border-color: #f5c6cb;
+}
+
+/* Forms */
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ color: #333;
+}
+
+.form-control {
+ display: block;
+ width: 100%;
+ padding: 0.75rem;
+ font-size: 1rem;
+ border: 1px solid #ced4da;
+ border-radius: 6px;
+ transition:
+ border-color 0.3s ease,
+ box-shadow 0.3s ease;
+}
+
+.form-control:focus {
+ outline: none;
+ border-color: #3498db;
+ box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
+}
+
+/* Responsive design */
+@media (max-width: 768px) {
+ .container {
+ padding: 0 15px;
+ }
+
+ .row {
+ flex-direction: column;
+ }
+
+ .col {
+ flex: 1;
+ margin-bottom: 1rem;
+ }
+
+ .navbar-nav {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ h1 {
+ font-size: 2rem;
+ }
+
+ h2 {
+ font-size: 1.5rem;
+ }
+}
+
+/* Animation utilities */
+.fade-in {
+ animation: fadeIn 0.5s ease-in;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.slide-up {
+ animation: slideUp 0.6s ease-out;
+}
+
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Static file serving indicator */
+.static-file-badge {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background-color: #28a745;
+ color: white;
+ padding: 8px 12px;
+ border-radius: 20px;
+ font-size: 0.8rem;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
+ z-index: 1000;
+}
+
+.static-file-badge:before {
+ content: "📁 ";
+}
+
+/* All images inside post body: never overflow the column. */
+.post-content-body img {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Post body: lead thumbnail image rendered from markdown (first paragraph, lone img) */
+.post-content-body > p:first-child > img:only-child {
+ width: 100%;
+ max-height: 24rem;
+ object-fit: contain;
+ border-radius: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+/* Hide duplicate H1 title from markdown body — already shown in the header section.
+ Covers two layouts:
+ - thumbnail-first:
→ adjacent sibling after first p
+ - no thumbnail: as first child */
+.post-content-body > p:first-child + h1,
+.post-content-body > h1:first-child {
+ display: none;
+}
+
+/* Content grid card thumbnail */
+.grid-post-figure {
+ max-height: 11rem;
+}
+
+.grid-post-img {
+ height: 100%;
+ max-height: 11rem;
+ object-fit: contain;
+ margin: 0 auto;
+}
+
+/* Theme-aware thumbnails: show light img on light theme, dark img on dark theme */
+.grid-post-img-dark {
+ display: none;
+}
+
+[data-theme="dark"] .grid-post-img-light {
+ display: none;
+}
+
+[data-theme="dark"] .grid-post-img-dark {
+ display: block;
+}
+
+/* ── Content graph mini ────────────────────────────────────────────────────── */
+
+/* Constrain graph to sidebar width; prevent overflow on mobile */
+.content-graph-mini-wrapper {
+ max-width: 280px;
+}
+
+/* Preview: scales SVG to wrapper width, zoom-in cursor */
+.content-graph-mini-preview {
+ cursor: zoom-in;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ transition: opacity 0.15s;
+}
+
+.content-graph-mini-preview:hover {
+ opacity: 0.85;
+}
+
+.content-graph-mini-preview svg {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+/* Dark-mode overrides for SVG CSS custom properties.
+ The SVG embeds
+
+
+
+# Why I Needed Rust, Finally
+
+I gave this talk at **Rustikon 2026** (Warsaw, March 19, 2026): *Why I Needed Rust, Finally — Infrastructure Automation I Can Sleep On*. It's the story of a long resistance and of what finally broke it.
+
+## The resistance
+
+For years I automated infrastructure with the usual tooling: scripts, templates, layers of YAML that validate late — once they are already running against a real environment. It worked until it didn't, and the failure always showed up at the worst possible moment: at deploy time, never before.
+
+## What broke it
+
+Rust didn't win me over on performance. It won me over because it moves failure **to the left**: from deploy to the compiler. A type that doesn't line up never reaches production — it doesn't even run. That guarantee, applied to the provisioning layer, is what turns "I hope this deploys" into "this cannot deploy wrong in that way."
+
+## Infrastructure you can sleep on
+
+The title isn't a slogan. When configuration is the source of truth and it's typed, and the system refuses to start if something doesn't fit, being on call stops being an act of faith. That was the point in Warsaw: not that Rust is fast, but that it lets you **sleep on it**.
diff --git a/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md b/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md
new file mode 100644
index 0000000..3b82ad4
--- /dev/null
+++ b/site/site/content/activities/es/charlas/rustikon-2026-why-i-needed-rust.md
@@ -0,0 +1,92 @@
+---
+# Activity metadata
+id: "rustikon-2026-why-i-needed-rust"
+title: "Por qué necesitaba Rust, por fin"
+slug: "rustikon-2026-por-que-necesitaba-rust"
+subtitle: "Automatización de infraestructura sobre la que se puede dormir tranquilo — Rustikon 2026, Varsovia"
+excerpt: "Mi charla en Rustikon 2026: por qué, tras años resistiéndome, Rust acabó siendo la pieza que me dejó automatizar infraestructura sin miedo. Tipos que atrapan el fallo antes del despliegue, y una base sobre la que de verdad se puede dormir tranquilo. La charla está en inglés."
+
+# Publication info
+author: "Jesús Pérez"
+date: "2026-03-19"
+published: true
+featured: true
+
+# Categorization
+category: "charlas"
+tags: ["rust", "infraestructura", "automatizacion", "provisioning", "rustikon", "developer"]
+
+# Card thumbnail + social/feature image
+thumbnail: "/images/rustikon-2026-poster.webp"
+image_url: "/images/rustikon-2026-poster.webp"
+
+# Display
+sort_order: 1
+css_class: "category-charlas"
+
+# Event-specific metadata (activity schema)
+metadata:
+ event_type: "talk"
+ event_date: "2026-03-19"
+ event_location: "Varsovia, Polonia"
+ gallery:
+ - "/images/rustikon-2026-stage.webp"
+---
+
+
+
+
+
+
+# Por qué necesitaba Rust, por fin
+
+Presenté esta charla en **Rustikon 2026** (Varsovia, 19 de marzo de 2026). El título lo dice sin rodeos: *Why I Needed Rust, Finally — Infrastructure Automation I Can Sleep On*. Es la historia de una resistencia larga y de qué la rompió.
+
+> La charla está en inglés. Las diapositivas las iremos publicando traducidas.
+
+## La resistencia
+
+Durante años automaticé infraestructura con las herramientas de siempre: scripts, plantillas, capas de YAML que se validan tarde — cuando ya están corriendo contra un entorno real. Funcionaba hasta que dejaba de funcionar, y el fallo aparecía siempre en el peor momento: en el despliegue, no antes.
+
+## Lo que la rompió
+
+Rust no me atrajo por rendimiento. Me atrajo porque mueve el fallo **hacia la izquierda**: del despliegue al compilador. Un tipo que no cuadra no llega a producción — no se ejecuta siquiera. Esa garantía, aplicada a la capa de provisión, es lo que convierte "espero que esto despliegue bien" en "esto no puede desplegar mal de esta forma".
+
+## Infraestructura sobre la que dormir
+
+El título no es un eslogan. Cuando la configuración es la fuente de verdad y está tipada, y el sistema se niega a arrancar si algo no encaja, el turno de guardia deja de ser un acto de fe. Eso es lo que quería contar en Rustikon: no que Rust sea rápido, sino que deja **dormir tranquilo**.
diff --git a/site/site/content/adr/en/accepted/adr-001.md b/site/site/content/adr/en/accepted/adr-001.md
new file mode 100644
index 0000000..595a83b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-001.md
@@ -0,0 +1,33 @@
+---
+id: "adr-001"
+title: "Ontoref is a Standalone Protocol Project, Not Part of Stratumiops"
+slug: "001"
+subtitle: "Accepted"
+excerpt: "The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogra"
+author: "ontoref"
+date: "2026-03-12"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-001", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ The ontology/reflection patterns originated inside stratumiops as self-description tooling (stratum-ontology-core, stratum-reflection-core, stratum-daemon). Consumer projects (typedialog, vapora, kogral) needed the same patterns but had no path to adoption that did not entangle them with stratumiops' pipeline-specific crates (stratum-graph, stratum-state, stratum-orchestrator). Protocol evolution (schema changes, ADR lifecycle, daemon features) was blocked behind stratumiops release cycles. The three crates were logically a specification layer that happened to live in the wrong repo.
+
+Decision
+
+Ontoref is extracted as a standalone protocol project with independent versioning, CI, and crates: ontoref-ontology (Rust types for the ontology graph), ontoref-reflection (mode runner and schema validation), ontoref-daemon (NCL export cache, file watcher, actor registry). Consumer projects adopt the protocol via a thin `scripts/ontoref` bash wrapper and a `.ontoref/config.ncl` declaration. The ontoref project itself is allowed to use stratum-db and platform-nats as optional peer dependencies via workspace path references, since those crates are infrastructural rather than domain-specific.
+
+Constraints
+
+Hard ontoref crates must not import stratumiops domain crates: stratum-graph, stratum-state, stratum-orchestrator, stratum-llm, stratum-embeddingsHard The ontoref entry point must not unconditionally overwrite ONTOREF_PROJECT_ROOT — it must default only when unsetSoft A consumer project must only need .ontoref/config.ncl and scripts/ontoref to adopt the protocol — no other files copied into the consumer
+
+Alternatives considered
+
+Keep all tooling in stratumiops, consumers depend on it as a git subtree or submodule — rejected: Submodule/subtree patterns create update friction and do not solve the versioning coupling. Every consumer needs stratumiops' full dependency tree even for protocol-only use.Publish ontoref crates to crates.io and consume via version pins — rejected: Rapid iteration on protocol schemas makes published crate semantics too rigid at this stage. Path dependencies allow simultaneous development of protocol and consumers without publication ceremony.Inline protocol tooling into each consumer project separately — rejected: Schema drift across projects would immediately arise. The protocol's value is precisely its shared contract — decentralizing it defeats the purpose.
+
+Related ADRs
+
+ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-001.ncl b/site/site/content/adr/en/accepted/adr-001.ncl
new file mode 100644
index 0000000..8757d63
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-001.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-001",
+ title = "Ontoref is a Standalone Protocol Project, Not Part of Stratumiops",
+ slug = "001",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/001",
+ graph = {
+ implements = [],
+ related_to = ["adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-002.md b/site/site/content/adr/en/accepted/adr-002.md
new file mode 100644
index 0000000..61a8cd3
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-002.md
@@ -0,0 +1,33 @@
+---
+id: "adr-002"
+title: "Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier"
+slug: "002"
+subtitle: "Accepted"
+excerpt: "Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers a"
+author: "ontoref"
+date: "2026-03-12"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-002", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Nushell reflection modules invoke `nickel export` as a subprocess ~39 times per full sync scan, taking 2m42s. Each invocation forks a new process (~100ms). There is no shared state between developers and agents working on the same project, no notification when a peer changes an ontology or ADR file mid-session, and no persistent store for scan results. The protocol-not-runtime axiom forbids required runtime services — any daemon must be optional with full subprocess fallback. This ADR supersedes stratumiops adr-007, which designed this system inside stratumiops before the protocol was extracted to ontoref.
+
+Decision
+
+ontoref-daemon is an optional persistent daemon providing: (1) NCL export caching — results keyed by (path, mtime, import_path) served via HTTP, file watcher invalidates on change; (2) actor registry — developers, agents, CI register on startup with deterministic tokens (type:hostname:pid), sweep reaps stale sessions every 30s; (3) notification barrier — file changes in .ontology/, adrs/, reflection/ generate typed notifications stored in a per-project ring buffer; pre-commit hook queries pending notifications and blocks commits until acknowledged (fail-open: daemon down = commit allowed). Consumer projects configure the daemon via `.ontoref/config.ncl` (daemon.enabled, daemon.port, db.enabled, db.url). All Nushell modules fall back to direct `nickel export` subprocess when the daemon is unavailable. stratum-db (optional feature, path dep on stratumiops) handles SurrealDB persistence. platform-nats (optional feature, path dep on stratumiops) handles NATS event publishing.
+
+Constraints
+
+Hard No Nushell module or bash script may fail when ontoref-daemon is unavailableHard ontoref-daemon must bind to 127.0.0.1, never to 0.0.0.0 or a public interfaceHard The pre-commit hook must allow commits when ontoref-daemon is unreachable, printing a warning but not blockingSoft All daemon HTTP requests from consumer wrappers must include X-Ontoref-Project header or equivalent project scoping
+
+Alternatives considered
+
+In-process Nickel evaluation via nickel-lang-core library — rejected: nickel-lang-core has an unstable Rust API and resolves import paths differently from the CLI. The subprocess approach with caching is simpler, more stable, and already <1ms cached.Required daemon (always running, no fallback) — rejected: Violates the protocol-not-runtime axiom. Consumer projects must function without any ontoref process running. The daemon is an optimization and awareness layer, not infrastructure.Filesystem-based JSON cache without a daemon process — rejected: File-based caching requires lock management, cannot serve concurrent requests from multiple actors, and does not provide file watching or the actor registry. A daemon centralizes these concerns cleanly.Bundle SurrealDB and NATS clients directly in ontoref, not as path deps — rejected: Duplicating stratum-db and platform-nats creates divergence. Both crates are general-purpose infrastructure; ontoref consumers already have stratumiops checked out for other reasons. Path deps preserve the single canonical implementation.
+
+Related ADRs
+
+ADR-001
diff --git a/site/site/content/adr/en/accepted/adr-002.ncl b/site/site/content/adr/en/accepted/adr-002.ncl
new file mode 100644
index 0000000..e284d0b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-002.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-002",
+ title = "Ontoref Daemon for NCL Caching, File Watching, and Actor Notification Barrier",
+ slug = "002",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/002",
+ graph = {
+ implements = [],
+ related_to = ["adr-001"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-003.md b/site/site/content/adr/en/accepted/adr-003.md
new file mode 100644
index 0000000..b602338
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-003.md
@@ -0,0 +1,33 @@
+---
+id: "adr-003"
+title: "Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage"
+slug: "003"
+subtitle: "Accepted"
+excerpt: "The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is in"
+author: "ontoref"
+date: "2026-03-12"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-003", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The initial Q&A bookmarks feature stored entries in browser localStorage keyed by project. This is convenient but violates the dag-formalized axiom: knowledge that lives only in a browser session is invisible to agents, not git-versioned, not queryable via MCP, and is lost on browser data reset. The same problem applies to quick actions and any other accumulated operational knowledge. The system accumulates knowledge during development sessions (AI interactions, architectural reviews, debugging) that should be first-class artifacts — not ephemeral browser state.
+
+Decision
+
+All Q&A entries persist to `reflection/qa.ncl` — a typed NCL record file governed by `reflection/schemas/qa.ncl`. Mutations happen via `crates/ontoref-daemon/src/ui/qa_ncl.rs` (line-level surgery, same pattern as backlog_ncl.rs). Four MCP tools expose the store to AI agents: `ontoref_qa_list` (read, with filter), `ontoref_qa_add` (append), `ontoref_qa_delete` (remove block), `ontoref_qa_update` (field mutation). HTTP endpoints `/qa-json` (GET), `/qa/add`, `/qa/delete`, `/qa/update` (POST) serve the UI. The UI renders server-side entries via Tera template injection (SERVER_ENTRIES JSON blob), eliminating the need for a separate fetch on load. The same principle is applied to quick actions: they are declared in `.ontoref/config.ncl` (quick_actions array) and new modes are created as `reflection/modes/<id>.ncl` files via `ontoref_action_add`.
+
+Constraints
+
+Hard All mutations to reflection/qa.ncl must go through crates/ontoref-daemon/src/ui/qa_ncl.rs — no direct file writes from other call sitesHard reflection/qa.ncl must conform to the QaStore contract from reflection/schemas/qa.ncl — nickel typecheck must passHard MCP tools ontoref_qa_list and ontoref_qa_add must never trigger sync apply steps or modify .ontology/ files
+
+Alternatives considered
+
+Keep localStorage as the storage backend, add optional sync to NCL on explicit user action — rejected: Two sources of truth creates sync complexity and divergence. AI agents would still not see localStorage entries. The sync step would be frequently skipped. NCL as primary is simpler.Store Q&A in SurrealDB via stratum-db (existing optional dependency) — rejected: Requires the db feature and a running SurrealDB instance. NCL files are always present, git-versioned, and work without any database. The protocol-not-runtime axiom argues for file-first. SurrealDB can be added as a secondary index later if full-text search is needed.Full AST parse of qa.ncl via nickel-lang-core for mutations — rejected: nickel-lang-core has an unstable Rust API. The file structure is predictable enough for line-level surgery. backlog_ncl.rs has been operating safely with this pattern. Adding a hard dependency on nickel-lang-core for a file that is always written by the daemon is unnecessary complexity.
+
+Related ADRs
+
+ADR-001 · ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-003.ncl b/site/site/content/adr/en/accepted/adr-003.ncl
new file mode 100644
index 0000000..5330e0e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-003.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-003",
+ title = "Q&A and Accumulated Knowledge Persist to NCL, Not Browser Storage",
+ slug = "003",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/003",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-004.md b/site/site/content/adr/en/accepted/adr-004.md
new file mode 100644
index 0000000..867921f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-004.md
@@ -0,0 +1,33 @@
+---
+id: "adr-004"
+title: "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline"
+slug: "004"
+subtitle: "Accepted"
+excerpt: "Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself cal"
+author: "ontoref"
+date: "2026-03-13"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-004", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself calls `nickel export` at runtime; (2) credentials and secrets embedded in config files (TOML, JSON) persist on disk after the process starts, creating a forensic artifact. The existing registry.toml approach (NCL → TOML file → daemon reads file) partially addresses the first problem but not the second — the TOML file remains on disk with hashed credentials. SOPS and Vault are standard secret management tools that produce decrypted output on stdout.
+
+Decision
+
+All config delivery to long-running processes follows a three-stage Unix pipeline: Stage 1 — structural validation: `nickel export --format json config.ncl` produces JSON with schema-validated structure but no secret values; Stage 2 — secret injection (optional): SOPS decrypt or Vault lookup merges credentials into the JSON stream; Stage 3 — process bootstrap: the target process reads the composed JSON from stdin via `--config-stdin`. No intermediate file is written to disk. If any stage fails, the pipeline breaks and the process does not start. A bash wrapper script (not Nu — Nu may not be available at service boot time) orchestrates the pipeline. A Nu helper `ncl-bootstrap` provides the same interface for interactive/development use.
+
+Constraints
+
+Hard The bootstrap pipeline must not write an intermediate config file to disk at any stageHard The bash wrapper must depend only on bash, nickel, and the target binary — no Nu, no jq unless SOPS/Vault stage is activeHard The target process must redirect stdin to /dev/null after reading the config JSONHard NCL config files used with ncl-bootstrap must not contain plaintext secret values — only SecretRef placeholders or empty fields
+
+Alternatives considered
+
+TOML file on disk (current registry.toml approach) — rejected: File persists on disk with credentials. Stale file may be read after config changes. Requires explicit cleanup logic. Forensic artifact risk.Environment variables for secrets — rejected: Environment variables are visible in /proc/PID/environ on Linux and via `ps eww` on some systems. They persist for the lifetime of the process and are inherited by child processes. Worse attack surface than stdin pipe.Encrypted TOML file (AES256 at rest) — rejected: Decryption key must be available at runtime — the problem is deferred, not solved. The decrypted form still passes through disk (tmpfs or swap). Adds a custom encryption layer instead of using standard tools (SOPS, Vault) that the ecosystem already supports.Daemon reads NCL directly at runtime via nickel-lang-core — rejected: nickel-lang-core has an unstable Rust API. More critically, it means the daemon can evaluate arbitrary Nickel — including NCL files with system calls via builtins. The pipeline approach ensures the daemon only ever sees validated JSON, never executable Nickel.
+
+Related ADRs
+
+ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-004.ncl b/site/site/content/adr/en/accepted/adr-004.ncl
new file mode 100644
index 0000000..43e213f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-004.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-004",
+ title = "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline",
+ slug = "004",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/004",
+ graph = {
+ implements = [],
+ related_to = ["adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-005.md b/site/site/content/adr/en/accepted/adr-005.md
new file mode 100644
index 0000000..6b23b4b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-005.md
@@ -0,0 +1,33 @@
+---
+id: "adr-005"
+title: "Unified Key-to-Session Auth Model Across CLI, UI, and MCP"
+slug: "005"
+subtitle: "Accepted"
+excerpt: "ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). P"
+author: "ontoref"
+date: "2026-03-13"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-005", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). Prior to this ADR, the UI login flow set a session cookie but the REST API accepted raw passwords as Bearer tokens on every request — each call paying ~100ms for argon2 verification. The CLI had no Bearer support at all; project-add and project-remove called the daemon without credentials. The daemon manage page had no admin identity concept. There was no way to enumerate or revoke active sessions.
+
+Decision
+
+All surfaces exchange a raw key once via POST /sessions for a UUID v4 bearer token (30-day lifetime, in-memory SessionStore). The session token is used for all subsequent calls — O(1) DashMap lookup. Sessions carry a stable public id (distinct from the bearer token) for safe list/revoke operations without leaking credentials. Project keys have a label field for audit trail. Daemon-level admin uses a separate argon2id hash (ONTOREF_ADMIN_TOKEN_FILE preferred over ONTOREF_ADMIN_TOKEN) and creates sessions under virtual slug '_daemon'. The CLI injects ONTOREF_TOKEN as Authorization: Bearer automatically via bearer-args in store.nu. Key rotation (PUT /projects/{slug}/keys) revokes all active sessions for the rotated project. GET /sessions and DELETE /sessions/{id} implement two-tier visibility: project admin sees own project sessions; daemon admin sees all.
+
+Constraints
+
+Hard GET /sessions responses must never include the bearer token, only the public session idHard POST /sessions must not require authentication — it is the credential exchange endpointHard PUT /projects/{slug}/keys must call revoke_all_for_slug before persisting new keysSoft All CLI HTTP calls to the daemon must use bearer-args from store.nu — no hardcoded curl without auth args
+
+Alternatives considered
+
+Verify argon2 on every Bearer request (no session concept) — rejected: ~100ms per request is unacceptable for CLI invocations (each command is a new HTTP call) and MCP tool sequences (multiple calls per agent turn). Session token lookup is sub-microsecond.Axum middleware for auth instead of per-handler check_primary_auth — rejected: Middleware applies uniformly to all routes. ontoref-daemon coexists auth-enabled and open projects on the same router — some endpoints are always public (health, POST /sessions itself), some require project-scoped auth, some require daemon admin. Per-handler checks encode these rules explicitly; middleware would require complex route exemption logic.Expose bearer token in session list responses — rejected: GET /sessions is accessible to project admins. Exposing the bearer would allow any project admin to impersonate any other session holder. The public session.id is a safe substitute for revocation targeting.Separate token store per project — rejected: A single DashMap keyed by token with a slug field in SessionEntry is sufficient. A secondary id_index DashMap gives O(1) revoke-by-id. Per-project sharding would add complexity without benefit given the expected session count (tens, not millions).JWT instead of opaque UUID v4 tokens — rejected: JWTs are self-contained and cannot be revoked without a denylist. Opaque tokens enable instant revocation (key rotation, logout, admin force-revoke) with O(1) lookup. The daemon is local-only — there is no distributed verification scenario that would justify JWT complexity.
+
+Related ADRs
+
+ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-005.ncl b/site/site/content/adr/en/accepted/adr-005.ncl
new file mode 100644
index 0000000..2f23d45
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-005.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-005",
+ title = "Unified Key-to-Session Auth Model Across CLI, UI, and MCP",
+ slug = "005",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/005",
+ graph = {
+ implements = [],
+ related_to = ["adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-006.md b/site/site/content/adr/en/accepted/adr-006.md
new file mode 100644
index 0000000..6d545d4
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-006.md
@@ -0,0 +1,29 @@
+---
+id: "adr-006"
+title: "Nushell 0.111 String Interpolation Compatibility Fix"
+slug: "006"
+subtitle: "Accepted"
+excerpt: "Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$'...'` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record"
+author: "ontoref"
+date: "2026-03-14"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-006", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Nushell 0.111 introduced a breaking change in string interpolation parsing: expressions inside `$"..."` that match the pattern `(identifier: expr)` are now parsed as command calls rather than as record literals or literal text. This broke four print statements in reflection/bin/ontoref.nu that used patterns like `(kind: ($kind))`, `(logo: ($logo_file))`, `(parents: ($parent_slugs))`, and `(POST /actors/register)`. The bug manifested when running `ontoref setup` and `ontoref hooks-install` on any consumer project using Nu 0.111+. The minimum Nu version gate (>= 0.110.0) did not catch 0.111 regressions since it only guards the lower bound.
+
+Decision
+
+Fix all four affected print statements by removing the outer parentheses from label-value pairs inside string interpolations, or by removing the `$` prefix from strings that contain no variable interpolation. The fix is minimal and non-semantic: `(kind: ($kind))` becomes `kind: ($kind)` (literal label + variable), and `$"(POST /actors/register)"` becomes `"(POST /actors/register)"` (plain string). The fix is applied to both the dev repo (reflection/bin/ontoref.nu) and the installed copy (~/.local/bin/ontoref via just install-daemon). The minimum version gate remains >= 0.110.0 but 0.111 is now the tested floor.
+
+Constraints
+
+Hard String interpolations in ontoref.nu must not use `(identifier: expr)` patterns — use bare `identifier: (expr)` insteadSoft Print statements with no variable interpolation must use plain strings, not `$"..."`
+
+Alternatives considered
+
+Raise minimum Nu version to 0.111 and document the breaking change — rejected: Does not fix the broken syntax — just makes the breakage explicit. Consumer projects already on 0.111 would still fail until the print statements are fixed.Use escape sequences or string concatenation to embed literal parens — rejected: Nushell has no escape for parens in string interpolation. String concatenation (e.g. `'(kind: ' + $kind + ')'`) works but is significantly less readable than bare `kind: ($kind)`.
diff --git a/site/site/content/adr/en/accepted/adr-006.ncl b/site/site/content/adr/en/accepted/adr-006.ncl
new file mode 100644
index 0000000..92ac6e4
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-006.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-006",
+ title = "Nushell 0.111 String Interpolation Compatibility Fix",
+ slug = "006",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/006",
+ graph = {
+ implements = [],
+ related_to = [],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-007.md b/site/site/content/adr/en/accepted/adr-007.md
new file mode 100644
index 0000000..05f6e64
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-007.md
@@ -0,0 +1,33 @@
+---
+id: "adr-007"
+title: "API Surface Discoverability via #[onto_api] Proc-Macro"
+slug: "007"
+subtitle: "Accepted"
+excerpt: "ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverabl"
+author: "ontoref"
+date: "2026-03-23"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-007", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ontoref-daemon exposes ~28 HTTP routes across api.rs, sync.rs, and other handler modules. Before this decision, the authoritative route list existed only in the axum Router definition — undiscoverable without reading source. MCP agents, CLI users, and the web UI had no machine-readable way to enumerate routes, their auth requirements, parameter shapes, or actor restrictions. OpenAPI was considered but rejected as a runtime dependency that would require schema maintenance separate from the handler code. The `#[onto_api]` proc-macro in `ontoref-derive` addresses this by making the handler annotation the single source of truth: the macro emits `inventory::submit!(ApiRouteEntry{...})` at link time, and `api_catalog::catalog()` collects them via `inventory::collect!`. No runtime registry, no startup allocation, no separate schema file.
+
+Decision
+
+Every HTTP handler in ontoref-daemon must carry `#[onto_api(method, path, description, auth, actors, params, tags)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ApiRouteEntry{...})` at link time. `GET /api/catalog` calls `api_catalog::catalog()` — a pure function over `inventory::iter::<ApiRouteEntry>()` — and returns the annotated surface as JSON. The web UI at `/ui/{slug}/api` renders it with client-side filtering. `describe api [--actor] [--tag] [--auth] [--fmt]` queries this endpoint from the CLI. The MCP tool `ontoref_api_catalog` calls `catalog()` directly without HTTP. This surfaces the complete API to three actors (browser, CLI, MCP agent) from one annotation site per handler.
+
+Constraints
+
+Hard Every public HTTP handler in ontoref-daemon must carry #[onto_api(...)]Hard inventory must remain a workspace dependency gated behind the 'catalog' feature of ontoref-derive; ontoref-ontology must not depend on inventory
+
+Alternatives considered
+
+OpenAPI / utoipa with generated JSON schema — rejected: Requires maintaining a separate schema artifact (openapi.json) and a runtime schema struct tree. The schema can drift from actual handler signatures. utoipa adds ~15 transitive deps including serde_yaml. Violates 'Protocol, Not Runtime' — the schema becomes a runtime artifact rather than a compile-time invariant.Manual route registry (Vec<RouteInfo> in main.rs) — rejected: A manually maintained Vec has guaranteed drift: handlers are added, routes change, and the Vec is updated inconsistently. Proven failure mode in the previous session where insert_mcp_ctx listed 15 tools while the router had 27.Runtime reflection via axum Router introspection — rejected: axum does not expose a stable introspection API for registered routes. Workarounds (tower_http trace layer capture, method_router hacks) are brittle across axum versions and cannot surface handler metadata (auth, actors, params).
+
+Related ADRs
+
+ADR-001
diff --git a/site/site/content/adr/en/accepted/adr-007.ncl b/site/site/content/adr/en/accepted/adr-007.ncl
new file mode 100644
index 0000000..c86a4f8
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-007.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-007",
+ title = "API Surface Discoverability via #[onto_api] Proc-Macro",
+ slug = "007",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/007",
+ graph = {
+ implements = [],
+ related_to = ["adr-001"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-008.md b/site/site/content/adr/en/accepted/adr-008.md
new file mode 100644
index 0000000..9186346
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-008.md
@@ -0,0 +1,33 @@
+---
+id: "adr-008"
+title: "NCL-First Config Validation and Override-Layer Mutation"
+slug: "008"
+subtitle: "Accepted"
+excerpt: "The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust st"
+author: "ontoref"
+date: "2026-03-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-008", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The config surface feature adds per-project config introspection and mutation to ontoref-daemon. Two design questions arise: (1) Where does config field validation live — in NCL contracts, in Rust struct validation, or both? (2) How does a PUT /config/{section} request mutate a project's config without corrupting the source NCL files? Direct mutation via nickel export → JSON → write-back destroys NCL comments, contract annotations, and section merge structure. Duplicating validation in both NCL and Rust creates two sources of truth with guaranteed divergence. The config surface spans ontoref's own .ontoref/config.ncl and all consumer-project configs, making the choice of validation ownership a protocol-level constraint.
+
+Decision
+
+NCL contracts (std.contract.from_validator) are the single validation layer for all config fields. Rust serde structs are contract-trusted readers: they carry #[serde(default)] and consume pre-validated JSON from nickel export — no validator(), no custom Deserialize, no duplicate field constraints. Config mutation via PUT /projects/{slug}/config/{section} never modifies the original NCL source files. Instead it writes a {section}.overrides.ncl file containing only the changed fields plus a _overrides_meta audit record (actor, reason, timestamp, previous values), then appends a single idempotent import line to the entry-point NCL (using NCL's & merge operator so the override wins). nickel export validates the merged result against the section's declared contract before the mutation is committed; validation failure reverts the override file and returns the nickel error verbatim. Ontoref demonstrates this pattern on itself: .ontoref/contracts.ncl declares LogConfig and DaemonConfig contracts applied in .ontoref/config.ncl.
+
+Constraints
+
+Hard The daemon must never write to original NCL config source files during a PUT /config/{section} mutation; only {section}.overrides.ncl may be created or modifiedHard Rust config structs must not implement custom field validation; all field constraints live in NCL contracts applied before JSON reaches RustSoft Every {section}.overrides.ncl file written by the daemon must contain a top-level _overrides_meta record with managed_by, created_at, and entries fields
+
+Alternatives considered
+
+Duplicate validation in Rust (validator crate or custom Deserialize) — rejected: Two validators for the same field inevitably diverge. The NCL contract is already the authoritative schema for documentation, MCP export, and quickref generation — adding a Rust duplicate makes it decorative. validator crate adds 8+ transitive dependencies and requires annotation churn across every config struct.Direct NCL file mutation (read → merge JSON → overwrite) — rejected: nickel export → JSON write-back destroys comments, contract annotations (| C.LogConfig), section merge structure, and in-file rationale. The resulting file is syntactically valid but semantically impoverished. Once a file is overwritten this way, the original structure cannot be recovered from git history if the file was also changed manually between sessions.Separate config store (JSON or TOML side-file) — rejected: A side-file in a different format bypasses NCL type safety entirely — the merge operator and contract validation no longer apply. The daemon would need a custom merge algorithm to reconcile the side-file with the source NCL, and agents would need to understand two config representations. NCL's & merge operator is purpose-built for this use case.
+
+Related ADRs
+
+ADR-002 · ADR-007
diff --git a/site/site/content/adr/en/accepted/adr-008.ncl b/site/site/content/adr/en/accepted/adr-008.ncl
new file mode 100644
index 0000000..6d4214e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-008.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-008",
+ title = "NCL-First Config Validation and Override-Layer Mutation",
+ slug = "008",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/008",
+ graph = {
+ implements = [],
+ related_to = ["adr-002", "adr-007"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-009.md b/site/site/content/adr/en/accepted/adr-009.md
new file mode 100644
index 0000000..ae62b41
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-009.md
@@ -0,0 +1,33 @@
+---
+id: "adr-009"
+title: "Manifest Self-Interrogation Layer — Three Semantic Axes"
+slug: "009"
+subtitle: "Accepted"
+excerpt: "The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a ca"
+author: "ontoref"
+date: "2026-03-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-009", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The manifest.ncl schema described structural facts (layers, modes, consumption modes, tools, config surface) but had no typed layer for self-interrogation: agents and operators could not query why a capability exists, what it needs to run, or what external dependencies have a documented blast radius. The existing tools[] field covered only dev tooling (install_method, version) — no prod/dev classification, no services, no environment variables, no infrastructure dependencies. Practice/node descriptions in core.ncl carry architectural meaning (invariant=true, ADR-backed) but are not the right home for operational, audience-facing descriptions of what a project offers. Capabilities, requirements, and dependency blast-radius analysis are three orthogonal concerns that needed typed, queryable homes in the manifest schema.
+
+Decision
+
+Three new typed arrays are added to manifest_type: capabilities[] (capability_type), requirements[] (requirement_type), and critical_deps[] (critical_dep_type). These are semantically distinct layers: capabilities answer 'what does this project do, why does it exist, and how does it work' with explicit cross-references to ontology node IDs and ADR IDs; requirements classify prerequisites by env_target_type ('Production | 'Development | 'Both) and requirement_kind_type ('Tool | 'Service | 'EnvVar | 'Infrastructure); critical_deps document blast radius — what breaks when an external dependency disappears or breaks its contract — distinct from requirements because the concern is runtime failure impact, not startup prerequisites. A description | String | default = '' field is also added to manifest_type, fixing a pre-existing bug where collect-identity in describe.nu read manifest.description? (always null) instead of a field that existed. describe requirements is added as a new subcommand; describe capabilities is extended to render manifest capabilities; describe guides output gains capabilities/requirements/critical_deps keys so agents on cold start receive full self-interrogation context.
+
+Constraints
+
+Soft capability_type.nodes[] entries must reference valid node IDs declared in .ontology/core.nclHard critical_dep_type.failure_impact must be a non-empty string — undocumented blast radius defeats the purpose of the type
+
+Alternatives considered
+
+Extend tools[] with env and kind fields — rejected: tools[] is semantically 'dev tooling required to build'. Extending it to cover SurrealDB (a production service) or ONTOREF_ADMIN_TOKEN_FILE (an env var) would violate the field's implied meaning. A type named tool_requirement_type that has kind = 'Service is confusing. Separate types with clear names are preferable to an overloaded catch-all.Add capability descriptions to Practice nodes in core.ncl — rejected: Practice nodes are architectural: invariant=true nodes are ADR-protected and represent constraints future contributors must follow. Adding 'what does this offer to end users' to the invariant graph would force every capability description through the ADR lifecycle. capabilities[] is per-project, evolves freely, and belongs to the manifest (the operational layer), not the ontology (the architectural layer).Merge critical_deps into requirements with an is_critical flag — rejected: The concern is different: requirements are prerequisites (the system cannot start without them). critical_deps are runtime load-bearing with a documented blast radius. A requirement that is optional (required = false) can still be a critical dep. The is_critical flag on requirement_type would blur this distinction and make failure_impact logically optional (not required for non-critical items) — creating a type that is partially applicable based on a flag, which is a code smell in typed schemas.
+
+Related ADRs
+
+ADR-001 · ADR-009
diff --git a/site/site/content/adr/en/accepted/adr-009.ncl b/site/site/content/adr/en/accepted/adr-009.ncl
new file mode 100644
index 0000000..a85a172
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-009.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-009",
+ title = "Manifest Self-Interrogation Layer — Three Semantic Axes",
+ slug = "009",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/009",
+ graph = {
+ implements = [],
+ related_to = ["adr-001"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-010.md b/site/site/content/adr/en/accepted/adr-010.md
new file mode 100644
index 0000000..ed447e0
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-010.md
@@ -0,0 +1,33 @@
+---
+id: "adr-010"
+title: "Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades"
+slug: "010"
+subtitle: "Accepted"
+excerpt: "As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual"
+author: "ontoref"
+date: "2026-03-28"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-010", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+As the ontoref protocol evolved (manifest.ncl self-interrogation, typed ADR checks, CLAUDE.md agent entry-point, justfile convention), the adoption tooling relied on static prompt templates with manual {placeholder} substitution. An agent or developer adopting ontoref had no machine-queryable way to know which protocol features were missing from their project, nor how to apply them in a safe, ordered sequence. The template approach produced four separate documents that drifted out of sync with the actual protocol state and required human judgement to determine which ones applied. There was no idempotency guarantee and no check mechanism — a project that had already applied a change would re-read instructions that no longer applied.
+
+Decision
+
+Protocol upgrades for consumer projects are expressed as ordered NCL migration files in reflection/migrations/NNN-slug.ncl. Each migration declares: id (zero-padded 4-digit string), slug, description, a typed check record (FileExists | Grep | NuCmd), and an instructions string interpolated at runtime with project_root and project_name. Applied state is determined solely by whether the check passes — there is no state file. This makes migrations fully idempotent: running `migrate list` on an already-compliant project shows all applied with no side effects. NuCmd checks must be valid Nushell (no bash &&, $env.VAR not $VAR, no bash redirects). Grep checks targeting ADR files must use the glob pattern adrs/adr-[0-9][0-9][0-9]-*.ncl to exclude infrastructure files (adr-schema.ncl, adr-constraints.ncl, _template.ncl) that legitimately contain deprecated field names as schema definitions. The system is exposed via `ontoref migrate list`, `migrate pending`, and `migrate show <id>` — wired into the interactive group dispatch and help system. Migrations are advisory: the system reports state, never applies changes automatically.
+
+Constraints
+
+Hard Any change to templates/, reflection/schemas/*.ncl, .claude/CLAUDE.md, or consumer-facing reflection/modes/ that consumer projects need to adopt must be accompanied by a new migration in reflection/migrations/Hard NuCmd check cmd fields must be valid Nushell — no bash operators (&&, ||, 2>/dev/null), no $VARNAME (must be $env.VARNAME)Soft Grep checks targeting ADR files must scope to adrs/adr-[0-9][0-9][0-9]-*.ncl, not adrs/ or adrs/adr-*.ncl
+
+Alternatives considered
+
+Single monolithic adoption prompt template with {placeholder} substitution — rejected: Produced four separate documents (project-full-adoption-prompt.md, update-ontology-prompt.md, manifest-self-interrogation-prompt.md, vendor-frontend-assets-prompt.md) that drifted out of sync. Required manual judgement to determine which applied to a given project. No idempotency, no machine-queryable state, no ordered application guarantee. Each new protocol feature required updating multiple templates.State file recording applied migration IDs — rejected: State files become stale on branch switches, cherry-picks, and fresh clones. They require commit discipline to keep in sync. A project where someone manually applied the changes without running the migration tool would show the migration as pending despite being satisfied — false negatives. The check-as-truth model has no false negatives by construction.Jinja2/j2 templating for instruction rendering — rejected: The ontoref runtime already runs Nushell for all automation. Adding a j2 dependency for template rendering introduces a new tool to install, configure, and maintain. Runtime string interpolation in Nushell (str replace --all) is sufficient for the two substitution values needed (project_root, project_name) and keeps the migration runner dependency-free.
+
+Related ADRs
+
+ADR-001 · ADR-006
diff --git a/site/site/content/adr/en/accepted/adr-010.ncl b/site/site/content/adr/en/accepted/adr-010.ncl
new file mode 100644
index 0000000..bca35aa
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-010.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-010",
+ title = "Protocol Migration System — Progressive NCL Checks for Consumer Project Upgrades",
+ slug = "010",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/010",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-006"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-011.md b/site/site/content/adr/en/accepted/adr-011.md
new file mode 100644
index 0000000..42aa510
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-011.md
@@ -0,0 +1,33 @@
+---
+id: "adr-011"
+title: "Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema"
+slug: "011"
+subtitle: "Accepted"
+excerpt: "Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable "
+author: "ontoref"
+date: "2026-03-30"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-011", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Reflection modes executed procedures as typed DAGs but lacked two capabilities that caused real failures: (1) modes could run against projects in invalid states — missing ontology files, unavailable tools, incomplete manifests — because preconditions were informational text, not executable checks. An agent following the protocol would execute sync-ontology on a project without core.ncl and get an opaque nickel error instead of a clear block. (2) Modes like sync-ontology require iteration — scan, diff, propose, apply, then verify that drift is zero. If drift remained after one pass, the mode reported success and the agent moved on. There was no mechanism for the protocol to say 'keep going until this condition is met'. Both gaps were identified during a systematic comparison against 45 augmented coding patterns (lexler.github.io/augmented-coding-patterns): Active Partner (#1) requires the system to push back on invalid actions, and Refinement Loop (#36) requires iteration until convergence. A separate action subsystem (ext/action) was considered and rejected in favor of extending the existing mode schema.
+
+Decision
+
+Extend reflection/schema.ncl with two new optional fields on ModeBase: guards (Array Guard) for pre-flight executable checks, and converge (Converge) for post-execution convergence loops. Guards run before any step and can Block (abort) or Warn (continue with message). Converge evaluates a condition command after all steps complete and re-executes failed or all steps up to max_iterations times. Both are backward-compatible — all existing modes export unchanged with guards defaulting to [] and converge being optional.
+
+Constraints
+
+Hard reflection/schema.ncl exports a Guard type with id, cmd, reason, and severity fieldsHard reflection/schema.ncl exports a Converge type with condition, max_iterations, and strategy fieldsHard The mode executor in reflection/nulib/modes.nu evaluates guards before executing steps
+
+Alternatives considered
+
+ext/action — separate action contract schema with gates, events, and convergence — rejected: Creates a parallel execution abstraction competing with modes. Consumer projects would need to learn both modes and actions, and the boundary between them would be ambiguous. The mode schema already has steps, dependencies, and error strategies — guards and converge are natural extensions of the same concept.Executable preconditions — make existing preconditions[] run commands instead of being text — rejected: Preconditions serve a different purpose: they document what the human should verify. Making them executable would lose the documentation function. Guards are a separate concept: machine-checked pre-flight blocks. Both can coexist — preconditions for humans, guards for machines.External convergence via Vapora workflows — let the orchestrator handle iteration — rejected: Convergence is a property of the mode itself, not of the orchestrator. sync-ontology should declare that it iterates until zero drift regardless of whether it runs via CLI, Vapora, or CI. Pushing this to the orchestrator means every orchestrator must know which modes need iteration and under what conditions — that knowledge belongs in the mode contract.
+
+Related ADRs
+
+ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-011.ncl b/site/site/content/adr/en/accepted/adr-011.ncl
new file mode 100644
index 0000000..6e4e830
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-011.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-011",
+ title = "Mode Guards and Convergence — Active Partner and Refinement Loop in the Mode Schema",
+ slug = "011",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/011",
+ graph = {
+ implements = [],
+ related_to = ["adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-012.md b/site/site/content/adr/en/accepted/adr-012.md
new file mode 100644
index 0000000..16d2612
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-012.md
@@ -0,0 +1,33 @@
+---
+id: "adr-012"
+title: "Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains"
+slug: "012"
+subtitle: "Accepted"
+excerpt: "Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no "
+author: "ontoref"
+date: "2026-04-05"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-012", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Consumer projects have project-type-specific CLI needs that ontoref's generic command set cannot satisfy. A PersonalOntology project has CFP pipelines, career schemas, and opportunity tracking that no other repo_kind needs. A DevWorkspace project has workspace cards, cluster state dimensions, and production-readiness gates. These capabilities lived as local scripts (scripts/jpl.nu) — invisible to ontoref's help system, absent from describe capabilities, and not portable to other PersonalOntology projects. The problem had two layers: (1) how to conditionally load domain-specific Nu commands without breaking the existing dispatcher, and (2) how to make domain commands discoverable (help, describe capabilities) and aliasable (ore prov, personal state). Nu's module system (`use`, `source`, `overlay use`) is compile-time: paths must be known at parse time and `overlay use` creates an isolated namespace that cannot extend `def main` in the existing dispatcher. Runtime loading of arbitrary Nu modules is architecturally impossible in the current Nu model.
+
+Decision
+
+Implement a bash-layer domain dispatch system. The ontoref bash wrapper (install/ontoref-global) resolves the first CLI argument against $ONTOREF_ROOT/domains/{arg}/repo_kinds.txt before delegating to the Nu dispatcher. If the argument matches a domain directory name (or a registered alias from domains/aliases.txt) AND the current project's repo_kind appears in that domain's repo_kinds.txt, dispatch directly to nu domains/{id}/commands.nu passing remaining args. Each domain ships three files: domain.ncl (NCL contract declaring commands, pages, repo_kinds, short_alias), commands.nu (Nu script with def main [...args] entry point), and repo_kinds.txt (plain-text list of matching repo_kind values, grep-readable by the bash wrapper without running nickel). install.nu copies the entire domains/ tree to $data_dir/domains/ and generates domains/aliases.txt mapping short_alias → domain_id. Short aliases also create standalone bin wrappers at $bin_dir/alias.
+
+Constraints
+
+Hard Every domain directory under $ONTOREF_ROOT/domains/{id}/ must contain domain.ncl, commands.nu, and repo_kinds.txtHard commands.nu must declare def main [...args: string] as its entry point — no dynamic use/source calls inside Nu scripts
+
+Alternatives considered
+
+Nu overlay use for runtime domain loading — rejected: overlay use creates an isolated namespace — commands defined in an overlayed module cannot be called by name in the parent scope. It is also parse-time in module context. Confirmed broken: nu -c 'use FILE *; command args' causes infinite recursion when called from def main.Add domain commands directly to reflection/bin/ontoref.nu — rejected: Would require hardcoding every domain's commands in the main dispatcher, or using dynamic path strings in `use` which Nu forbids. Also violates the no-enforcement axiom — the main dispatcher should not know about PersonalOntology specifics.Project-local scripts/ (scripts/jpl.nu approach) — rejected: Invisible to ore help and ore describe capabilities. Not portable across PersonalOntology projects. Namespace requires prefix (jpl cfp vs cfp). Dispatch requires knowing the script path.
+
+Related ADRs
+
+ADR-001 · ADR-006
diff --git a/site/site/content/adr/en/accepted/adr-012.ncl b/site/site/content/adr/en/accepted/adr-012.ncl
new file mode 100644
index 0000000..aec46a9
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-012.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-012",
+ title = "Domain Extension System — Bash-Layer Dispatch for repo_kind-Conditional CLI Domains",
+ slug = "012",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/012",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-006"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-013.md b/site/site/content/adr/en/accepted/adr-013.md
new file mode 100644
index 0000000..6450a79
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-013.md
@@ -0,0 +1,33 @@
+---
+id: "adr-013"
+title: "VCS Abstraction Layer — Uniform jj/git API via vcs.nu"
+slug: "013"
+subtitle: "Accepted"
+excerpt: "ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration pro"
+author: "ontoref"
+date: "2026-04-07"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-013", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ontoref modules that interact with version control (opmode.nu, git-event.nu, jjw.nu, init-repo.nu) historically hardcoded ^git subcommands. As jj adoption grows among contributors and orchestration projects (vapora), hardcoded git calls produce silent failures: jj repos have .jj/ but may lack a .git/ HEAD in expected locations, and jj semantics differ (the working copy is always a commit, @- is the parent, `jj file show` replaces `git show HEAD:path`). The dual-VCS problem had two layers: (1) detection — which VCS is active in a given project root, and (2) semantics — same logical operation (show last committed state, restore a file, get remote URL) expressed differently per VCS. Spreading detection logic across modules produces duplication and makes future VCS additions (e.g. Pijul, Sapling) a multi-file change.
+
+Decision
+
+Introduce reflection/modules/vcs.nu as the single VCS abstraction layer. It exports: detect (returns 'jj' | 'git' | 'none' via filesystem check — .jj/ presence), is-repo, show-committed (jj: `jj file show -r @-`; git: `git show HEAD:path`), restore-file (jj: `jj restore --from @-`; git: `git checkout --`), remote-url (jj: `jj git remote list`; git: `git remote get-url origin`), current-branch (jj: `jj log -r @ --no-graph -T bookmarks`; git: `git branch --show-current`), uncommitted-files (jj: `jj diff --summary -r @`; git: `git status --porcelain`), commit-count (jj: `jj log --no-graph -T '' | lines | length`; git: `git rev-list --count HEAD`). All ontoref modules must import vcs.nu and call these exports — direct ^git or ^jj subprocess calls inside modules are prohibited. jj and rad are not listed as requirements in ontoref's manifest: they are opt-in tools whose requirements belong in orchestration projects that depend on them.
+
+Constraints
+
+Hard All VCS subprocess calls in reflection/modules/ and reflection/bin/ must go through vcs.nu exports — no direct ^git or ^jj calls outside vcs.nu itselfHard jj and rad must not appear as required = true entries in .ontology/manifest.ncl requirements[]
+
+Alternatives considered
+
+Env var ONTOREF_VCS to select backend — rejected: Creates mutable state that can desync from the actual repo state. A repo cloned fresh has no env var set; a contributor switching between git and jj repos would need to update the env var manually. Filesystem detection is always correct without configuration.Per-module inline detection (duplicate detect logic in each file) — rejected: Already the de-facto state before vcs.nu. Duplicated detection means any change to jj semantics (e.g. a jj CLI flag change) requires hunting every module. The abstraction cost is one import line per module.Wrap the entire CLI in a shim that translates git commands to jj — rejected: Shim-layer translation is fragile — git and jj command surfaces are not isomorphic (jj has no git stash equivalent; jj describe vs git commit -m). The operations ontoref needs are a small, well-defined set; a typed Nu module is a cleaner contract than a command-translation shim.
+
+Related ADRs
+
+ADR-012
diff --git a/site/site/content/adr/en/accepted/adr-013.ncl b/site/site/content/adr/en/accepted/adr-013.ncl
new file mode 100644
index 0000000..a348c5b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-013.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-013",
+ title = "VCS Abstraction Layer — Uniform jj/git API via vcs.nu",
+ slug = "013",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/013",
+ graph = {
+ implements = [],
+ related_to = ["adr-012"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-014.md b/site/site/content/adr/en/accepted/adr-014.md
new file mode 100644
index 0000000..f347a0f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-014.md
@@ -0,0 +1,33 @@
+---
+id: "adr-014"
+title: "Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL"
+slug: "014"
+subtitle: "Accepted"
+excerpt: "ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disa"
+author: "ontoref"
+date: "2026-04-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-014", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ontoref-daemon exposes optional services (MCP, GraphQL) compiled in via Cargo feature flags. Once compiled, these services were always active for the lifetime of the process. Two scenarios require disabling them at runtime without restart: (1) security incident — temporarily disable a surface while investigating a compromise; (2) operator choice — enable graphql during a debug session, disable it in production. The alternative of restarting the daemon is disruptive because it drops all in-memory sessions, actors, and notification queues. Compile-time feature flags solve the binary presence problem but cannot address runtime availability.
+
+Decision
+
+Introduce ServiceFlags (pub struct in api.rs) holding one AtomicBool per toggleable service, gated by the corresponding feature flag. ServiceFlags::new() initialises all flags to true (enabled). AppState holds Arc<ServiceFlags> shared across all clones. The toggle check lives in a route_layer middleware on the sub-router for each service — not inside the service handlers themselves — so the check is enforced regardless of which handler a request reaches. Two toggle surfaces are provided: (1) REST API: PUT /api/services/:service {"enabled": bool} — daemon admin Bearer required; (2) UI: POST /ui/manage/services/:service/toggle — AdminGuard (cookie session). Both surfaces return the new state. The manage page shows each compiled-in service with an HTMX toggle button; the navbar badges reflect runtime state on every page render.
+
+Constraints
+
+Hard ServiceFlags::new() must initialise all AtomicBool flags to true — a compiled-in service is always enabled at startupHard Any new optional service added to the daemon must add an AtomicBool to ServiceFlags and a route_layer toggle middleware on its sub-routerSoft Service toggle endpoints require daemon admin credentials — project-level auth is insufficient
+
+Alternatives considered
+
+Restart daemon with different feature flags or config — rejected: Restart drops SessionStore (all active logins), actor registry, notification queue, and NATS subscriptions. A 1-second outage is acceptable for planned maintenance but not for rapid incident response.RwLock<bool> per service in AppState — rejected: RwLock introduces lock contention on every request. The toggle check does not need mutual exclusion with writes — a store and a load never run concurrently in a way that would corrupt state. Relaxed AtomicBool is sufficient and faster.Dynamic axum Router rebuild — swap out the sub-router entirely — rejected: axum Router is not live-rebuildable without replacing the entire tower Service. This would require Arc<RwLock<Router>>, a custom Service wrapper, and would still incur a lock per request. The middleware approach achieves the same result with orders of magnitude less complexity.
+
+Related ADRs
+
+ADR-002 · ADR-005
diff --git a/site/site/content/adr/en/accepted/adr-014.ncl b/site/site/content/adr/en/accepted/adr-014.ncl
new file mode 100644
index 0000000..6eb257c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-014.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-014",
+ title = "Runtime Service Toggles — AtomicBool Flags for MCP and GraphQL",
+ slug = "014",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/014",
+ graph = {
+ implements = [],
+ related_to = ["adr-002", "adr-005"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-015.md b/site/site/content/adr/en/accepted/adr-015.md
new file mode 100644
index 0000000..d48995f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-015.md
@@ -0,0 +1,33 @@
+---
+id: "adr-015"
+title: "MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory"
+slug: "015"
+subtitle: "Accepted"
+excerpt: "ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name"
+author: "ontoref"
+date: "2026-04-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-015", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ontoref-daemon exposes 33 MCP tools via the rmcp ToolRouter in crates/ontoref-daemon/src/mcp/mod.rs. The authoritative tool implementation lives in each tool struct's `ToolBase`/`AsyncTool` impls (name, description, schema). However, the agent-facing tool catalog returned by `ontoref_help` was a hand-typed JSON literal (~100 lines, mod.rs:1129-1226) duplicating every tool's name, description, and parameter shape. This is the same failure mode ADR-007 documents as the original motivation for `#[onto_api]`: the previous-session bug where `insert_mcp_ctx` listed 15 tools while the router had 27. ADR-007 fixed that drift for the HTTP API surface (inventory-collected `ApiRouteEntry`) but did not extend the pattern to MCP. As of 2026-04-26 the manifest claim 'daemon exposes 33 MCP tools' is also a hand-maintained string in `.ontology/manifest.ncl`. With the rate of MCP tool additions through 2026-Q1 (qa, bookmarks, actions, config, ontology extensions all added in separate sessions), drift was becoming inevitable.
+
+Decision
+
+Every MCP tool struct in ontoref-daemon must carry `#[onto_mcp_tool(name, description, category, params)]`. The proc-macro (in `crates/ontoref-derive`) emits `inventory::submit!(ontoref_ontology::McpToolEntry{...})` at link time and leaves the annotated struct unchanged — the existing `ToolBase` and `AsyncTool` impls are untouched. A new pure function `ontoref_daemon::mcp::catalog()` walks `inventory::iter::<McpToolEntry>()`, sorts by name, and returns `Vec<&'static McpToolEntry>`. `HelpTool::invoke` now serializes `catalog()` instead of holding a hand-typed JSON literal. `McpToolEntry` lives in `ontoref-ontology` next to `ApiRouteEntry` and reuses `ApiParam` for parameter metadata — both are protocol surfaces, the type is generic. `tool_router()`'s compile-time `with_async_tool::<T>()` list is left as-is; the Rust type system requires the type list at compile time and a separate macro architecture would be needed to derive it from inventory (out of scope for this ADR).
+
+Constraints
+
+Hard Every MCP tool struct in ontoref-daemon must carry #[onto_mcp_tool(...)]Soft The number of #[onto_mcp_tool] annotations must equal the number of with_async_tool::<T>() calls in tool_router()
+
+Alternatives considered
+
+Generate the help JSON from ToolBase::name() + description() directly via reflection — rejected: ToolBase exposes name and description but not parameter metadata in a structured form (input_schema returns a JsonObject blob, not the per-param required/values/note shape that ontoref_help emits). Walking the JSON schema and reverse-engineering the per-param hints is brittle. The inventory entry lets us declare the agent-facing param documentation in a stable, typed shape next to the ToolBase impl.Replace tool_router()'s with_async_tool::<T>() list with macro-driven iteration over inventory — rejected: rmcp's ToolRouter requires the tool type at compile time (generic associated types over each tool's Parameter/Output types). Driving registration from inventory entries — which are runtime values of `&'static McpToolEntry` — would require either a build.rs that emits the registration list or a macro that takes the full type list as input. Either is feasible but is a larger architectural change with no immediate reliability win beyond what the inventory already provides for the help/catalog surface. Deferred.Skip the macro and write an `inventory::submit!` block manually next to each tool — rejected: Eliminates the macro infrastructure but loses the validation that #[onto_mcp_tool] applies (key spelling check, param string parsing reuse). Manual blocks also bypass the file!() capture for source-file traceability that ApiRouteEntry already uses.
+
+Related ADRs
+
+ADR-007 · ADR-001
diff --git a/site/site/content/adr/en/accepted/adr-015.ncl b/site/site/content/adr/en/accepted/adr-015.ncl
new file mode 100644
index 0000000..5c64cb5
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-015.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-015",
+ title = "MCP Tool Catalog via #[onto_mcp_tool] Proc-Macro + Inventory",
+ slug = "015",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/015",
+ graph = {
+ implements = [],
+ related_to = ["adr-007", "adr-001"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-016.md b/site/site/content/adr/en/accepted/adr-016.md
new file mode 100644
index 0000000..7103517
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-016.md
@@ -0,0 +1,33 @@
+---
+id: "adr-016"
+title: "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction"
+slug: "016"
+subtitle: "Accepted"
+excerpt: "Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside"
+author: "ontoref"
+date: "2026-05-01"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-016", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Ontoref itself was extracted from stratumiops (ADR-001). The same pattern recurred in the provisioning project: buildkit-launcher (build substrate) and backup-manager (backup orchestration) grew inside provisioning but serve a broader class of consumers — vapora, workspace infras, CI pipelines, and third-party projects. No formal criterion existed for deciding when a component is ready to become a standalone peer project. Without a criterion, the decision is arbitrary and either premature (decomposition overhead before consumer plurality) or delayed (host coupling accretes, extraction becomes expensive).
+
+Decision
+
+A component is extracted as a standalone peer project when it passes all four criteria: (1) Orthogonal concern — the component's core domain is not the host project's core domain; (2) Consumer plurality — at least two distinct callers exist or are immediately planned; (3) Release cadence divergence — the component's evolution is not gated by the host project's release cycle; (4) Config path-agnostic — the component can receive its config from any caller without importing host infrastructure (workspace crates, host config loaders, host schema registries). The extracted project registers in ontoref before any code moves. The host project retains extension-side artifacts (schemas, defaults, component declarations) that allow workspace infras to declare directives for the extracted tool.
+
+Constraints
+
+Hard An extracted project must have .ontology/core.ncl, .ontology/state.ncl, and adrs/adr-001 committed before any code from the host is movedHard The extracted project must not import workspace crates, config loaders, or schema registries from the host project
+
+Alternatives considered
+
+Extract only when a second consumer exists and requests it — rejected: Reactive extraction means coupling has already accreted. By the time a second consumer requests the component, host infrastructure imports are deep. Proactive evaluation at the four-criterion gate is cheaper than reactive untangling.Keep all tools inside provisioning as internal workspace crates, expose via provisioning CLI — rejected: This routes all callers through provisioning's release cycle and binary. vapora and workspace CI pipelines cannot use the tool without depending on provisioning. The four-criterion test exists precisely to identify when this constraint becomes incorrect.Publish extracted crates to crates.io immediately — rejected: Published crates require stable API contracts before the consumer relationship is proven. Path-based standalone project references allow simultaneous development of the extracted project and its consumers without publication ceremony.
+
+Related ADRs
+
+ADR-001
diff --git a/site/site/content/adr/en/accepted/adr-016.ncl b/site/site/content/adr/en/accepted/adr-016.ncl
new file mode 100644
index 0000000..8db82bc
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-016.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-016",
+ title = "Component Lift-Out Pattern: Four-Criterion Gate for Standalone Extraction",
+ slug = "016",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/016",
+ graph = {
+ implements = [],
+ related_to = ["adr-001"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-017.md b/site/site/content/adr/en/accepted/adr-017.md
new file mode 100644
index 0000000..615db1e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-017.md
@@ -0,0 +1,33 @@
+---
+id: "adr-017"
+title: "Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access"
+slug: "017"
+subtitle: "Accepted"
+excerpt: "The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class conce"
+author: "ontoref"
+date: "2026-05-01"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-017", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The provisioning registry (zot OCI) became the primary coordination hub for domain and mode artifacts (ADR-016 consequences, migration 0015). This introduced registry credentials as a first-class concern: every project that pushes or pulls artifacts needs credentials, and those credentials must be distributed across actors (developers, CI pipelines, the ontoref daemon, AI agents, ops tooling) with different access levels. The naive model — environment variables or ambient ~/.docker/config.json — has three compounding failure modes: (1) credentials are ambient and unscoped, so an operation against project A can use credentials that belong to project B; (2) there is no distribution mechanism — adding a new team member or CI key requires manual redistribution of a shared secret; (3) there is no audit trail for credential access or changes. A supply-chain attack via misconfigured registry namespace endpoints is the concrete risk: if the daemon proxied registry calls, any actor with MCP access could redirect them to an arbitrary endpoint using ambient credentials.
+
+Decision
+
+Registry credentials are managed through a per-project src-vault stored as an OCI artifact in the same ZOT registry it protects. The src-vault uses sops with age multi-recipient encryption: each actor role has its own age keypair, and credential files are encrypted for the exact set of recipients that need access. The vault backend (restic or kopia) handles versioning and local copies; the ZOT registry handles distribution. Each project has a local access.sops.yaml in ~/.config/ontoref/vaults/<project-slug>/ containing three fields: zot_username, zot_password, and vault_key. All three are encrypted by the actor's master age private key (.kage), which lives at an external path the actor controls (hardware key, encrypted disk, or declared path in config.ncl) — never inside the vault directory. At operation time, sops decrypts access.sops.yaml in memory: zot_username and zot_password are used to pull the src-vault OCI artifact via a DOCKER_CONFIG tmpdir that is deleted immediately after; vault_key is passed as RESTIC_PASSWORD or KOPIA_PASSWORD env var for the duration of the vault operation and never written to disk. No plaintext credential of any kind persists beyond the operation scope. Credential resolution runs exclusively in the ontoref CLI — the daemon is structurally excluded. All oras invocations use an isolated DOCKER_CONFIG tmpdir; no ambient ~/.docker/config.json is consulted. Access logs are appended to a jsonl file stored as a layer in the same src-vault OCI artifact and mirrored locally in ~/.config/ontoref/vaults/<project-slug>/logs/access.jsonl.
+
+Constraints
+
+Hard The credentials required to pull the src-vault OCI artifact from ZOT must be stored outside the vault itself — each src-vault has its own access credentials, held in the actor's local .kage, not inside any vault it protectsHard RegistryEntry must not use credential_env — only credential_sops or credential_oidc are valid credential reference fieldsHard Every *.sops.yaml credential file must include at minimum the admin and the bound_actor recipients for its access classHard Any domain or mode that pushes or pulls from a registry must declare uses_registry referencing the RegistryEntry id in manifest.nclSoft All sops operations on registry credential files must go through ore secrets — direct sops invocations bypass lock state and audit logHard Every oras invocation must use DOCKER_CONFIG pointing to a tmpdir containing only the credential for the target registry endpoint — no ambient ~/.docker/config.jsonHard vault_key must never be written to disk in plaintext — it is decrypted from access.sops.yaml into RESTIC_PASSWORD or KOPIA_PASSWORD for the duration of the operation onlySoft All vault snapshot operations must use vault-backend.nu — direct restic or kopia invocations are not permitted in recipes or modulesHard Every push of src-vault/<project>:latest to ZOT must produce a cosign signature; every pull must verify the signature before the artifact is trusted
+
+Alternatives considered
+
+Environment variables (REGISTRY_TOKEN, DOCKER_CONFIG) per CI job — rejected: Visible in process list, inherited by subprocesses, logged by CI systems, cannot be scoped to a specific registry endpoint. No revocation without rotating all consumers. The primary attack surface for supply-chain credential leakage.Shared age keyring — one private key distributed to all developers — rejected: Revocation requires rotating the shared key and redistributing to all remaining members — coordination cost scales with team size. No per-actor audit trail. A leaked key compromises all actors simultaneously.HashiCorp Vault or similar external secrets manager — rejected: Introduces a network dependency for every credential resolution. Requires operating a separate service with its own HA, backup, and auth model. The OCI registry is already the coordination hub — using it as the vault distribution backend reuses existing infrastructure and auth.Daemon resolves credentials on behalf of CLI actors — rejected: The daemon is a long-lived process accessible to multiple actors (developer, agent, CI via MCP). Giving it credential resolution capability means any actor with daemon access can trigger registry operations using credentials they do not personally hold. The structural exclusion of the daemon from credential resolution is a load-bearing architectural property.Single credential file per registry (no RO/RW split) — rejected: A single credential with RW access distributed to read-only actors (cdci, ontoref, agent) violates least-privilege. A compromised CI pipeline with RW credentials can push malicious artifacts. The RO/RW split means a compromised read-only credential cannot alter the artifact namespace.
+
+Related ADRs
+
+ADR-005 · ADR-012 · ADR-016
diff --git a/site/site/content/adr/en/accepted/adr-017.ncl b/site/site/content/adr/en/accepted/adr-017.ncl
new file mode 100644
index 0000000..a8f8b08
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-017.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-017",
+ title = "Registry Credential Vault Model: src-vault, Multi-Recipient sops, and Actor-Scoped Access",
+ slug = "017",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/017",
+ graph = {
+ implements = [],
+ related_to = ["adr-005", "adr-012", "adr-016"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-018.md b/site/site/content/adr/en/accepted/adr-018.md
new file mode 100644
index 0000000..0dbedd8
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-018.md
@@ -0,0 +1,33 @@
+---
+id: "adr-018"
+title: "Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal"
+slug: "018"
+subtitle: "Accepted"
+excerpt: "ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflect"
+author: "ontoref"
+date: "2026-05-01"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-018", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-012 introduced a domain extension system that enables project-specific specialization of ontoref. In practice this created a three-level hierarchy: (1) ontoref base — generic protocol and reflection operations; (2) project domain — specialization of ontoref for a specific project type (provisioning, personal, ...); (3) domain instance — a concrete project derived from a domain (a workspace, an infra, a team ontoref). The hierarchy was not formalized: no level declares its identity, no mode declares whether its implementation is complete or delegates to the level above, and no mechanism exists to determine which level answered a given operation. The result is implicit traversal — a caller invoking 'build-docs' has no way to know whether the operation ran at level 1, 2, or 3, whether it merged contributions from multiple levels, or whether it silently fell through to the base because the domain had not implemented it yet. The discussion that produced this ADR also identified that resolution strategies cannot be uniform: the correct strategy depends on the mode, the project context, and the adoption phase. A domain in early adoption may Delegate all documentation modes to the base; the same domain at maturity Overrides them with project-specific implementations. The transition between strategies must itself be observable — crossing a level boundary is an architectural event, not an implementation detail.
+
+Decision
+
+Formalize the three-level hierarchy with four mechanisms: (1) Level identity declaration — each ontoref instance declares level.index (1=base, 2=domain, 3=instance), level.name, and level.parent (name of the level above; absent at level 1) in manifest.ncl. This makes level identity explicit and queryable. (2) Per-mode resolution strategy — each reflection mode declares a strategy field using one of four values: 'Override (implementation is complete at this level, traversal stops), 'Delegate (no implementation here, traverse to parent), 'Merge (accumulate fields bottom-up across all levels; lower level wins on conflicts), 'Compose (declare explicit partial inheritance via an extends field naming specific steps or fields to inherit from the parent). Strategy is required at level 2+; absent at level 1 (base is always Override by definition). Implicit absence at level 2+ is treated as 'Delegate with a Soft validation warning. (3) FSM-bound strategy transitions — when a mode's strategy is expected to change as the project matures, declare a state dimension in state.ncl whose current_state tracks the strategy value. The transition from 'Delegate to 'Override (or any other pair) is then an observable FSM event: tracked in state.ncl, visible in ore describe state, and triggerable by the same transition conditions as any other dimension. Strategy changes are architectural events, not silent refactors. (4) Observable traversal via ore mode resolve — the command 'ore mode resolve <id>' reports which level answered the operation, the strategy applied, the source file, and the reason (declared strategy or FSM state). No mode resolution is ever silent.
+
+Constraints
+
+Hard Any ontoref instance at level 2 or 3 must declare level.index, level.name, and level.parent in manifest.nclSoft Every reflection mode at level 2+ must declare strategy explicitly; implicit absence is a Soft violationHard No Delegate chain may terminate without an Override at some level — every mode invocation must resolve to a concrete implementationSoft If state.ncl declares a dimension for a mode's strategy, its current_state must match the strategy field declared in the mode NCLHard A mode with strategy = 'Compose must declare an extends field; every step or field reference in extends must exist in the named parent mode
+
+Alternatives considered
+
+Implicit inheritance — current state, no declaration required — rejected: Makes level traversal invisible. Coupling is undiagnosable. Adoption phase is unknown. Boundary crossing is unobservable. This is the state that produced the architectural confusion this ADR resolves.Global strategy per level — one strategy applies to all modes at a given level — rejected: Domains specialize incrementally. A global Override for level 2 blocks early adoption (all modes must be implemented before any work). A global Delegate for level 2 makes domains invisible (nothing is ever implemented). Per-mode strategy reflects the real adoption curve.Override-only — levels either implement fully or do not appear — rejected: Eliminates phased adoption. A domain cannot exist in the system until it has implemented every mode — a steep entry cost that contradicts ADR-001 (voluntary coherence, no enforcement). Delegate and Compose are specifically needed for incremental adoption.Separate level.ncl file instead of level field in manifest.ncl — rejected: manifest.ncl already holds structural self-description (requirements, capabilities, registry topology, layers). Level identity is structural metadata of the same kind. A separate file adds coordination overhead (two files to keep in sync, two files for describe to read) for no additional expressiveness.
+
+Related ADRs
+
+ADR-001 · ADR-010 · ADR-011 · ADR-012
diff --git a/site/site/content/adr/en/accepted/adr-018.ncl b/site/site/content/adr/en/accepted/adr-018.ncl
new file mode 100644
index 0000000..a6a8c73
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-018.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-018",
+ title = "Level Hierarchy and Mode Resolution Strategy — Observable Boundary Traversal",
+ slug = "018",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/018",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-010", "adr-011", "adr-012"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-019.md b/site/site/content/adr/en/accepted/adr-019.md
new file mode 100644
index 0000000..b27a164
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-019.md
@@ -0,0 +1,33 @@
+---
+id: "adr-019"
+title: "Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault"
+slug: "019"
+subtitle: "Accepted"
+excerpt: "ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.so"
+author: "ontoref"
+date: "2026-05-03"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-019", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-017 established per-project credential vaults as OCI artifacts in ZOT, encrypted with sops + age multi-recipient. The model held one recipient set per vault: every actor who could decrypt access.sops.yaml could decrypt every credential file inside the vault. Real projects (libre-wuji, the canonical multi-tenant example) need stronger separation — a single project hosts multiple clients with distinct services, AI agents that operate with restricted access, and developer/admin roles that occasionally overlap. Three concrete failure modes appear: (1) credential-of-clientB is visible to anyone who can open the vault even when only working on clientA — recipient lists are coarse and indiscriminate; (2) AI agents with read-only intent receive credentials whose blast radius exceeds their declared scope; (3) blast-radius limitation requires per-file recipient sets, which the original single-set model does not express. A naive answer is multi-vault — one vault_id per tenant or per environment — but it cascades through every layer (schema, helpers, recipes, dispatcher, migration) and creates an architectural debt without justifying a real isolation requirement (separate master keys, separate restic repos) for the typical case.
+
+Decision
+
+Tenant isolation within a single project is expressed via sops creation_rules, declared in project.ncl::sops as recipient_groups (named lists of age public keys) and recipient_rules (path_regex to group-union mappings). The bootstrap recipe generates <vault_dir>/.sops.yaml from these declarations and sops natively encrypts each file with the union of declared groups. One vault_id per project remains the unit. Multi-vault is explicitly NOT implemented and remains out of scope until a project requires HARD isolation (separate master keys, separate restic repos, compliance-grade separation surviving accidental cross-decryption); such a case requires a future ADR. When recipient_rules are declared, project.ncl is the single source of truth: secrets-add-key and secrets-remove-key error out and direct the operator to edit project.ncl plus run secrets-rekey, which regenerates .sops.yaml and re-encrypts every *.sops.yaml file. Three adoption templates (single-team, multi-tenant, agent-first) ship in install/resources/templates/sops/ as copy-paste starting points; a project may adopt any pattern or none.
+
+Constraints
+
+Hard Every group referenced in recipient_rules must be declared in recipient_groupsHard A rule whose group union resolves to zero recipients is rejected at bootstrap and rekeyHard secrets-add-key and secrets-remove-key recipes must error when project declares recipient_rules; canonical workflow is edit project.ncl + secrets-rekeyHard Every *.sops.yaml under <vault_dir>/ must match at least one declared rule when recipient_rules is non-emptyHard Projects must not declare a multi-vault structure (e.g. sops.vaults map). Multi-vault adoption requires a new ADR superseding or extending this oneSoft Three adoption templates (single-team, multi-tenant, agent-first) live under install/resources/templates/sops/ and are referenced by qa.ncl::credential-vault-templates
+
+Alternatives considered
+
+Multi-vault: project.ncl::sops.vault_id (single string) becomes sops.vaults (record of named SopsConfigs) — rejected: Cascades through every layer (schema, helpers, recipes, dispatcher), forcing a 12-component refactor and a migration for every existing project to express what one optional schema field accomplishes via sops creation_rules. The HARD isolation it provides (separate master keys, separate restic repos) is rarely required; per-file routing covers the common case while leaving the door open for a future multi-vault ADR if a project genuinely needs filesystem-level separation.Single recipient set + role-based decryption gating in helper code — rejected: Would require a custom layer over sops and reinvent recipient routing. sops already does it natively via creation_rules. Inventing a parallel mechanism doubles the surface and breaks composition with sops tooling (sops --decrypt, updatekeys).Externalize tenant credentials to an external secret manager (e.g. HashiCorp Vault) — rejected: Adds an external runtime dependency that contradicts the ADR-017 invariant of self-contained, distribution-via-OCI credentials. Reasonable for projects that already operate such a system, but inappropriate as the default ontoref pattern.
+
+Related ADRs
+
+ADR-017 · ADR-015
diff --git a/site/site/content/adr/en/accepted/adr-019.ncl b/site/site/content/adr/en/accepted/adr-019.ncl
new file mode 100644
index 0000000..bba4418
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-019.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-019",
+ title = "Per-File Recipient Routing for Tenant Isolation in lieu of Multi-Vault",
+ slug = "019",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/019",
+ graph = {
+ implements = [],
+ related_to = ["adr-017", "adr-015"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-021.md b/site/site/content/adr/en/accepted/adr-021.md
new file mode 100644
index 0000000..b78e364
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-021.md
@@ -0,0 +1,33 @@
+---
+id: "adr-021"
+title: "Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO"
+slug: "021"
+subtitle: "Accepted"
+excerpt: "The daemon historically owned its own session store, Argon2id password hashing, Tera template engine init, and cookie/Bearer extraction logic. Lian-build re-implemented the same surface independently. "
+author: "ontoref"
+date: "2026-05-12"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-021", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The daemon historically owned its own session store, Argon2id password hashing, Tera template engine init, and cookie/Bearer extraction logic. Lian-build re-implemented the same surface independently. With ontoref-auth and ontoref-ui shipping as standalone foundation siblings (ADR-016 lift-out pattern), every daemon-internal copy is duplicate code maintained out of band: bug fixes, hardening, and Ed25519 token issuance for SSO had to be re-applied to each consumer. Federation across the family also required a common token vocabulary (issuer, kid, audience) that no daemon had so far. Without consolidation, the SSO mode that lets multiple panels share authentication remains unimplementable.
+
+Decision
+
+Migrate ontoref-daemon to depend on `ontoref-auth` (path dep, axum+persist features) and `ontoref-ui` (path dep, gated behind the existing `ui` feature). Re-export `Role` and `KeyEntry` from `ontoref_auth` through `crate::registry`; rewrite `crate::session` as a thin facade that preserves the historic daemon API surface (`SessionEntry`, `SessionStore`, `SessionView`, `RevokeResult`, `COOKIE_NAME`, `extract_cookie`) while delegating storage and lifecycle to `ontoref_auth::SessionStore`. Replace `tera::Tera::new(glob)` initialization with `ontoref_ui::init_tera(TeraOptions)`; `AppState.tera` now holds `Option<Arc<RwLock<ontoref_ui::TeraEnv>>>`. Daemon-specific page templates (manage.html, project_picker.html, dashboard.html, etc.) remain in `crates/ontoref-daemon/templates/` and continue to be loaded via the `template_dirs` consumer-override path. Add `TokenIssuer` (Ed25519, seed loaded from `ONTOREF_SIGNING_KEY_FILE` when set, ephemeral otherwise with WARNING) and `SignedTokenProvider` to `AppState`. Publish JWKS at `GET /.well-known/ontoref-keys.json` and introspection at `POST /.session/introspect` — both unauthenticated by SSO contract.
+
+Constraints
+
+Hard ontoref-daemon must declare ontoref-auth as a path dependency with axum and persist features enabledHard ontoref-daemon must declare ontoref-ui as an optional path dependency, gated behind the ui featureHard ontoref-daemon source must not call tera::Tera::new directly — Tera initialization flows through ontoref_ui::init_teraHard The daemon must publish its JWKS at /.well-known/ontoref-keys.jsonHard The daemon must publish RFC 7662-shaped introspection at /.session/introspect
+
+Alternatives considered
+
+Wholesale call-site migration to ontoref_auth::AuthUser extractor and ontoref_auth::Session — rejected: The daemon's AuthUser is multi-project: it extracts a Path<String> slug and validates that the session's scope matches the requested project. ontoref_auth::AuthUser is single-tenant — it resolves a credential to an Identity without slug-awareness. Replacing the daemon's extractor would require either (a) duplicating slug validation in every handler, or (b) wrapping ontoref-auth's extractor with a daemon-specific layer that re-does the slug check. Both are strictly more code than keeping the daemon's slug-aware extractor and routing it to the shared SessionStore.Bridge ontoref_auth::Role and registry::Role with a daemon-local enum + From impls — rejected: The variants and serde representations are identical. The two-enum bridge would force every keys-overlay.json reader, every projects.ncl serializer, and every UI template that reads the role string to perform conversions. The re-export keeps the wire format identical and the type stable.Embed ontoref-ui's full base.html and override only nav fragments — rejected: ontoref-ui's base.html does not have block hooks for the daemon's domain ontology widget, registry topology bar, or vault state row. Either ontoref-ui grows daemon-specific blocks (which violates the harmonization-first lift-out principle — A.2 explicitly keeps generic core/), or the daemon defines its own base.html that includes ontoref-ui's partials piece by piece. The full-override path the daemon already takes is simpler and preserves the exact pre-migration rendering.Defer JWKS until the SignedTokenProvider has a key rotation policy implemented in code — rejected: TokenIssuer::rotate() is already implemented in ontoref-auth and exposes JwksSnapshot containing both current and previous public keys. The policy decision (when to rotate, when to forget_previous) is operational, not structural — it lives in the rotation playbook, not in the JWKS endpoint code. Shipping JWKS now with an ephemeral default unblocks SSO; the rotation policy ships as runbook content alongside the panel daemon's adoption of SSO mode.
+
+Related ADRs
+
+ADR-001 · ADR-005 · ADR-016
diff --git a/site/site/content/adr/en/accepted/adr-021.ncl b/site/site/content/adr/en/accepted/adr-021.ncl
new file mode 100644
index 0000000..b40079c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-021.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-021",
+ title = "Auth and UI Lift-Out: ontoref-daemon consumes ontoref-auth + ontoref-ui; JWKS and Introspect Exposed for SSO",
+ slug = "021",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/021",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-005", "adr-016"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-023.md b/site/site/content/adr/en/accepted/adr-023.md
new file mode 100644
index 0000000..6ac1019
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-023.md
@@ -0,0 +1,33 @@
+---
+id: "adr-023"
+title: "Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate"
+slug: "023"
+subtitle: "Accepted"
+excerpt: "Ontoref's current substrate is plana: NCL files on disk (.ontology/, adrs/, reflection/) + git history + filesystem watching + Nushell automation + Rust crates loading NCL into typed structs. Verificat"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-023", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Ontoref's current substrate is plana: NCL files on disk (.ontology/, adrs/, reflection/) + git history + filesystem watching + Nushell automation + Rust crates loading NCL into typed structs. Verification of any claim about a project requires running ontoref against the full SST: there is no way to attest 'this project's ontology has property X at time T' without re-evaluating the whole tree. The Self-Describing and DAG-Formalized Knowledge axioms are therefore aspirational — they describe what the protocol promises, not what an external verifier can confirm. Multi-actor concurrency is unaddressed: who wrote which assertion, when, with what authority, is implicit in git history rather than in the protocol. The recent commits 82a358f (#[onto_mcp_tool] catalog, OCI vault, ADR-018 mode hierarchy validation), 6721daf (NCL-first workflow generator), and ADRs 021 and 022 each surfaced the same structural deficit: every layer that needs typed contracts, signed operations, or external attestations reimplements them ad hoc. The verifiable substrate, prototyped in .coder/2026-05-24-verifiable-substrate-prototype.plan.md and consolidated in .coder/2026-05-25-implementation-final.plan.md, addresses this by replacing the substrate itself: every assertion about a project becomes a typed, signed, content-addressed operation in an append-only log; the materialized state (triples) and the cryptographic commitment (Merkle state root) are derivable from the log; JetStream carries operations to validators, materializers, and agents as a fabric, not as a runtime dependency. The change is structural, not incremental.
+
+Decision
+
+Adopt the verifiable substrate as the canonical substrate for ontoref, following the gates G1 through G12 of .coder/2026-05-25-implementation-final.plan.md. Construct the substrate in an isolated branch (ontoref-kernel) on top of the existing main. The substrate consists of seven layered crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric) composed by ontoref-core, plus ontoref-modes, ontoref-cli, ontoref-secrets (extracted pre-G1 from the daemon), and an internally rewritten ontoref-daemon that preserves auth and operator surface (ADR-021 carried verbatim). NCL/Nushell layers (.ontology/, adrs/*.ncl, reflection/) are removed at G12 after the kernel demo passes; their content is optionally migrated via tools/ncl-import. Two named tensions in .ontology/core.ncl are explicitly engaged by this decision (ondaod): ontology-vs-reflection and formalization-vs-adoption. The synthesis state and direction of motion for each are recorded in .coder/2026-05-25-implementation-final.plan.md §1.bis and MUST be read in conjunction with this ADR; collapsing either Spiral (e.g. by treating the substrate as 'reflection only' or by enforcing universal formalization without tier-1 fallback) constitutes a forbidden pattern. Activation tier for the prototype is Tier 2 (kernel + fabric); Wasm components, ZK proving, and Radicle p2p replication are deferred to post-G12 work and do not block this decision.
+
+Constraints
+
+Hard New kernel crates (ontoref-types, ontoref-blobs, ontoref-oplog, ontoref-triples, ontoref-commit, ontoref-query, ontoref-fabric, ontoref-core, ontoref-modes, ontoref-cli) MUST NOT depend on stratumiops crates (stratum-graph, stratum-state, stratum-db, platform-nats)Hard Every operation MUST round-trip through canonical CBOR encoding with byte-stable output: encode(op) == encode(op) for all op; OpId(op) == blake3(encode(op))Hard Substrate crates MUST NOT contain todo!(), unimplemented!(), or panic!() with not-implemented messagesHard Plan §1.bis MUST exist and name both engaged tensions (ontology-vs-reflection, formalization-vs-adoption) with direction-of-motion and synthesis state; this ADR cites itHard At G12 completion, crates/legacy/ MUST NOT exist; crates/ontoref-ontology/, crates/ontoref-reflection/ MUST NOT exist; reflection/, .ontology/, adrs/*.ncl, templates/ MUST NOT exist
+
+Alternatives considered
+
+Continue evolving on the NCL+filesystem+git substrate; incrementally add typed contracts where pain emerges — rejected: Each new typed surface (#[onto_mcp_tool] catalog, OCI vault, ADR-021 TokenIssuer) has been bolted onto a substrate that does not natively support typed signed operations. The result is increasing surface area implementing the same primitives in isolation. The decision is not 'should we add typed signed operations', it is 'should one substrate-level layer own them'. Incrementalism preserves the substrate but pays the integration cost N times.Adopt an off-the-shelf knowledge graph database (Datomic, XTDB, TerminusDB, Neo4j) — rejected: These systems target operational knowledge bases, not protocols that projects implement. Bitemporality (Datomic, XTDB) covers a subset of the substrate's properties but not multi-actor signed operations or external attestation via state-root commitments. Wrapping one of them would make ontoref a runtime dependency (violating Invariant #1: Protocol Not Runtime). The substrate is a protocol specification with reference implementation; an off-the-shelf KG DB is a runtime product.Implement only the op-log layer (L0–L2) and skip triples/commit/query/fabric — rejected: Without the commitment layer (L4), external verification is impossible. Without the fabric (Tier 2), multi-actor demonstrations cannot run. Without the query layer (L5), the substrate's discoverability is worse than the current describe.nu surface. The substrate's value is the combination; partial adoption defers the verification property to indefinite future work.Build the substrate as a new project (substrate-* crates), keep ontoref consuming it — rejected: This was the predecessor plan's framing (.coder/2026-05-24-verifiable-substrate-implementation.plan.md naming). D9 reverses it: ontoref-* prefix preserved because the substrate is ontoref realising its own axioms, not a new product line. A separate substrate-* project would create two protocols where one suffices, splitting the contributor community and forcing every consumer to track two projects.
+
+Related ADRs
+
+ADR-001 · ADR-002 · ADR-016 · ADR-018 · ADR-021 · ADR-022
diff --git a/site/site/content/adr/en/accepted/adr-023.ncl b/site/site/content/adr/en/accepted/adr-023.ncl
new file mode 100644
index 0000000..608b4ef
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-023.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-023",
+ title = "Adoption of the Verifiable Substrate: Op Log + Content-Addressed Blobs + Bitemporal Triples + Merkle Commitments + JetStream Fabric, Replacing the NCL/Filesystem/Git Substrate",
+ slug = "023",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/023",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-002", "adr-016", "adr-018", "adr-021", "adr-022"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-024.md b/site/site/content/adr/en/accepted/adr-024.md
new file mode 100644
index 0000000..3a6e461
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-024.md
@@ -0,0 +1,113 @@
+---
+id: "adr-024"
+title: "Operations Layer — Domain Operations as the Agent's Only Project-Touching Path"
+slug: "024"
+subtitle: "Accepted"
+excerpt: "After extensive interactive diagnosis across the session of 2026-05-25 (see"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-024", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+After extensive interactive diagnosis across the session of 2026-05-25 (see
+.coder/2026-05-25-substrate-utility-audit.info.md and the dialogue that produced
+it .coder/2026-05-25-implementation-final-stop-review_done.md),
+the recurring pain experienced across all 19 ontoref-onboarded projects
+(provisioning, mina, lian-build, forge-fleet, libre-{daoshi,wuji,forge},
+stratumiops, rustelo, vapora, kogral, secretumvault, typedialog, jpl-website,
+personal-ontoref, jpl-ontoref, cloudatasave, build-in-layers, mirador,
+DD7pasos, librosys, ontoref itself) is located at a layer that is neither the
+ontology layer (rich, declarative, mature) nor the verifiable substrate (G1-G10
+of the ontoref-kernel work is green and works).
+The pain lives at the acoplamiento entre acción y consumo — agents have generic
+primitives (Bash, Edit, Read, Write) plus optional ontology access (34 query-only
+MCP tools listed in crates/ontoref-daemon/src/mcp/mod.rs), and the ontology
+consumption is never required before action. Evidence from this repository:
+- MCP surface is 34 tools, all queries: list_projects, search, describe,
+ list_adrs, get_adr, get_node, validate, impact, status, guides, etc.
+ Mutations are limited to qa_add, action_add, bookmark_add, set_project,
+ config_update — META operations on ontoref itself, not DOMAIN operations
+ on the project being worked on.
+ - crates/ontoref-daemon/src/federation.rs (419 lines) implements cross-project
+ BFS impact graph with depth 5 and HTTP for push-only remotes. It is unused:
+ install/resources/remote-projects.ncl is the empty list. The infrastructure
+ exists; the use case for it has not been activated.
+ - ADR-012 explicitly bypasses the NCL canon (repo_kinds.txt as grep-readable
+ side channel) because nickel export was too slow for hot-path dispatch
+ (200-400ms per invocation). The NCL canon is aspirational; the operative
+ path goes around it whenever performance demands it.
+ - The three-layer model of ADR-020 declares an integration surface (Layer 2)
+ but its Layer-2 contract is type-shape (schemas, catalog data), not
+ operation-shape — consumers bind to data structures, not to verbs.
+The consequence: agents consume the ontology only when nudged; otherwise they
+operate through Bash/Edit on raw filesystem, producing outcomes that may or may
+not respect declared constraints. Validation runs post-mortem (pre-commit hooks,
+CI checks) rather than constitutively. Cross-project coordination requires the
+human to remember what lives where. The interactive session that produced this
+ADR consumed many hours of dialogue precisely because the human had to refill
+context the agent failed to fetch — and this dialogue cannot be relied on to
+persist across sessions or to survive context compaction.
+The verifiable substrate (ADR-023, Proposed) provides storage and witness
+mechanisms (canonical encoding, bitemporal triples, content-addressed blobs,
+state-root commitments) but does not on its own resolve why agents bypass the
+declared content. The substrate is necessary infrastructure for receipts and
+witnesses; it is not the mechanism that forces consumption.
+
+Decision
+
+Every ontoref-onboarded project carries — or progressively gains — a catalog of
+domain operations exposed exclusively to agents as their tool surface. The
+agent's configuration on a project DOES NOT include generic primitives (Bash,
+Edit, Read, Write) over project paths. The only path by which an agent may
+query, mutate, or affect the project is through operations in the catalog.
+Each domain operation declares:
+1. Required ontological precondition slice — which nodes, ADRs, state
+ dimensions, constraints, or external project entities must be loaded
+ and validated for this operation to proceed.
+ 2. Input contract — typed inputs, satisfied or rejected at invocation time.
+ 3. Effects — what the operation may change (file paths, state dimensions,
+ downstream entities, cross-project signals).
+ 4. Witness shape — what receipt is emitted upon successful completion.
+The operation runtime:
+1. Loads the declared precondition slice before invoking the operation body.
+ Loading is constitutive of the invocation, not a courtesy of the agent.
+ 2. Validates the precondition slice against the operation's declared
+ constraints. Failure produces a structured block message naming the
+ unmet precondition, not a generic error.
+ 3. Executes the operation body in a confined context that has access only
+ to the loaded slice and the declared effect scope.
+ 4. Emits a signed receipt: operation_id, actor, timestamp, state_root,
+ effect summary, downstream project signals if any.
+Cross-project operations are routed through the existing federation.rs
+infrastructure: an operation in project A that declares a dependency on or
+effect in project B resolves the B-side via the federated query mechanism.
+Operation receipts are first-class entities in the substrate (operations as
+entities, per the audit's H7 finding), queryable cross-project.
+Runtime placement is shared: the operations runtime is built once and lives in
+ontoref-daemon (or a new ontoref-ops crate, decided at implementation time).
+The operation catalog is per-project: each project owns its domain verbs under
+a new declared path (catalog/operations/), with each operation contract written
+in NCL using the ontoref-derive macros to surface to the MCP/API layer.
+Agent configuration discipline: agents launched into an ontoref-onboarded
+project receive an MCP server whose tool list is exactly the project's
+operations catalog (plus the existing META operations like describe). Raw
+filesystem tools over the project root are disabled at the agent-launch
+boundary. The launcher is responsible for enforcing this; ontoref provides the
+catalog and the runtime.
+
+Constraints
+
+Hard Agent-facing mutations on a project's authoritative state must go through catalogued operations (dispatch_op), not raw filesystem primitives (Bash/Edit). Direct file edits bypass typed preconditions and the witness-seam.Hard Every catalogued operation must declare at least one typed precondition. Ops with no preconditions provide no validation plane and defeat the purpose of the operations layer.
+
+Alternatives considered
+
+Keep generic agent tools, improve ontology documentation and the MCP query surface — rejected: Years of evidence across 19 projects demonstrate that better declarative content does not lead to consumption when consumption is optional. The optionality IS the failure mode; adding more documentation reinforces the same pattern at greater cost.Build the full G1-G12 substrate plan as written, defer operations layer indefinitely — rejected: The substrate without an operations layer is verifiable storage that no agent uses operationally. G1-G10 is already green; G11-G12 (multi-actor daemon, demo) would extend storage without addressing why agents bypass the storage. The recurring pain persists across all of G11-G12's work.Per-project ad-hoc solutions (custom CLI wrappers, project-specific guardrails, per-project skills) — rejected: Combinatorial cost across 19 projects. No shared infrastructure means each project re-derives the constrained-action pattern. Cross-project federation impossible by construction. This is the current state and the source of the recurring pain.Revert ontoref to pre-substrate state and restart the design — rejected: The substrate G1-G10 provides exactly the receipt/witness mechanism this ADR's operations layer requires. Reverting throws away material that becomes load-bearing under this decision. The substrate was not wrong; it was incomplete on its own. ADR-023 and ADR-024 together close the gap.Enforce ontology consumption via stricter pre-action hooks instead of constraining the tool surface — rejected: Hooks fire on commit, not on action. An agent can perform many actions before commit; by then the cost of bad action is already paid. Constraining the tool surface is the only mechanism that intervenes before the action exists.
+
+Related ADRs
+
+ADR-001 · ADR-005 · ADR-012 · ADR-018 · ADR-020 · ADR-022 · ADR-023
diff --git a/site/site/content/adr/en/accepted/adr-024.ncl b/site/site/content/adr/en/accepted/adr-024.ncl
new file mode 100644
index 0000000..073e2d7
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-024.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-024",
+ title = "Operations Layer — Domain Operations as the Agent's Only Project-Touching Path",
+ slug = "024",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/024",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-005", "adr-012", "adr-018", "adr-020", "adr-022", "adr-023"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-025.md b/site/site/content/adr/en/accepted/adr-025.md
new file mode 100644
index 0000000..3dcee67
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-025.md
@@ -0,0 +1,142 @@
+---
+id: "adr-025"
+title: "Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption"
+slug: "025"
+subtitle: "Accepted"
+excerpt: "ADR-024 establishes the operations layer as the agent's only project-touching"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-025", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-024 establishes the operations layer as the agent's only project-touching
+path. This ADR closes the remaining ambiguity ADR-024 left implicit: where does
+the authoritative project state actually live, and what is the role of the
+existing NCL filesystem (.ontology/, adrs/, reflection/).
+Two competing readings have coexisted through the project's evolution:
+Reading 1 (current de facto): the NCL filesystem IS the state. Rust code
+ (ontoref-ontology, ontoref-reflection crates) parses NCL into typed structs
+ for query. Mutations happen by editing files; git is the history.
+Reading 2 (substrate plan original direction): NCL is intended to disappear
+ (ADR-023 § decision, constraint legacy-crates-removed-at-g12). State lives in
+ triples (G4) + commitments (G5); operations are recorded in oplog (G3). NCL
+ was destined to become migration tool input only.
+Neither reading is currently coherent with the project's lived axioms and the
+direction set by ADR-024. Reading 1 contradicts ADR-024 (an agent who edits
+.ontology/*.ncl with Edit bypasses operations entirely). Reading 2 dissolves
+.ontology/ which collides with the Self-Describing axiom and removes the
+human-legible substrate the project has invested years in.
+The session of 2026-05-25 (see .coder/2026-05-25-implementation-final-stop-review_done.md)
+closed Reading 2 by adopting ADR-024: operations layer becomes the fin, substrate
+becomes the medio. This ADR closes the matching question on state location: the
+authoritative state lives in Rust (loaded into typed structs, committed via
+the substrate), and NCL is one of several manifestations rendered from that
+state. The plain-text legibility of .ontology/ is preserved; its semantic
+authority is moved to the Rust-side state.
+The transition has a load-bearing constraint: it must be progressive. A
+big-bang switch to "NCL is read-only output" would block all legitimate human
+edits during the period when the operations catalog is incomplete (which is
+inherent to first adoption — the catalog grows dialectically with use). The
+decision adopts a two-phase model: Phase 1 NCL editable with parse-on-save
+reconciliation; Phase 2 NCL render-only when the catalog is mature.
+The decision also collides with the existing axiom no-enforcement
+(.ontology/core.ncl, invariant=true): "There is no enforcement mechanism.
+Coherence is voluntary." Once operations are the only mutation path and
+parse-on-save can reject inconsistent edits, there IS enforcement — internal,
+structural, post-adoption. The axiom must evolve to reflect this distinction
+between voluntary adoption (preserved) and enforced internal coherence (new).
+
+Decision
+
+The authoritative project state lives in Rust, materialized over the
+verifiable substrate (ontoref-types + ontoref-triples + ontoref-oplog +
+ontoref-commit, G1-G10 verde). NCL files in .ontology/, adrs/, and reflection/
+become one manifestation of that state — human-legible, git-versioned, but no
+longer the seat of semantic authority.
+The transition is two-phase:
+Phase 1 — C-progressive (during catalog construction)
+NCL files remain manually editable. Each save to .ontology/*.ncl,
+ adrs/*.ncl, or reflection/*.ncl triggers a reconciliation:
+parse(file) → diff vs current state → emit ops to align → apply
+If parse fails or diff requires an op not in the catalog, the save fails
+ with a structured message naming the missing operation. Each failure
+ teaches the dialectical step needed to extend the catalog.
+Render from state to NCL happens after any state mutation (whether
+ triggered by a save or by a direct ops invocation), so the NCL files
+ always reflect the post-mutation state.
+Phase 1 is the operating mode during the build-out of ADR-024's
+ operations catalog. It coexists with the existing CLI surface; ops
+ invocations via MCP, ops invocations via save-triggered reconciliation,
+ and (during this phase) legacy CLI mutations via the existing ontoref CLI
+ commands all converge on the same state.
+Phase 2 — C-pure (post-catalog-maturity)
+NCL files become render-only. Direct edits are silently overwritten by the
+ next render. All mutations flow through declared operations.
+Transition from Phase 1 to Phase 2 is a deliberate decision recorded as a
+ configuration flag (.ontoref/config.ncl::ops.phase = 'Pure) plus a new
+ ADR (likely ADR-026) documenting the maturity assessment.
+Reconciliation between this decision and the existing no-enforcement axiom
+requires the axiom to evolve. no-enforcement is split into two axioms that
+preserve voluntary adoption while making explicit the enforcement that lives
+inside the post-adoption operation runtime:
+voluntary-adoption (invariant=true) — Projects adopt ontoref by choice; the
+ protocol never imposes itself. Adoption is opt-in per tier (tier-0
+ minimal NCL, tier-1 substrate, tier-2 operations).
+internal-coherence-enforced (invariant=true) — Once a project adopts the
+ operations tier, mutation of authoritative state happens exclusively
+ through declared domain operations. The agent (human or AI) cannot
+ bypass.
+Both new axioms are added to .ontology/core.ncl as part of the pre-flight to
+the operations-layer implementation plan; the existing no-enforcement node is
+removed. This is enforced as a Hard constraint of this ADR.
+Invocation surfaces (the API answer):
+Operations are invoked exclusively through typed API surfaces. NCL files are
+ NEVER the invocation channel; they are the catalog (declaration of what
+ operations exist) and the manifestation (render of resulting state). Three
+ API surfaces, all built on the existing ontoref-daemon authentication
+ (ADR-005):
+1. MCP (Model Context Protocol) — agents call ops via tools/call <op_id>;
+ the tool list is dynamically populated from ontoref-ops::registry
+ (inventory::collect of OperationEntry). Auth via Bearer session token.
+ Existing 34 query tools coexist.
+2. HTTP/REST — POST /ops/{id} with JSON body matching the op's declared
+ inputs; response includes the witness. Auth via Bearer. Suitable for
+ scripts, CI/CD, GraphQL bridge, and remote clients.
+3. CLI — ./ontoref ops <id> --json '{...}' (alternatively positional args
+ resolved by the op's input schema). Same dispatch path as MCP and
+ HTTP under the hood.
+All three surfaces converge on ontoref-ops::dispatch(op_id, inputs) which
+ loads the precondition slice, validates, executes, and emits the witness.
+ In Phase 1, save-to-NCL is a fourth implicit invocation channel that goes
+ through parse → diff → emit_ops → dispatch. In Phase 2, save-to-NCL has no
+ effect on state.
+Crate consequences:
+- ontoref-ontology stays — its role shifts from "NCL parser" to "NCL ↔ state
+ bridge" (parse-to-ops in Phase 1; render-from-state in both phases).
+ - ontoref-reflection stays — modes execute against state queries (via
+ ontoref-query L5) rather than parsing reflection/modes/*.ncl on each call.
+ - ontoref-ops (new crate, plan's D2) is the operation runtime; it composes
+ ontoref-ontology (for render/parse), ontoref-triples (for state), and
+ ontoref-commit (for witnesses).
+ - ontoref-daemon adapts to expose POST /ops/{id} routes and integrates
+ ops into the MCP tool list; no rewrite required.
+ - The legacy-crates-removed-at-g12 constraint in ADR-023 is invalidated by
+ this ADR and is to be removed when ADR-023 transitions to Accepted (post
+ O3 of the operations-layer plan).
+
+Constraints
+
+Hard .ontology/core.ncl MUST replace the no-enforcement node by two new axiom nodes: voluntary-adoption and internal-coherence-enforced (both invariant=true). The no-enforcement id MUST NOT remain in the nodes array.Hard Rendering state → NCL MUST be byte-stable: render(state, file_path) produces identical bytes on repeated invocations for the same state, and parsing the rendered file MUST yield a state equal to the source state.Hard .ontoref/config.ncl MUST declare an ops.phase field with value 'Progressive | 'Pure once ADR-025 is Accepted. Default value 'Progressive applies until a follow-up ADR records the Phase 2 transition.Hard This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the to-be-split no-enforcement axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.
+
+Alternatives considered
+
+Reading 1: keep NCL as authoritative state, ADR-024 enforcement applied only when operations exist — rejected: This is the current state. The H7 diagnosis demonstrates it does not work: agents bypass NCL because direct file edit is structurally cheaper than consulting via MCP. The ADR-024 commitment to operations-as-only-path is incoherent if the underlying state is still file-edit-accessible. The reading collapses ADR-024 into a recommendation rather than a constitutive constraint.Reading 2: dissolve NCL completely per the original substrate plan — rejected: Closing the human-legible substrate breaks Self-Describing's lived form (the directories ARE the description, not an optional rendering). It also abandons years of investment in NCL schemas, contracts, and onboarding tooling. The H7 dialectic showed the problem was not NCL-the-substrate but the missing acting-binding to NCL — the substrate is fine; what was missing was the mutation discipline. Dissolving NCL is over-reach.C-pure from day one (state in Rust, NCL render-only immediately) — rejected: Big-bang transition during catalog incompleteness blocks legitimate work. Every mutation that has not yet been catalog-modeled is a hard block on the editor. Reasonable extensions (a new tension name in a new ontology node, a new ADR with a yet-unmodeled constraint type) become catalog-extension dialectics before they can be expressed. The friction kills adoption velocity precisely when adoption velocity is needed to mature the catalog.C-progressive permanent (parse-on-save without ever reaching pure render-only) — rejected: Stops at the half-way point. The internal-coherence-enforced axiom is only partially satisfied — humans can still mutate via save, ops are not the unique path. The cryptographic verifiability that ADR-024 promised (witness-of-mutation cannot be fabricated) is undermined by the existence of an alternative mutation channel. The model is functional but incomplete; Phase 2 must be the eventual target even if its trigger is far in the future.Keep no-enforcement axiom unchanged; treat ADR-024 + ADR-025 as conscious axiom violations documented per case — rejected: Encourages the protocol to drift from its declared axioms while the axioms remain frozen as aspiration. Future readers of core.ncl would see no-enforcement as truth while the runtime structurally enforces. The protocol's self-description must remain accurate; an axiom that no longer describes the runtime is not an axiom, it is a wish. The split makes the lived position legible.
+
+Related ADRs
+
+ADR-001 · ADR-007 · ADR-009 · ADR-018 · ADR-020 · ADR-023 · ADR-024
diff --git a/site/site/content/adr/en/accepted/adr-025.ncl b/site/site/content/adr/en/accepted/adr-025.ncl
new file mode 100644
index 0000000..ef00e9a
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-025.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-025",
+ title = "Ontology Core as Authoritative State, NCL as Manifestation — Two-Phase Adoption",
+ slug = "025",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/025",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-007", "adr-009", "adr-018", "adr-020", "adr-023", "adr-024"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-026.md b/site/site/content/adr/en/accepted/adr-026.md
new file mode 100644
index 0000000..50b571c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-026.md
@@ -0,0 +1,151 @@
+---
+id: "adr-026"
+title: "Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend"
+slug: "026"
+subtitle: "Accepted"
+excerpt: "ADR-024 establishes the operations layer as the agent's only project-touching"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-026", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-024 establishes the operations layer as the agent's only project-touching
+path. ADR-025 establishes that the authoritative state lives in the
+verifiable substrate and NCL is its manifestation. Both decisions leave one
+question deliberately open: how is the coherence of a mutation actually
+verified? The plan that implements ADR-024 + ADR-025 mentions "validate" as a
+step inside the operation runtime but does not articulate where validators
+live, what kinds of validation apply, or what verifiability guarantees are
+emitted alongside the mutation.
+Without this articulation, the operations layer collapses to a thin wrapper:
+the runtime loads a precondition slice, runs an opaque validate() body
+inside the operation, and emits a witness that no external party can
+meaningfully check. This is the failure pattern of the current pre-commit
+hooks at scale — validators that run inside the producer's process,
+producing pass/fail signals that consumer projects must trust on faith. The
+H7 diagnosis is repeated at the validator layer: validators that consume the
+full state saturate, drift, and lose locality.
+Three structural deficits the operations layer alone does not resolve:
+1. Validators that require complete knowledge of project state cannot run
+ locally without paying the saturation cost. The substrate's witness
+ mechanism (G5) provides local verifiability for cells, but the
+ operations layer does not exploit it — validators read the live state
+ instead of reading a witnessed slice.
+2. The same validator predicate runs on every invocation regardless of
+ contextual relevance. An IPv4-shape validator runs identically for
+ every IP; a "this IP is in this pool given the FSM state" validator
+ needs slice + context. The current pre-commit pattern conflates the
+ two — both run as NuCmd processes loading whatever they want.
+3. Validation latency and concurrency requirements are not expressible per
+ operation. A move_fsm_state must validate synchronously (the state
+ transition is the contract); a cross-project impact assertion may
+ legitimately validate eventually (consumer in B asserts the
+ consequence of A's op asynchronously). The current model imposes one
+ mode (sync, pre-commit) on all validations.
+The substrate G1-G10 was designed precisely to support locally-verifiable
+witnesses — bitemporal triples (G4), state root commitments (G5), proof
+paths for cells (witness API). These primitives have been built and tested
+but not consumed by an articulated validation layer. This ADR closes that
+gap.
+
+Decision
+
+Validation is articulated as a first-class architecture, parallel to the
+operation catalog. The architecture has three load-bearing properties:
+Three planes of validation:
+Pre-validation — runs after precondition slice load and before the
+ operation body executes. If any pre-validator returns Verdict::Reject,
+ the operation does not execute; the runtime returns a structured block
+ message naming the validator and the unmet predicate.
+Inline validation — runs during the operation body. The body may invoke
+ additional validators that depend on values computed mid-execution
+ (e.g. a cross-project query whose result is itself validated). Failures
+ abort the body with rollback at the substrate level (the op is not
+ appended to the oplog).
+Post-validation — runs after the operation body has applied to the state.
+ Two modes:
+ - Synchronous: the runtime invokes registered validators inline; the
+ op is committed only if all return Accept.
+ - Asynchronous: validators are external processes consuming the oplog
+ via the existing subscription channel (ontoref-query G6
+ subscribe()); they emit AttestationOp entries materialized in the
+ same substrate. The state can carry not-yet-attested ops; consumer
+ queries can filter by attestation status.
+Two categories of validators:
+Structural validators — predicates over input or local data with no
+ dependency on project state or context. Examples: IPv4 shape, CBOR
+ canonical, Ed25519 signature valid, NCL schema contract held.
+ Universally applicable, cheap, deterministic.
+Contextual validators — predicates over a slice of project state, possibly
+ parameterized by actor, dimension, or domain. Examples: "this entity is
+ in this pool given the current FSM state", "this actor may invoke this
+ op given their role", "cross-project op respects constraint declared in
+ project B". The slice query is part of the validator declaration; the
+ runtime loads exactly that slice — no more — before running the
+ predicate.
+Validators are first-class entities, declared in two combinable forms:
+catalog/validators/*.ncl — NCL declaration: id, description, category
+ (Structural | Contextual), slice_query (for contextual), predicate_ref,
+ witness_shape, actor_scope. Reusable across operations.
+#[onto_validator(id, kind, slice, ...)] — Rust macro on the validator
+ implementation function. Emits inventory::submit!(ValidatorEntry { ... })
+ at link time. The validator function signature is
+ fn validate(slice: &Slice, ctx: &ValidationCtx) -> Verdict.
+An operation references validators by id in its constraints field;
+ alternatively, an operation may declare inline constraints when the
+ validator is single-use and reuse is not anticipated. Both forms coexist.
+SLA per operation:
+Each operation declares a validation_sla field:
+ 'Synchronous — all validators (pre, inline, post) run sync;
+ op only commits on full Accept
+ 'EventualWithin(Duration) — pre/inline sync; post validators run async
+ with deadline; consumers query attestation
+ status with as_of clauses
+ 'Background — pre/inline sync; post validators run background;
+ no deadline; ops are eventually-attested
+The runtime applies the declared mode; mixing modes within a single op is
+ not permitted (to keep dispatch unambiguous).
+Pluggable commitment backend:
+The substrate's commitment layer (G5, currently hand-rolled binary Merkle)
+ is abstracted behind a CommitmentBackend trait with methods apply, root,
+ witness, verify_witness. The G5 hand-rolled implementation becomes the
+ BinaryMerkle impl. The trait permits alternative impls (NOMT, jmt,
+ sparse-merkle-tree) without modifying call sites.
+The choice of backend is deferred: the BinaryMerkle impl is the default
+ and proven (10 tests, clippy clean). A trigger-based spike runs only when
+ empirical signals from the pilot indicate a backend with different
+ trade-offs may be beneficial:
+- validate_latency_p99 > 500ms (sustained)
+ - state_cells_count > 100k
+ - commit_root_time_p99 > 100ms (sustained)
+ - daemon memory pressure > 1GB on substrate alone
+A one-off type-fit assessment (1-2 hours, no benchmark) checks whether
+ candidate backends (NOMT 1.0.4, jmt latest, sparse-merkle-tree latest)
+ adapt to the CommitmentBackend trait without invasive wrappers. Backends
+ that do not fit are eliminated without benchmark cost. The assessment is
+ optional and may be deferred indefinitely; absence of assessment leaves
+ BinaryMerkle as the conscious choice.
+Local verifiability:
+Every operation emits a witness containing op_id, post-apply state_root,
+ signed by the actor (ADR-005 keypair model), plus Merkle proof paths for
+ cells the operation's effects touched. An external verifier holding only
+ the state_root + witness can check predicate(slice, witness, state_root)
+ for any contextual validator without needing access to the full state.
+ This is the Sigstore-like property the substrate was built to enable.
+
+Constraints
+
+Hard crates/ontoref-ops MUST declare the trait Validator with method fn validate(&self, slice: &Slice, ctx: &ValidationCtx) -> Verdict; crates/ontoref-commit MUST refactor the existing commit module to extract trait CommitmentBackend with methods apply, root, witness, verify_witness. BinaryMerkle (the existing hand-rolled impl) MUST be the default impl.Hard Every operation in catalog/operations/*.ncl MUST declare a validation_sla field with one of the values 'Synchronous, 'EventualWithin(duration), or 'Background. Operations that lack the field are rejected by the catalog schema at parse time.Hard .ontoref/config.ncl MUST declare an ops.commitment_backend field with enum value 'BinaryMerkle | 'Nomt | 'Jmt | 'SparseMerkleTree. Default value is 'BinaryMerkle. The runtime selects the impl based on this field.Soft docs/validation/spike-triggers.md MUST exist documenting the empirical thresholds that activate the commitment-backend spike (validate_latency_p99, state_cells_count, commit_root_time_p99, daemon memory pressure). The thresholds MUST be revisable via PR; the document records the current values plus rationale.Hard This ADR engages and synthesizes the named tensions ontology-vs-reflection, formalization-vs-adoption, and the internal-coherence-enforced axiom; the synthesis state and direction of motion MUST be discoverable in the decision and rationale.
+
+Alternatives considered
+
+Single-plane validation: pre-only (as in current pre-commit hooks) — rejected: Pre-only cannot handle validators that depend on values computed during op execution (inline) and cannot handle expensive validators that would block throughput unacceptably (async post). The current model fails precisely because it has only this one plane — the H7 diagnosis traces some of its dysfunction to validators that try to do everything pre.Validators as Rust traits only, no NCL declaration — rejected: Loses the Self-Describing property: validators become invisible to non-Rust consumers (NCL readers, GraphQL clients, MCP agents querying catalog). Also loses the ability for validators to be cross-project queryable — a project that wants to know what validators apply to its mutations would need to read source code rather than declarative artifacts.Validators as NCL only, no Rust impl bridge — rejected: NCL is declarative; predicate evaluation logic must run somewhere. Forcing all predicates into Nickel contract syntax limits expressiveness severely (no graph traversal, no FFI, no I/O). The dual-write (NCL declares, Rust implements) is the only way to satisfy both Self-Describing (NCL is the catalog) and effective implementation (Rust runs the code).Force synchronous validation for all ops (no SLA per op) — rejected: Equates correctness with synchronous correctness. Many legitimate validators (cross-project, statistical, graph-traversal) cannot reasonably run synchronously without throttling the runtime. The sync-only model would either reject these validators (impoverishing the architecture) or require their bodies to be approximations of the real predicate (sacrificing correctness for latency).Commit immediately to NOMT or jmt without spike or trait abstraction — rejected: No prior implementation experience exists. Either choice without data is gambling. The trait abstraction costs minimal effort (the G5 implementation needs trait extraction; the other backends, if ever evaluated, just implement the trait). Deferring the decision preserves optionality at near-zero cost; committing now sacrifices optionality for no benefit.Defer all of validation architecture to a later ADR, ship operations layer with thin validate() — rejected: The operations layer without articulated validators is the failure pattern this entire trajectory is trying to escape. Shipping ADR-024 + ADR-025 + the operations-layer plan without ADR-026 would produce a runtime that has the surface of validation (a validate step in the dispatch pipeline) but the substance of the current pre-commit model (opaque body running ad-hoc checks). Validation architecture is co-constitutive with operations architecture; they share the load-bearing.
+
+Related ADRs
+
+ADR-001 · ADR-002 · ADR-005 · ADR-007 · ADR-014 · ADR-023 · ADR-024 · ADR-025
diff --git a/site/site/content/adr/en/accepted/adr-026.ncl b/site/site/content/adr/en/accepted/adr-026.ncl
new file mode 100644
index 0000000..fb23eaf
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-026.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-026",
+ title = "Validation Architecture — Three Planes, First-Class Validators, SLA per Operation, Pluggable Commitment Backend",
+ slug = "026",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/026",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-002", "adr-005", "adr-007", "adr-014", "adr-023", "adr-024", "adr-025"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-027.md b/site/site/content/adr/en/accepted/adr-027.md
new file mode 100644
index 0000000..cf7144a
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-027.md
@@ -0,0 +1,145 @@
+---
+id: "adr-027"
+title: "Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain"
+slug: "027"
+subtitle: "Accepted"
+excerpt: "ADR-024 establishes operations as the agent's only project-touching path."
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-027", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-024 establishes operations as the agent's only project-touching path.
+ADR-025 establishes that authoritative state lives in Rust on the substrate
+(G1-G10) with NCL as manifestation. ADR-026 establishes the validation
+architecture with witness-based local verification. All three were written
+assuming a single-actor, single-node operational model — the substrate runs
+locally, the operation dispatches locally, the validators verify locally.
+The current production reality contradicts this assumption: ontoref-daemon
+ships with stratum-db (feature-gated) that connects to a remote SurrealDB
+server for the query cache. The 19 ontoref-onboarded projects share tables
+in this remote DB via {slug}--{id} prefixes. This is a centralized model: a
+remote authority holds the materialized state; outage of the DB outage
+breaks discovery and cross-project query for every consumer.
+A deeper structural deficit was identified during the session of 2026-05-26
+(this ADR's session): when the ontology lives inside the project's filesystem,
+cross-project operations or verifications require dragging the entire other
+project into local scope. This repeats the H7 saturation pattern at a
+different layer — instead of agent-saturated-with-context, we have
+verifier-saturated-with-other-project. The substrate's witness mechanism
+(G5 Merkle proofs) was designed precisely to escape this saturation — a
+verifier holding only state_root and witness can validate a claim without
+holding the source. But the witness mechanism is useless if there is no
+decentralized exchange channel and no separation of ontology from project.
+This ADR closes the decentralization question. ADR-028 closes the matching
+ontology-from-project separation question. Together they reorganize the
+operational model from "centralized substrate + remote DB" to "P2P pure +
+per-actor substrate + content-addressed ontology layer + witness-based
+cross-actor verification".
+The decision is irreversible-but-incremental: P2P pure is the architectural
+commitment; the sync protocol implementation is deferred via a trait
+abstraction that lets candidates (Iroh, Hypercore, Radicle, NATS) be
+evaluated when empirical signals demand. The reversibility lives in the
+backend choice, not in the model.
+
+Decision
+
+Adopt P2P-pure decentralization as the operational model: no central
+authority node; each actor maintains its own substrate locally; sync is
+peer-to-peer between actors who explicitly follow each other.
+Four load-bearing capabilities that the architecture must support:
+1. Multi-actor concurrent with sovereignty per actor — multiple actors
+ (humans + AI agents) operate simultaneously on the same logical
+ project; each holds its own copy; decides whom to follow; no
+ coordination authority.
+2. Verifiability without complete state access — any actor can verify a
+ claim by another with only state_root + witness; the substrate's G5
+ Merkle proofs are the mechanism; ADR-026's validators rely on this.
+3. Decentralized discovery — actors find each other via DHT, gossip, or
+ social protocol (Radicle); no central registry. The current
+ ~/.config/ontoref/projects.ncl becomes "peers I follow", not
+ "registry I depend on".
+4. Resilience to partition / offline-first — an actor offline retains
+ full local operation; reconnection syncs lazily; no synchronous
+ coordination required for correctness.
+SyncBackend trait (pluggable backend, decision deferred):
+trait SyncBackend {
+ async fn announce(&self, oplog_head: OpId, public_key: PublicKey);
+ async fn discover_peers(&self) -> Vec<Peer>;
+ async fn fetch_op(&self, peer: &Peer, op_id: OpId) -> Result<Op>;
+ async fn subscribe_peer(&self, peer: &Peer) -> Stream<Op>;
+ }
+Candidate impls behind trigger-based assessment (same pattern as ADR-026
+ CommitmentBackend):
+ - FilesystemSync — default, trivial impl (peers share a filesystem
+ directory; useful for development and testing; not really
+ decentralized but zero-dependency)
+ - IrohSync — n0-iroh; Rust-native; gossip + content-addressed blobs +
+ DHT; aligned with Ed25519 identity (ADR-005)
+ - HypercoreSync — Hypercore via Rust bindings if mature; append-only
+ log primitive aligned with oplog
+ - RadicleSync — git-protocol-over-Radicle for repos; potentially
+ different layer from oplog sync
+ - NatsSync — NATS JetStream as fabric (G7 of the substrate plan,
+ previously skipped)
+The decision among these is deferred. The trigger conditions:
+ - cross_actor_sync_required > 0 (i.e., the pilot has reached the point
+ where actual P2P sync is needed; until then, FilesystemSync suffices)
+ - actor_count > 1 (a real second actor exists, not a self-loop)
+ - peer_discovery_latency or sync_throughput become measurable concerns
+The fit-assessment (type-fit, not benchmark) for each candidate runs as
+ ADR-026 spike: does the candidate adapt to SyncBackend trait without
+ invasive wrappers. Candidates that do not fit are eliminated.
+CRDT per-domain (selective CRDT, not global):
+D6 of the old plan ("HLC without CRDT") was a sound default for the
+ substrate's structural correctness but is insufficient when multi-actor
+ concurrent edits target the same cell. This ADR modifies D6 selectively:
+ domains where concurrent edits over the same cell are plausible declare a
+ CRDT merge strategy; other domains retain HLC last-writer-wins.
+Identified domains needing CRDT (this session's identification):
+ - Backlog (BacklogItem): multiple actors add/status/done concurrently;
+ MergeStrategy::OrSet for items; MergeStrategy::Lww per status with
+ HLC tiebreak
+ - Ontology nodes (description, edges): collaborative description
+ enrichment + edge addition by multiple contributors;
+ MergeStrategy::TextMerge for description (LSEQ or similar);
+ MergeStrategy::OrSet for edges
+ - QA entries + search bookmarks: append-only accumulators;
+ MergeStrategy::GSet trivially
+CrdtMergeStrategy trait (pluggable, similar to SyncBackend and
+ CommitmentBackend):
+trait CrdtMergeStrategy {
+ type Item;
+ fn merge(&self, a: Self::Item, b: Self::Item) -> Self::Item;
+ fn merge_witness(&self, ...) -> MergeWitness;
+ }
+Default impl: HlcLastWriterWins (preserves current D6 behavior).
+ Per-domain impls declared in the domain's NCL schema.
+SurrealDB position under P2P pure:
+SurrealDB becomes opt-in via the existing feature flag (cargo build
+ -p ontoref-daemon --features db). The runtime does not depend on
+ SurrealDB; consumers that want SurrealDB's query expressiveness configure
+ it locally. The stratum-db dependency moves from "feature-gated default"
+ to "feature-gated optional with explicit choice". Existing 19 onboarded
+ projects continue to work unchanged; the migration to non-SurrealDB
+ query layer (datalog G6 + ontoref-query) is opt-in per project.
+Crucially: SurrealDB is local-only under P2P pure. The connect_remote
+ path becomes connect_local (embedded mode) or is deprecated when the
+ project chooses opt-out.
+
+Constraints
+
+Hard crates/ontoref-ops or new crate ontoref-sync MUST declare trait SyncBackend with methods announce, discover_peers, fetch_op, subscribe_peer. A default impl FilesystemSync MUST exist that satisfies the trait without external dependencies (suitable for development and single-actor pilot phase).Hard crates/ontoref-ops MUST declare trait CrdtMergeStrategy with method merge(a, b) -> Item + merge_witness(...). A default impl HlcLastWriterWins MUST exist. Domain-specific impls (OrSet for backlog, TextMerge for ontology descriptions, GSet for QA accumulators) MUST be declared when the corresponding domain catalog operations are implemented.Hard ontoref-daemon MUST build successfully with --no-default-features --features mcp (or equivalent) — i.e. without the db feature that pulls stratum-db. The runtime MUST function for query, mutation, validation, and witness emission without SurrealDB present.Hard This ADR engages and synthesizes the voluntary-adoption + internal-coherence-enforced axioms (ADR-025) and the formalization-vs-adoption tension. The synthesis is the per-actor sovereignty model: adoption of P2P is voluntary per project (opt-in tier), but a project that adopts P2P operates within enforced internal coherence (witness-verified ops, signed identity, CRDT per declared domain).
+
+Alternatives considered
+
+Keep centralized SurrealDB as authoritative query layer; add P2P as optional layer above — rejected: Reproduces the H7 saturation pattern at the storage layer: actors depend on a central authority for queries, defeating the substrate's local-verifiability purpose. The 'optional P2P' layer becomes a courtesy feature rather than the operational backbone; usage in practice falls back to the centralized path. The decision to decentralize must be in the model, not an additional layer.Federation model (ActivityPub / Matrix / NATS cluster) — nodes federate but each is a hub — rejected: Federation introduces 'nodes' as a category distinct from 'actors', creating a power asymmetry: node operators control sync topology. This contradicts the per-actor sovereignty capacity. Federation is the right model for some systems (Mastodon, Matrix), but not for ontoref's protocol-of-self-knowledge use case where every actor is logically equivalent.CRDT globally for all domains — rejected: Forces CRDT complexity on domains that do not need it. ADR transitions are sequential by their definition (Proposed → Accepted is causally ordered; there is no semantic merge of 'two simultaneous Accepts'). Forcing CRDT here creates a richer data model without operational benefit. The per-domain selective model is the right grain.Pick a sync protocol now (e.g. Iroh) and commit before having multi-actor scenarios — rejected: Same mistake as ADR-026 commitment-backend would be without trigger-based deferral. Sync protocol choice without operational scenarios is a guess; the cost of remaking the choice later (after seeing actual sync patterns) outweighs the benefit of committing now. The trait abstraction is cheap; the deferral is honest.Remove SurrealDB unilaterally; force all consumers to datalog G6 — rejected: Breaks 19 onboarded projects without consultation. The capacity to use SurrealDB for SurrealQL expressiveness is legitimate for projects that have invested in it. The opt-in demotion lets each project decide; coexistence is sustainable under P2P pure because SurrealDB is local-only.
+
+Related ADRs
+
+ADR-001 · ADR-005 · ADR-013 · ADR-017 · ADR-023 · ADR-024 · ADR-025 · ADR-026
diff --git a/site/site/content/adr/en/accepted/adr-027.ncl b/site/site/content/adr/en/accepted/adr-027.ncl
new file mode 100644
index 0000000..7aeefd9
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-027.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-027",
+ title = "Decentralization Architecture — P2P Pure, Pluggable SyncBackend, CRDT per Domain",
+ slug = "027",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/027",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-005", "adr-013", "adr-017", "adr-023", "adr-024", "adr-025", "adr-026"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-028.md b/site/site/content/adr/en/accepted/adr-028.md
new file mode 100644
index 0000000..5722df6
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-028.md
@@ -0,0 +1,181 @@
+---
+id: "adr-028"
+title: "Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification"
+slug: "028"
+subtitle: "Accepted"
+excerpt: "Through this session's evolution, the project's ontology lived inside the"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-028", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Through this session's evolution, the project's ontology lived inside the
+project's filesystem (.ontology/, adrs/, reflection/), git-versioned
+alongside the project's code. This pattern made sense when the ontology was
+specific to a single project; it became structurally problematic as the
+ecosystem grew to 19 onboarded projects sharing the same ontoref protocol,
+the same axioms, the same Practice nodes for common patterns, and
+cross-project federation became a recurring need.
+The session's load-bearing insight (your formulation): when the ontology is
+coupled to the project's filesystem, cross-project operations or
+verifications require dragging the entire other project into local scope.
+A project A trying to validate against a constraint declared in project B
+has no choice but to clone B, parse B's NCL, materialize B's state — and
+then perform a single check that touches one node of B. This is the H7
+saturation pattern at the storage layer: as agents saturate when loaded
+with full project context, verifiers saturate when loaded with full
+foreign-project context.
+The substrate G1-G10 (G5 in particular: Merkle commitments + witness API)
+was designed to escape this — a verifier holding only state_root + witness
+can validate a claim without holding the source. But the witness mechanism
+is useless when the verifier still must clone the source to know what
+state_root applies, what schema is in force, what validators bind to which
+ops. The asymmetry between (small witness) and (huge cloned context) breaks
+the local-verifiability promise.
+This ADR closes the asymmetry by separating two layers that have been
+conflated:
+Project layer — the consumer codebase. Rust crates, Nushell scripts,
+ config files, project-specific NCL extensions. Lives
+ in Radicle / jj repositories. Managed by the existing
+ VCS abstraction (ADR-013).
+Ontology layer — protocol-level and shared-domain ontologies. Lives
+ as content-addressed decentralized streams: each
+ ontology is an append-only oplog (G3 primitive), with
+ Merkle commitments (G5) over its current state.
+ Reachable by content-address, not by filesystem
+ location. Multiple projects reference the same
+ ontology by its content-address; none of them owns it.
+Cross-project verification under this separation: project A's operation
+that requires validating against an ontological entity declared in shared
+ontology X resolves the reference by content-address, fetches only the
+witness for the specific cell, verifies against the ontology's state_root.
+No project is dragged; only the witness travels.
+
+Decision
+
+Separate the operational world into two layers with distinct sovereignty,
+sync mechanisms, and discovery patterns:
+Ontology layer (decentralized, content-addressed):
+An ontology is an append-only oplog (G3 primitive from substrate G1-G10)
+ containing operations that build the ontological knowledge: axioms,
+ tensions, practices, ADR entries, validators, schemas. Each ontology
+ has:
+ - An identity (Ed25519 keypair for its maintainers; multi-maintainer
+ via CRDT per-domain from ADR-027)
+ - A content-address: blake3 hash of the head OpId chain root
+ - A current state_root (G5 Merkle commitment)
+ - A discovery surface: pubkey + recent head OpId, broadcast via
+ SyncBackend (ADR-027) — peer projects subscribe to follow updates
+Ontologies are independent of any project. They exist as their own data
+ structures with their own sync, their own maintainers, their own
+ evolution. A project consumes ontologies by reference; cannot mutate
+ them locally; cannot fork them without explicit forking semantics
+ (which is itself an ontology operation — fork is a recorded act).
+Project layer (Radicle + jj, existing infrastructure):
+Project codebases continue to live in Radicle repos managed via jj
+ (ADR-013 VCS abstraction + the existing agent-workspace-orchestration
+ pattern with jjw). What changes is the content:
+ - Source code (Rust, Nushell, etc.) — unchanged
+ - Project-specific NCL extensions — minimized; most ontology content
+ moves to the ontology layer
+ - Local oplog of the project — records ops the project emits
+ (proposed ADRs, FSM state moves, backlog ops); references the
+ ontology layer for the schema and validators that apply
+ - Witnesses + state_root snapshots — for cross-project verification
+Ontology classification with granularity:
+Type 1 — Protocol ontology (mandatorily decentralized):
+ The ontoref-core itself — axioms (voluntary-adoption,
+ internal-coherence-enforced, protocol-not-runtime, self-describing,
+ dag-formalized), named tensions, base practices, base validators.
+ Every adopting project references this by content-address; no project
+ embeds it. The protocol ontology's maintainers are the ontoref
+ project's contributors (this repo's current authors); its
+ content-address is the protocol's published identity.
+Type 2 — Shared domain ontology (granular: federable to mandatorily
+ decentralized):
+ Domain ontologies used by multiple projects: provisioning (cluster
+ ops, component catalog), persona (career, opportunities), lian-build
+ (image layers), forge-fleet (fleet management). The granularity:
+ - Type 2a — Federable: shared between this author's projects;
+ decentralization recommended but not mandatory; can live as a
+ single oplog accessible to a known peer set
+ - Type 2b — Mandatorily decentralized: shared with external
+ consumers (any third party may consume); MUST be content-
+ addressed and discoverable via SyncBackend; maintainers may be
+ broader than the originating author
+Type 3 — Project-specific ontology (optional decentralization):
+ Extensions unique to a single project that have not (yet) graduated
+ to shared-domain status. Lives as project-local oplog initially;
+ promotable to Type 2 when consumption emerges. The current
+ .ontology/ content of the 19 onboarded projects predominantly falls
+ here.
+Type 4 — Private ontology (local, tier-0):
+ Projects that have NCL files but no substrate, no oplog, no
+ operations layer. The pre-ADR-024 baseline. Untouched by this ADR.
+The classification is metadata on every ontology declaration. The
+ ontoref-ontology-content crate (new, see below) reads the classification
+ and applies the corresponding sync / sovereignty rules.
+New crate: ontoref-ontology-content
+Hosts the per-ontology oplog primitives, content-addressing, and
+ fetch-by-witness logic. Distinct from ontoref-ontology (which becomes
+ the bridge between state and NCL manifestation, per ADR-025). Roles:
+ - OntologyId(blake3) — content-address
+ - fetch_ontology(id, sync_backend) — pull ops from peers
+ - fetch_cell_witness(id, cell_query) — fetch only the witness needed
+ to verify a specific cell, not the whole state
+ - verify_cell(ontology_id, cell, witness, state_root) — local check
+ - publish_op(ontology_id, op) — append to the ontology's oplog if
+ authorized; broadcast via SyncBackend
+Cross-project verification via witness (the asymmetry-closing property):
+Project A invokes an operation whose precondition includes "this entity
+ is in the pool declared by shared ontology X". The runtime:
+ 1. Resolves X by content-address (ontology_id)
+ 2. Determines the cell-of-X the precondition reads (e.g. the pool
+ entity)
+ 3. Calls fetch_cell_witness(X, pool_query) — receives only the
+ witness for that cell + the current state_root of X
+ 4. Verifies the cell content + Merkle proof against state_root
+ 5. If valid, the precondition is satisfied; runtime proceeds
+No clone of X. No materialization of X's state. Only the witness
+ travels. The asymmetry is closed.
+Migration path:
+The existing 19 onboarded projects do not migrate immediately. ADR-028
+ is a structural commitment with progressive realization:
+ - The ontoref-core ontology (Type 1) is extracted first into its own
+ content-addressed oplog; the existing .ontology/core.ncl content
+ becomes the bootstrap content of that oplog
+ - Each shared-domain ontology (Type 2) is extracted when its
+ multi-project usage is operationally evident (provisioning is the
+ most concrete candidate — used by provisioning + forge-fleet +
+ libre-* projects)
+ - Project-specific (Type 3) extensions stay in their projects until
+ they need cross-project consumption
+ - Private (Type 4) projects remain unaffected
+
+Constraints
+
+Hard A new crate ontoref-ontology-content MUST exist with the following surface: OntologyId, fetch_ontology, fetch_cell_witness, verify_cell, publish_op. The crate depends on ontoref-types (for OpBody), ontoref-oplog (for log primitives), ontoref-commit (for state_root and witnesses), and optionally an SyncBackend impl.Hard Every ontology (in this project: ontoref-core, the existing .ontology/) MUST declare its classification (Type 1 | Type 2a | Type 2b | Type 3 | Type 4) in its metadata. The classification field is part of the OntologyMetadata struct surfaced via ontoref-ontology-content::metadata(ontology_id).Hard When ADR-028 transitions to Accepted (post-implementation), the ontoref-core ontology (current .ontology/core.ncl content) MUST be addressable via its content-address (blake3 of its head OpId chain root) and discoverable via at least one SyncBackend (FilesystemSync acceptable initially). The transition records the content-address as the canonical reference in CHANGELOG.md.Hard An integration test in crates/ontoref-ontology-content MUST demonstrate: project A invokes an operation whose precondition references a cell in ontology X, the runtime fetches only the cell witness (not the full ontology), verifies via state_root, and the operation proceeds without materializing X locally. The test fails if X's full state is loaded.Hard This ADR engages and synthesizes the protocol-not-runtime + self-describing axioms and the formalization-vs-adoption tension. The synthesis preserves all three: the ontology layer is reference content addressable by content-hash (not a runtime); self-description moves to its own layer (preserved structurally); classification with granularity absorbs the adoption shock per ontology.
+
+Alternatives considered
+
+Keep ontology inside each project; build cross-project federation that smart-loads partial ontology data — rejected: This was the current federation.rs design (BFS depth 5 over HTTP). It has been unused for 419 lines of code precisely because the cost of operating it (every project must expose endpoints, every cross-project query is a network call to a project's filesystem) exceeds the benefit. ADR-028's content-addressing inverts the model: the ontology IS the addressable unit, not 'a slice of a project'. The cost of operation drops because the sync target is purpose-built.Single global ontology repo (one Radicle repo for all shared-domain content) — rejected: Reproduces the centralization anti-pattern at the content-management layer. A single repo for all shared ontologies creates a coordination point (who can merge to it?) and a single failure mode. The per-ontology oplog model gives each ontology its own sovereignty — provisioning ontology and persona ontology have different maintainers, different update cadences, different consumers; collapsing them into one repo would force false coordination.Move all ontology to a decentralized layer immediately; no Type 3 (project-specific) or Type 4 (private) tier — rejected: Crushes adoption. A new project trying ontoref for the first time would be forced to publish its ontology to a decentralized channel as a precondition of using the protocol at all. The classification with optional decentralization for Types 3 and 4 lets projects start where they are; the decentralization happens when consumption justifies it, not as an entry tax.Keep ontology in project but use IPFS-style content addressing for cross-project refs — rejected: Hybrid that does not resolve the core asymmetry. The ontology still lives in project filesystem; the content-address is a thin convenience layer. Cross-project queries still require the responding project to materialize its full ontology (because the content-address points to a file the consumer might not have, hence needs full materialization to serve the content). The asymmetry is unresolved.Use Radicle for ontology layer too (one Radicle repo per ontology) — rejected: Radicle is a git-protocol-based system; its strengths are code review, branching, social collaboration. Ontology evolution is operationally distinct — append-only oplog with CRDT merge, not git-style branching. Forcing ontology into Radicle would impose the wrong semantics. The ontology layer needs its own primitive (oplog from G3); Radicle stays for project code.
+
+Anti-patterns
+
+Drag Whole Project For Cross-Project Verification — Verifying a single ontological assertion declared in another project by
+cloning that project and materializing its entire NCL state. Inverts the
+substrate's G5 witness asymmetry: a Merkle witness is hundreds of bytes,
+a project clone is megabytes-to-gigabytes plus parse cost. Saturates
+verifiers the way unbounded context saturates agents (H7 pattern).Centralized Shared-Ontology Repo — Collapsing every shared-domain ontology (provisioning, persona, lian-build,
+forge-fleet) into a single global repository. Creates one merge gatekeeper,
+one failure mode, and forces false coordination between ontologies whose
+maintainers, update cadences, and consumer sets are independent.
+
+Related ADRs
+
+ADR-001 · ADR-005 · ADR-009 · ADR-013 · ADR-018 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027
diff --git a/site/site/content/adr/en/accepted/adr-028.ncl b/site/site/content/adr/en/accepted/adr-028.ncl
new file mode 100644
index 0000000..d1e4a47
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-028.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-028",
+ title = "Ontology Layer Separation from Project Layer — Content-Addressed Decentralized Ontology with Classification",
+ slug = "028",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/028",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-005", "adr-009", "adr-013", "adr-018", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-029.md b/site/site/content/adr/en/accepted/adr-029.md
new file mode 100644
index 0000000..b84ee27
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-029.md
@@ -0,0 +1,143 @@
+---
+id: "adr-029"
+title: "Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration"
+slug: "029"
+subtitle: "Accepted"
+excerpt: "ADR-024 (operations as the agent's only project-touching path) and ADR-025"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-029", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-024 (operations as the agent's only project-touching path) and ADR-025
+(ontology core as authoritative state, NCL as manifestation) introduce a
+new mutation discipline grounded in the verifiable substrate (ADR-023).
+ADR-025 also performs the axiom split that replaces `no-enforcement` with
+`voluntary-adoption` (invariant=true) and `internal-coherence-enforced`
+(invariant=true). The split is precise: external adoption remains opt-in;
+internal coherence is enforced only AFTER adoption of the operations tier.
+Across the implementation of the operations-layer plan (gates O-pre → O7,
+session 2026-05-26), an observed ambiguity arose in operational decisions:
+the ontoref piloto self-host has migrated to operations as authoritative
+(tier-2, Phase 1), but every other onboarded project (mirador, provisioning,
+lian-build, vapora, stratumiops, kogral, libre-{daoshi,wuji,forge},
+secretumvault, typedialog, jpl-website, personal-ontoref, jpl-ontoref,
+cloudatasave, build-in-layers, DD7pasos, librosys, forge-fleet, mina) still
+relies on NCL-authoritative state with file-edit mutation. Questions
+recurred during the session — "are we still going to support NCL?", "did
+we migrate to Rust?", "do the tiers have the same platform requirements?"
+— that surfaced an unstated design property: the protocol supports
+multiple authoritative-state modes simultaneously, by construction, and
+indefinitely.
+The plan itself states this in three places without naming it as an
+architectural property worth its own ADR:
+- §1: "Piloto: ontoref mismo (self-hosted). Phase 2 queda fuera de
+ scope."
+ - §7 R11: "Phase 1 → Phase 2 nunca llega: Aceptable resultado
+ intermedio. No-transición es estado válido."
+ - §8: "Expansión a proyectos no-ontoref … requieren cada uno su sesión
+ de catálogo." (per-project decision, no global migration)
+Without an ADR naming the property, future sessions risk interpreting
+the migration as transitional and removing the tier-0/1 paths once the
+piloto stabilises. Doing so would silently violate the
+`voluntary-adoption` axiom and break the 18 onboarded projects that
+chose minimal NCL as their adoption surface. The session that produced
+this ADR included a sub-bug — the daemon's `global_schemas_path()` did
+not expose the data-dir schemas, so consumer projects in tier-0/1 got
+WARN-logged `config_surface not loaded` errors. The fix
+(`global_schema_dirs()` returning multiple paths) was structurally
+correct because it preserved tier-0/1 operability — but it would have
+been incorrect (silent migration coercion) if the design were
+transitional. This ADR records the property so the fix-or-not decision
+is explicit at every future point.
+
+Decision
+
+The protocol supports three authoritative-state modes simultaneously and
+indefinitely, as the canonical operational surface:
+tier-0 — minimal NCL adoption.
+ Authoritative state: `.ontology/*.ncl`, `adrs/`, `reflection/` as files.
+ Mutation: file edit (any editor).
+ Stack: `nickel` binary + optional `ontoref` Nushell CLI.
+ No daemon required for the project's own correctness; the daemon may
+ serve the UI and describe queries if installed.
+tier-1 — substrate adopted.
+ Authoritative state: NCL files PLUS the substrate's commit layer + oplog.
+ Mutation: file edit + reconcile (substrate ingests NCL on read).
+ Stack: tier-0 + `ontoref-daemon` binary + G1-G10 substrate crates.
+ Cryptographic: blake3 (commitment root).
+ Cross-instance verifiability via state_root + witness, but no domain
+ operations.
+tier-2 — operations adopted.
+ Authoritative state: Rust substrate (commit_layer + state_root).
+ NCL is manifestation rendered from state.
+ Mutation: dispatch_op exclusively (MCP, HTTP, CLI, or save-on-NCL
+ reconciler in Phase 1).
+ Stack: tier-1 + `ontoref-ops` + `ontoref-derive` + actor Ed25519 keypair.
+ Cryptographic: blake3 + Ed25519 (witness signing).
+ `internal-coherence-enforced` axiom applies HERE — operations are the
+ only mutation path. Below tier-2 the axiom does not apply (the
+ project hasn't adopted the operations tier; coherence remains
+ voluntary).
+The three modes coexist permanently. There is no scheduled transition.
+A project may stay at tier-0 indefinitely. A project may climb to
+tier-1 or tier-2 voluntarily, on its own schedule, project by project.
+The ontoref piloto runs at tier-2 / Phase 1; every other onboarded
+project chooses its own tier independently.
+Within tier-2, the Phase 1 → Phase 2 transition (NCL render-only,
+save-reconciler off) is also opt-in per project and recorded in
+`.ontoref/config.ncl::ops.phase`. The plan documents that Phase 2 may
+never be reached — that outcome is valid by design.
+Stack requirements are strictly additive: tier-N includes everything
+tier-(N-1) needs plus its own delta. No tier requires network access or
+external services for correctness. Optional features (`db`, `nats`,
+`ui`, `mcp`, `graphql`, P2P sync backends, S3/OCI blob backends) are
+orthogonal to tier — any project at any tier can opt into any feature
+independently. Network-using features (real sync, remote blobs) are
+trigger-based per ADR-027 / ADR-026 / D23: activated only when
+documented thresholds are exceeded.
+The daemon binary serves all tiers from a single build. Per-project
+configuration declares the tier; the daemon dispatches against the
+declared mode. Migration of an individual project is a per-project
+deliberate act, not a protocol upgrade.
+
+Constraints
+
+Hard The protocol MUST NOT include any mechanism that automatically migrates a project from one tier to another without an explicit per-project decision recorded in `.ontoref/config.ncl`. Migration aids (templates, documentation, CLI helpers) are permitted; automatic mutation of a project's tier is not.Hard The daemon's NICKEL_IMPORT_PATH MUST resolve canonical schema imports (`manifest`, `core`, `state`, `gate`, `ontoref-project`) for tier-0/1 projects. The resolution MUST include `$data_dir/ontology/schemas/` and `$config_dir/schemas/`.Hard The default build of `ontoref-daemon` (any features active) MUST operate correctness-wise without network access. Optional sync backends (SyncBackend impls beyond FilesystemSync) and blob backends (BlobBackend impls beyond LocalFilesystemBlobs) MAY require network when activated, but the default substrate MUST NOT.Hard Changes to ontoref-ops, ontoref-derive, ontoref-ontology-content, or the substrate crates MUST NOT remove a public API that tier-0/1 consumers rely on. Schema additions are permitted; schema removals require a new ADR documenting the deprecation path.Hard Tier transitions (upgrade tier-N → tier-N+1 OR downgrade tier-N → tier-N-1)
+MUST refuse to execute when any protocol migration is pending. Specifically:
+`ontoref migrate pending` MUST return an empty list before any change to
+`.ontoref/config.ncl::ops.tier` is accepted by the daemon.
+
+Enforcement layers:
+ 1. The future `transition_tier` operation (to be defined alongside
+ ADR-033) declares this constraint as a synchronous pre-validator.
+ 2. Until `transition_tier` is implemented, the daemon's ConfigWatcher
+ enforces the same precondition: any mutation of `ops.tier` in
+ `.ontoref/config.ncl` triggers a guard that consults the project's
+ migration store. When pending migrations are detected, the daemon
+ emits a Block-severity notification, refuses to swap the config in
+ its registry, and the project remains at its current tier value.
+
+This is a forward declaration: the constraint exists at the
+protocol-semantic level (ADR-029) before its operational
+materialisation (ADR-033). The daemon-side enforcement closes the
+operational gap immediately so manual `.ontoref/config.ncl` edits
+cannot bypass the rule during the period between this amendment and
+ADR-033's authorship.
+
+Alternatives considered
+
+Schedule a transition: all projects migrate to tier-2 by date X — rejected: Violates voluntary-adoption (invariant=true) by construction. Forcing migration would require superseding ADR-025 with a new axiom — and no empirical evidence motivates that change. The 18 onboarded projects chose tier-0/1 deliberately; revoking that choice is hostile to ecosystem participants. Also: there is no engineering need — the substrate G1-G10 already coexists with NCL ingestion and the cost of supporting both is dwarfed by the cost of forcing migration on consumer teams.Pick one mode (NCL-authoritative OR Rust-authoritative) and deprecate the other — rejected: Both modes have first-class users. Documentation-heavy projects need NCL editability with low ceremony. Multi-actor projects need cryptographic witnesses. Pruning either side narrows the protocol's value. The plan itself preserved this — Phase 2 is opt-in not mandatory, tier-2 is opt-in not mandatory. Codifying that choice as an ADR makes it stable.Leave the property implicit in plan §7 R11 and §8 without a dedicated ADR — rejected: The implicit-in-plan path produced confusion during the implementation session itself ('did we migrate?', 'why are we fixing NCL paths?'). If the design intent was unclear at the moment of implementation, it will be unclear at every future session. The cost of one ADR is small; the cost of repeated rediscovery is large.Define an even finer tier ladder (tier-0.5, tier-1.5, etc.) for fractional adoption — rejected: The three-tier model already maps to the three distinct value propositions (typed records / cryptographic state / verifiable ops). Sub-tiers would multiply the support matrix without adding qualitatively new modes. If a use case emerges that genuinely needs an intermediate tier, a future ADR can extend the ladder; pre-emptively splitting it is over-design.Force tier-2 only for new projects, leave existing 18 on tier-0/1 — rejected: Splits the ecosystem into 'old protocol' and 'new protocol' permanently — every cross-project feature has to handle the discontinuity. Permanent coexistence within a single protocol is cleaner: every project chooses its tier, every tier interoperates with the others (within voluntary-adoption limits), and the design admits no temporal cohort.
+
+Anti-patterns
+
+Formalizing during the fire (or under perpetual firefighting) — Treating ontoref as something to build or update mid-incident, when latency-to-action dominates — or adopting it at all when the work is perpetual firefighting or throwaway (a spike you will delete). The types, flows, and definitions become pure tax: they stop you to understand something you will not revisit. The resolution is not 'never stop' but the distinction between stopping-to-build and stopping-to-read: introspection is time-shifted, not eliminated. You pay in the calm and spend the dividend in the fire — a `describe impact` / `describe constraints` read costs seconds and tells you which pipe not to cut. Harm occurs only when you try to formalize during the fire, or when no calm window ever exists.Tier-0 false certainty — form outruns truth, no witness to anchor — A well-typed NCL graph looks authoritative even when it is stale or aspirational; the crispness of the form lends unearned credibility to the content. A desired_state can read as a real rudder when it was only a wish. Below tier-2 this harm is undefended: a pretty stale graph misleads exactly like a stale README — worse, because the README does not feign rigor. The witness (tier-2) is the only thing that anchors certainty to something independently verifiable; without it, ontoref can manufacture false certainty.Ceremony capture — feeding the protocol instead of the work — Adoption degrades into satisfying the protocol — updating nodes, writing ADRs, contenting validators — rather than doing the work the protocol was meant to serve. The tail wags the dog. This is the Yang collapse of formalization-vs-adoption: maximal formalization imposed where it does not pay back.Premature formalization — freezing an unstable identity — Typing something whose identity is not yet stable freezes a guess. Declaring invariants over what is not yet invariant — a pre-product-market-fit product, a person in genuine transition — ossifies what should stay fluid and makes the inevitable change expensive. Form applied before the substance has settled is rigidity, not coherence.
+
+Related ADRs
+
+ADR-001 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028
diff --git a/site/site/content/adr/en/accepted/adr-029.ncl b/site/site/content/adr/en/accepted/adr-029.ncl
new file mode 100644
index 0000000..526fd01
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-029.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-029",
+ title = "Tier Coexistence as Permanent Design — Strictly Additive Stack, Offline-First, No Forced Migration",
+ slug = "029",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/029",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-031.md b/site/site/content/adr/en/accepted/adr-031.md
new file mode 100644
index 0000000..cca7d85
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-031.md
@@ -0,0 +1,246 @@
+---
+id: "adr-031"
+title: "Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness"
+slug: "031"
+subtitle: "Accepted"
+excerpt: "The named tension `ontology-vs-reflection` has lived in `.ontology/core.ncl`"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-031", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The named tension `ontology-vs-reflection` has lived in `.ontology/core.ncl`
+since the project's first ondaod pass with `pole = 'Spiral` and the gloss
+"This tension is onref's core identity." The tension is the protocol's name
+for itself: **on+re**. Ontology captures what IS (invariants, structure,
+being); Reflection captures what BECOMES (operations, drift, memory). The
+Spiral pole records that neither half is reducible to the other.
+The session arc 2026-05-20 → 2026-05-26 (ADR-023 verifiable substrate, ADR-024
+operations layer, ADR-025 ontology core as state and NCL as manifestation,
+ADR-026 three-plane validation, ADR-027 P2P pluggable sync, ADR-028
+ontology layer separation from project layer, ADR-029 tier coexistence,
+ADR-030 catalog discovery cross-project) produced a precise, lived
+articulation of one half of the tension — the **ontology axis**. Substrate
+(G1-G10), commit roots, state authority, content-addressed ontologies,
+schema discovery: all of these structurally name the substance pole. The
+**reflection axis** received concrete commitments too (operations as the
+only mutation path, witnesses as cryptographic acts, reflection modes as
+NCL DAGs, describe queries as self-knowledge), but its axiom-level status
+was never asserted on equal footing.
+Two observations made the gap visible:
+(1) A reading of the eight 2026-05-20s ADRs against the external "modern
+ ontology stack" vocabulary (Cagle / Shannon 2026-05-19, *What a Modern
+ Ontology Stack Actually Looks Like*) revealed that the article's five
+ layers (annotational, schema, graph, projection, inference) cover the
+ ontology axis cleanly and have no vocabulary for the reflection axis.
+ The article's "inference layer" is passive derivation (SPARQL,
+ SHACL rules, LLM resonance over a static substrate); ontoref's
+ reflection layer is enactive (DAG modes execute work, forms mutate
+ lifecycle, run protocols are acts the system performs on itself).
+ Forcing reflection into a "layer" of an ontology-axis stack imports
+ the substance-only metaphysics the external article carries.
+(2) ADR-030 surfaces the duality operationally without naming it: the
+ catalog has a "DECLARATIVE side (NCL)" — ontology pole — and an
+ "EXECUTIVE side (Rust)" — reflection pole. ADR-030 § decision
+ observes that the declarative side composes cross-project today via
+ ADR-028 content-addressing while the executive side awaits triggers.
+ The two sides progress at different rates because they live on
+ different axes. ADR-030 names this asymmetry empirically; ADR-031
+ names it architecturally.
+Without an axiom-level recognition, the same drift will recur: future
+sessions may interpret reflection components as "tooling around the
+ontology" or treat state as primary rather than as a manifestation that
+acts deposit on substance. ADR-025 already moved state from primary to
+"render of authority"; that move is half of what this ADR generalises. The
+generalisation: the protocol is **constitutively dual**. Every component
+sits on the ontology axis, the reflection axis, or the seam where the two
+are bound. The seam has a name in the existing architecture — the
+**witness** (ADR-023 § decision, ADR-024 § acting-binding). Every reflective
+act emits a witness; every witness deposits manifestation into the
+ontological substrate. The seam is the formalism by which the two axes
+remain bound without one absorbing the other.
+The risk of NOT recording this duality at axiom level is concrete: the
+external "modern ontology stack" vocabulary is mature, legible, and
+adoption-ready, but it carries a substance-only frame that — uncritically
+imported — would collapse the protocol's core identity. The recent ADRs
+023-030 stand within the protocol's own discipline (ondaod, Spiral
+preservation, voluntary-adoption). An ADR that elevates the duality
+itself becomes the constitutional anchor against which any future external
+vocabulary import is checked.
+
+Decision
+
+Elevate the existing `ontology-vs-reflection` Spiral tension to an axiom-
+level constitutive duality of the protocol. Two new axiom nodes are added
+to `.ontology/core.ncl`; the existing tension node remains in place as the
+ondaod-procedural anchor (tensions are where the Spiral is named; axioms
+are where the structural commitments live). The duality has three
+formal commitments:
+1. ONTOLOGY AXIS — `ontology-axis-substance` (axiom, invariant=true)
+The protocol's substance pole. Names: axioms, tensions, practices,
+ schemas, ADR declarations, content-addressed ontologies, state as
+ manifestation (ADR-025), substrate commitments (ADR-023), the
+ declarative side of catalogs (ADR-030). Maps onto the existing
+ `pole = 'Yin` convention for nodes whose primary character is
+ being/structure.
+Operational reading: every artifact that answers "what IS" — a
+ schema declaration, an ADR constraint, an ontology node, a render
+ from state, a commit root — lives on this axis.
+2. REFLECTION AXIS — `reflection-axis-act` (axiom, invariant=true)
+The protocol's act pole. Names: operations, dispatch, reflection
+ modes (DAG enactment), forms (lifecycle mutation), describe queries
+ (self-observation), run protocols (acts the system performs on
+ itself), the executive side of catalogs (ADR-030), validator
+ execution. Maps onto the existing `pole = 'Yang` convention for
+ nodes whose primary character is becoming/operation.
+Operational reading: every artifact that answers "what DOES" or
+ "what KNOWS itself" — a domain operation, a mode step, a form
+ submission, a `describe` invocation, a validator firing, a run
+ transition — lives on this axis.
+3. WITNESS AS SEAM — `witness-as-axis-seam` (axiom, invariant=true)
+The witness (ADR-023 G5 commitment + signature, ADR-024 acting-
+ binding) is the formal seam where the two axes meet. Every act on
+ the reflection axis that mutates substance emits a witness; every
+ mutation of the ontology axis state is the deposit of a witnessed
+ act. The witness is neither pure substance nor pure act — it is the
+ point at which the two are bound. Maps onto the existing
+ `pole = 'Spiral` convention for nodes that sustain both poles
+ without collapse.
+Operational reading: any component on the seam (witness chain,
+ commit emission, dispatch protocol, state reconciliation in
+ Phase 1 of ADR-025) carries the load of preserving the duality
+ across the act-to-substance transition.
+AXIS DECLARATION REQUIREMENT (new structural property):
+Every node in `.ontology/core.ncl` already carries a `pole` field
+ ('Yin, 'Yang, 'Spiral) and a `level` field ('Axiom for invariant-level
+ nodes among others). ADR-031 fixes the operational semantics of the
+ pole field as the axis-declaration mechanism:
+'Yin → ontology axis (substance side)
+ 'Yang → reflection axis (act side)
+ 'Spiral → seam (sustains both poles; not a derived synthesis)
+The pole field of `Axiom`-level nodes that ALSO commit to an axis MUST
+ carry a secondary `axis` field with value 'Substance | 'Act | 'Seam.
+ This is additive and zero-migration for the existing nodes whose pole
+ already encodes their axis cleanly.
+GLOSSARY AXIS FIELD (forward integration):
+The forthcoming `.ontology/glossary.ncl` schema (anticipated by the
+ ontoterm-* skills and the present session's draft state) MUST include
+ an `axis: 'Substance | 'Act | 'Seam` field on every canonical term.
+ Terms that name substance ("axiom", "schema", "manifestation",
+ "commit-root") declare 'Substance. Terms that name acts ("operation",
+ "dispatch", "describe", "mode", "form") declare 'Act. Terms that name
+ seams ("witness", "on+re", "ondaod") declare 'Seam.
+FORBIDDEN PATTERNS (consequence of the duality):
+The following architectural moves become explicit anti-patterns under
+ ADR-031:
+(a) Treating reflection components as "tooling" or "infrastructure
+ around the ontology". Reflection is axis-level; not subordinate.
+ (b) Treating state (ADR-025 manifestation) as the primary seat of
+ authority. State is derived from witnessed acts; it is substance
+ sediment, not source.
+ (c) Importing external vocabulary that names only one axis (e.g. the
+ RDF / SHACL semantic-stack vocabulary that has no analogue for
+ enactive reflection) AS IF it covered the protocol. Such
+ vocabulary may be used for projection (per ADR-028 / ADR-030),
+ but never as primary terminology for ontoref components.
+ (d) Synthesizing the two axes into a single "stack" or "layered
+ architecture". The duality is constitutive: there is no layer
+ in which it dissolves.
+ (e) Designing components that operate on one axis without declaring
+ their seam interaction. A new reflection-axis component (e.g. a
+ new mode kind, a new form variant) MUST declare how its acts
+ emit witnesses; a new ontology-axis component (e.g. a new schema
+ type, a new ADR family) MUST declare how it receives manifestation
+ from witnessed acts.
+OPERATIONAL CONSEQUENCES (downstream ADRs reread under ADR-031):
+- ADR-023 verifiable substrate: G1-G5 are substance-axis (types,
+ triples, commitments); G6 (oplog) is act-axis (recording acts); G5
+ witness is seam.
+ - ADR-024 operations layer: acting-binding declares the act-axis as
+ the only path to substance mutation. ADR-031 generalises:
+ "structurally enforced internal coherence" (the existing axiom)
+ operates by routing all acts through the seam.
+ - ADR-025 state as manifestation: substance is rendered from
+ witnessed act log. The axiom split (voluntary-adoption +
+ internal-coherence-enforced) ADR-025 introduced is now expressible:
+ voluntary-adoption is a property of the BINDING between act and
+ substance (adoption is itself an act); internal-coherence-enforced
+ is a property of the SEAM (witness chain rejects ill-formed acts).
+ - ADR-026 three-plane validation: the three planes (Structural /
+ Semantic / Dialectical) operate at the seam. They are not
+ substance-axis checks; they are conditions on what acts may pass
+ through the seam.
+ - ADR-027 P2P pluggable sync + ADR-028 ontology layer separation:
+ cross-project content-addressing is substance-axis (the ontology
+ travels as a substance artifact). Cross-project operation invocation
+ (ADR-030) is act-axis (the act travels). The seam-axis question is
+ cross-project witness verification (how does project B verify a
+ witness signed by project A) — recorded as an open seam-axis
+ question for future ADR.
+ - ADR-029 tier coexistence: the three tiers correspond to three
+ points of duality engagement. tier-0 engages only the substance
+ axis (NCL files). tier-1 adds substrate (substance-axis with
+ cryptographic state, but no acting-binding). tier-2 fully engages
+ the seam (acts emit witnesses; substance is sediment of witnessed
+ acts). The tiers are progressive realisations of the same duality,
+ not separate architectures.
+ - ADR-030 catalog discovery: the declarative/executive split is the
+ ontology/reflection split. Phase A mechanisms preserve the duality
+ (declarative composes via substance-axis content addressing;
+ executive composes via act-axis dispatch). The CatalogBackend
+ abstraction sits on the seam.
+NON-DECISIONS (deliberately left open):
+- Terminology for the two poles in user-facing documentation
+ (substance/act, being/becoming, yin/yang, 体/用, ontology/reflection).
+ The axiom IDs `ontology-axis-substance` and `reflection-axis-act`
+ fix the canonical English handle; vocabulary choice in docs,
+ glossary, and CLI output is open for an annotational-layer ADR.
+ - Whether the witness seam needs a finer decomposition (witness emission
+ vs witness verification vs witness chain integrity) at axiom level.
+ Deferred until cross-project witness verification (per ADR-028)
+ surfaces empirical pressure.
+ - Whether reflection-axis components require their own typing
+ discipline parallel to ontology-axis schemas. The current operation
+ catalog (ADR-030 declarative side) is the existing answer; whether
+ a richer "operation ontology" is needed is deferred.
+
+Constraints
+
+Hard .ontology/core.ncl MUST contain three new Axiom-level nodes with ids ontology-axis-substance, reflection-axis-act, and witness-as-axis-seam. All three MUST have invariant=true. The existing ontology-vs-reflection Tension node MUST remain present (axiom elevation is additive, not replacing).Hard Each of the three new Axiom nodes MUST declare an axis field with value 'Substance (for ontology-axis-substance), 'Act (for reflection-axis-act), or 'Seam (for witness-as-axis-seam). The field is additive; pre-existing axioms are unaffected.Hard Documentation and ADRs MUST NOT adopt the Cagle/Shannon modern-ontology-stack five-layer model (annotational / schema / graph / projection / inference) as primary self-description of ontoref. The model MAY be used at the projection layer (docs/correspondences/) for external-audience translation, but MUST NOT replace the on+re duality vocabulary in core protocol description.Hard This ADR engages and synthesizes the named tension ontology-vs-reflection AND records the synthesis as axiom-level elevation rather than collapse. The synthesis state and direction of motion MUST be discoverable in the decision and rationale of this ADR.Hard A migration MUST be added to reflection/migrations/ that instructs consumer projects to add the three new Axiom nodes (ontology-axis-substance, reflection-axis-act, witness-as-axis-seam) to their own core.ncl. The migration MUST be idempotent and MUST NOT remove the existing ontology-vs-reflection Tension node.
+
+Alternatives considered
+
+Leave ontology-vs-reflection as a Tension only, without elevating to axiom — rejected: The current Tension-only position is what allowed the asymmetric articulation observed in ADRs 023-030 — substance pole received structural commitments (substrate, state authority, content-addressing) while act pole received only operational commitments (operations exist, modes exist) without axiom-level recognition. Without axiom elevation, the same drift will recur on every future external-vocabulary import. The Tension records the polarity; only an axiom records the structural commitment to preserve it.Adopt the Cagle/Shannon modern-ontology-stack five-layer model as ontoref's primary self-description — rejected: The five-layer model (annotational, schema, graph, projection, inference) covers the substance axis cleanly and has NO vocabulary for the act axis beyond passive derivation in the inference layer. Adopting it primarily would erase the reflection axis from ontoref's self-description, collapsing the duality the existing Tension explicitly preserves. The model is useful at the projection layer (per ADR-028) as a correspondence for external RDF/SHACL audiences — but not as primary terminology.Introduce three new axes (substance, act, seam) without preserving the existing ontology-vs-reflection Tension node — rejected: Removing the Tension node would lose the ondaod-procedural anchor: tensions are where the Spiral is named for review by future sessions. Replacing the Tension with three Axioms collapses the dialectical record into a structural commitment. ADR-031 keeps both: the Tension preserves the Spiral-naming discipline; the three new Axioms commit to the structural property. Both are non-redundant.Use only the existing pole field (Yin/Yang/Spiral) without adding an explicit 'axis' field on the new axiom nodes — rejected: The pole field carries informal convention; axiom nodes deserve an explicit field for axis declaration because (a) axioms set the structural commitment that the convention must henceforth follow, and (b) the explicit field protects against future pole-field semantics drift. The 'axis' field is additive and zero-migration; the cost is small, the explicitness benefit is real.Defer axiom elevation until cross-project witness verification surfaces empirical pressure — rejected: The pressure has already surfaced — twice. First operationally (ADR-030's declarative/executive split). Second meta-architecturally (the present session's analysis of external semantic-stack vocabulary). Deferring would let future sessions silently adopt vocabulary that erodes the duality. The cost of an early axiom is small; the cost of late axiom recovery (retroactively rejecting an adopted vocabulary) is large. Trigger-based deferral (ADR-026/D15 pattern) applies to abstraction-point IMPL choices, not to constitutional commitments.
+
+Anti-patterns
+
+Reflection-as-Tooling — Treating reflection components — modes, forms, describe, dispatch,
+validators — as "tooling" or "infrastructure around the ontology".
+Demotes the act axis to subordinate plumbing. Reflection is axis-level,
+co-equal with ontology; calling it tooling erases the constitutive
+duality at the vocabulary boundary.State-as-Primary-Authority — Treating state (the manifestation rendered from witnessed acts) as the
+primary seat of authority. Reverses the ADR-025 direction: state is
+substance sediment, not source. Acting on state directly bypasses the
+witness chain that gives state its integrity.Single-Axis Vocabulary Import — Adopting an external vocabulary that names only one axis (e.g. the
+RDF/SHACL/OWL semantic-stack: annotational, schema, graph, projection,
+inference) as ontoref's primary terminology. Imports a substance-only
+metaphysics that has no analogue for enactive reflection — the act axis
+becomes invisible in the imported lexicon.Duality-as-Layered-Stack — Flattening the on+re duality into a single layered architecture (e.g.
+"ontology layer → operations layer → UI layer"). Layers imply
+subordination; the substance and act axes are co-equal and the seam is
+not a layer between them but the formalism that binds them. There is no
+layer in which the duality dissolves.Axis Component Without Seam Declaration — Designing a new component that operates on one axis without declaring its
+seam interaction. A reflection-axis component (new mode kind, new form
+variant) that does not declare how its acts emit witnesses. An ontology-
+axis component (new schema family, new ADR genre) that does not declare
+how it receives manifestation from witnessed acts. Either omission lets
+the two axes drift toward disconnection one component at a time.
+
+Related ADRs
+
+ADR-009 · ADR-018 · ADR-020 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028 · ADR-029 · ADR-030
diff --git a/site/site/content/adr/en/accepted/adr-031.ncl b/site/site/content/adr/en/accepted/adr-031.ncl
new file mode 100644
index 0000000..5640562
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-031.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-031",
+ title = "Ontology and Reflection as Constitutive Duality — Co-Equal Axes Sealed by Witness",
+ slug = "031",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/031",
+ graph = {
+ implements = [],
+ related_to = ["adr-009", "adr-018", "adr-020", "adr-023", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028", "adr-029", "adr-030"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-032.md b/site/site/content/adr/en/accepted/adr-032.md
new file mode 100644
index 0000000..b01b731
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-032.md
@@ -0,0 +1,161 @@
+---
+id: "adr-032"
+title: "Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint"
+slug: "032"
+subtitle: "Accepted"
+excerpt: "Every project that adopts ontoref currently inherits a sprawl of root"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-032", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Every project that adopts ontoref currently inherits a sprawl of root
+directories: `.ontoref/` (hidden — config and runtime state), `.ontology/`
+(hidden — declarative ontology), `adrs/` (visible — Architecture Decision
+Records), `reflection/` (visible — qa, backlog, modes, schemas, modules,
+migrations, nulib), and most recently `catalog/` (visible — operations and
+validators per ADR-024, ADR-026). For consumer projects this means:
+- Five root directories owned by ontoref alone, consuming 33-40% of the
+ `L-ROOT-LIMIT` (target 12-15 total root dirs) defined in
+ `.claude/layout_conventions.md`.
+ - Inconsistent hidden/visible split: `.ontoref/` and `.ontology/` are
+ dot-prefixed (following `L-NAMING-HIDDEN` for infrastructure dirs),
+ while `adrs/`, `reflection/`, `catalog/` are visible without
+ architectural justification for the inconsistency.
+ - Mixed identity signal: the visible directories present as
+ "first-class project content" alongside `crates/`, `src/`, `docs/`,
+ when in fact they are infrastructure of the protocol supporting the
+ project — not the project's substantive content.
+ - Operational friction: copying ontoref state across projects requires
+ enumerating five paths; deleting cleanly requires five `rm -rf`
+ invocations; CI exclusion requires five glob patterns; backup/restore
+ means five tar entries; auditing "is ontoref clean here?" means five
+ independent checks.
+The current layout grew organically across ontoref's evolution. Each
+section (`.ontology/` from the earliest ondaod pass; `adrs/` from
+adr-001's adoption of ADR-as-data; `reflection/` from the
+adopt-ontoref-tooling onboarding template; `catalog/` from ADR-024's
+operations layer) arrived as a section of the protocol's design and was
+placed where it made narrative sense AT THAT MOMENT. Nothing ever
+collected them under a common root because the protocol grew as a tree,
+not as a module. The session 2026-05-26 (ADR-029 tier coexistence
+articulation, then ADR-030 catalog discovery, then ADR-031 on+re duality
+elevation) produced both more sub-directories AND increased visibility
+of the cost of the layout sprawl. The user's observation:
+"ontoref debería aparecer en los proyectos como una estructura en un sólo
+ folder ... esto convierte al layout de proyectos en un lío además de no
+ cumplir con `.claude/layout_conventions.md`. Facilitaría mucho que todo
+ estuviera dentro de una misma jerarquía."
+This ADR records that observation as architectural decision and consolidates
+ontoref's consumer footprint into a single hidden hierarchy under
+`.ontoref/`, propagated per-project via migration 0023 (opt-in, consistent
+with `voluntary-adoption`). The change is a protocol version increment to
+**0.1.1** — breaking layout change with migration assistance and dual-path
+resolver back-compat during the transition window.
+
+Decision
+
+All ontoref consumer-facing artefacts MUST consolidate under a single
+hidden root directory `.ontoref/` per consumer project. The canonical
+layout becomes:
+my-project/
+ └── .ontoref/ ← single hidden ontoref subsystem root
+ ├── config.ncl ← daemon config (unchanged location)
+ ├── project.ncl ← project identity (unchanged location)
+ ├── actors.ncl ← actor keypairs (unchanged location)
+ ├── ontology/ ← MOVED FROM .ontology/
+ │ ├── core.ncl
+ │ ├── state.ncl
+ │ ├── gate.ncl
+ │ ├── manifest.ncl
+ │ └── _refs.ncl ← was _ontology_refs.ncl
+ ├── adrs/ ← MOVED FROM adrs/
+ │ ├── adr-*.ncl
+ │ └── _template.ncl
+ ├── reflection/ ← MOVED FROM reflection/
+ │ ├── qa.ncl
+ │ ├── backlog.ncl
+ │ ├── search_bookmarks.ncl
+ │ ├── modes/
+ │ ├── forms/
+ │ ├── schemas/
+ │ ├── modules/
+ │ ├── migrations/
+ │ └── nulib/
+ ├── catalog/ ← MOVED FROM catalog/
+ │ ├── schema.ncl
+ │ ├── operations/
+ │ └── validators/
+ ├── store/ ← runtime state (oplogs, witness chain)
+ │ └── ontologies/ ← was .ontoref/ontologies/ (content-addressed oplogs)
+ └── locks/ ← runtime advisory locks (unchanged)
+The consumer project's own content (`crates/`, `src/`, `docs/`, `assets/`,
+etc.) retains its existing layout. Only ontoref's footprint moves.
+DUAL-PATH RESOLVER (transition period)
+During the version 0.1.0 → 0.1.1 transition, the daemon implements a
+ dual-path resolver in `crates/ontoref-daemon/src/registry.rs` and
+ `crates/ontoref-ontology/src/ontology.rs` that:
+1. Prefers the new path (`.ontoref/<section>/`) when it exists.
+ 2. Falls back to the legacy path (root-level `.ontology/`, `adrs/`,
+ `reflection/`, `catalog/`) when the new path is absent.
+ 3. Emits an `info`-level log when the legacy path is used, noting
+ the project has not yet applied migration 0023.
+The dual-path resolver remains in the codebase until ontoref version
+ 0.2.0; at that point a future ADR may declare the legacy paths
+ removed (advisory, not forced).
+ONTOREF-EL-PROYECTO MIGRATES FIRST (canonical demonstration)
+Per the `self-describing` axiom (invariant=true), ontoref-el-proyecto
+ applies migration 0023 before any consumer project. This produces:
+- The canonical layout demonstration that other projects can follow
+ - First-class evidence that the migration script works on real
+ content (ontoref's own ontology has 40 nodes, 91+ edges, 32 ADRs)
+ - The reference state for the dual-path resolver tests
+Consumer projects (mirador, libre-{daoshi,wuji,forge}, lian-build, the
+ other 14 currently onboarded) apply migration 0023 voluntarily on
+ their own schedules. Until they do, the daemon serves them via the
+ legacy paths through the dual-path resolver.
+SCOPE — WHAT MOVES, WHAT DOES NOT
+MOVES into .ontoref/:
+ .ontology/ → .ontoref/ontology/
+ adrs/ → .ontoref/adrs/
+ reflection/ → .ontoref/reflection/
+ catalog/ → .ontoref/catalog/
+STAYS at root (for ontoref-el-proyecto only, not for consumers):
+ crates/ (Rust implementation crates — not consumer-relevant)
+ install/ (install scripts — not in consumer projects)
+ justfiles/ (build automation)
+ scripts/ (support scripts — co-existing with the consumer's own)
+ templates/ (consumer onboarding templates)
+ ontology/ (defaults + schemas installed to consumer data dir)
+ assets/ (web + presentation)
+REMAINS unchanged (already under .ontoref/):
+ .ontoref/config.ncl
+ .ontoref/project.ncl
+ .ontoref/actors.ncl
+ .ontoref/locks/
+NCL IMPORT PATH UPDATES
+Inside the moved files, relative `import "..."` paths are updated to
+ reflect the new depth:
+OLD: `.ontology/core.ncl` with `import "../ontology/defaults/core.ncl"`
+ NEW: `.ontoref/ontology/core.ncl` with `import "../../ontology/defaults/core.ncl"`
+The daemon's `NICKEL_IMPORT_PATH` (managed by `global_schema_dirs()`)
+ continues to include the data-dir schemas at `$data_dir/ontology/schemas/`
+ and `$data_dir/ontology/`. Absolute schema imports (`import "manifest"`,
+ `import "core"`) keep working unchanged.
+
+Constraints
+
+Hard All ontoref consumer-facing artefacts (ontology declarations, ADRs, reflection store, catalog declarations, runtime state, locks) MUST live under `.ontoref/` in a consumer project. The legacy root-level directories `.ontology/`, `adrs/`, `reflection/`, `catalog/` are deprecated by migration 0023 and removed by the migration's mv operations.Hard The daemon's `registry.rs` and `ontology.rs` MUST implement a dual-path resolver that prefers the new path (`.ontoref/<section>/`) and falls back to the legacy path (root-level `.ontology/`, `adrs/`, `reflection/`, `catalog/`) when the new path is absent. The fallback MUST emit an info-level log noting the project has not yet applied migration 0023.Hard Migration 0023 MUST be applied per-project via the standard `ontoref migrate` workflow. No automatic / silent / boot-time migration of consumer projects is permitted. The daemon detects un-migrated consumers via the legacy-path fallback and surfaces the recommendation via `ontoref migrate pending`, but does not act on it.Hard ontoref-el-proyecto (this repository) MUST apply migration 0023 to its own consumer-facing artefacts before any consumer project is recommended to apply it. The canonical layout under `.ontoref/` is demonstrated in ontoref's own repo first; consumer documentation references the ontoref repo as the worked example.Hard The project beacon `card.ncl` and the generated `artifacts/api-catalog-*.ncl` files MUST live under `.ontoref/` after migration 0023, NOT at project root. `card.ncl` is pure ontoref metadata (not an OS-level discovery convention like `Cargo.toml` or `package.json`) and `artifacts/` is generated ontoref output — both belong under the consolidated root.Hard Section paths (ontology_dir, adrs_dir, reflection_dir, catalog_dir, artifacts_dir, card_path) MUST be addressable through `LayoutConfig` rather than hard-coded literals in production code. Defaults match the consolidated layout (`.ontoref/<section>/`), but projects MAY override any field via their `.ontoref/config.ncl` `[layout]` section. All call sites that read or write ontoref artefacts MUST route through `ontoref_ontology::layout::resolve_section` (reads, with legacy fallback) or `canonical_section` (writes, no fallback) or an explicit `LayoutConfig::resolve` / `canonical` method.
+
+Alternatives considered
+
+Keep the current layout — accept the L-ROOT-LIMIT violation as a cost of organic growth — rejected: The user's observation made the cost concrete and named the protocol's own convention (`.claude/layout_conventions.md`) as the rule being violated. The protocol cannot ignore its own conventions without losing credibility as a self-describing system. Five root directories for one subsystem is not organic growth; it is unsynchronised growth that the consolidation corrects.Move ALL of ontoref under a visible `ontoref/` directory (not hidden) — rejected: Visible directories signal 'first-class project content' alongside crates/, src/, docs/. ontoref is infrastructure-of-the-project, not the project's value content. L-NAMING-HIDDEN explicitly prescribes dot-prefix for process and configuration directories. Visible `ontoref/` would re-introduce the same identity-signal confusion the consolidation is meant to eliminate.Hybrid layout — runtime state hidden, declarative content visible (`.ontoref/store/` + `ontoref/ontology/`, etc.) — rejected: Re-introduces the inconsistency this ADR was opened to resolve. Two root dirs instead of five is an improvement but stops short of the single-point operational manipulation that the configuration-cube of ADR-029 requires. Half-measures cost the same migration effort as the full move with less of the benefit.Per-tier layouts (tier-0 → minimal under .ontoref/; tier-2 → richer hierarchy) — rejected: Multiplies the support matrix without qualitative gain. ADR-029 / `tier-coexistence-permanent-design` says tiers are about authoritative-state regimes, not about disk layout. Having one canonical layout that scales to all tiers (some directories empty at tier-0, populated at tier-2) is cleaner than three layouts.Move incrementally — only `catalog/` to `.ontoref/catalog/` first (it is the newest, with no legacy users), defer `adrs/` / `reflection/` / `.ontology/` for later versions — rejected: Three partial migrations cost more than one comprehensive migration. Consumer maintainers would face N rounds of layout disruption instead of one. The dual-path resolver pattern works equally well for one or four moved directories. Doing it all at version 0.1.1 lets the protocol stabilise at the new layout sooner.
+
+Related ADRs
+
+ADR-001 · ADR-010 · ADR-020 · ADR-024 · ADR-025 · ADR-026 · ADR-028 · ADR-029 · ADR-030 · ADR-031
diff --git a/site/site/content/adr/en/accepted/adr-032.ncl b/site/site/content/adr/en/accepted/adr-032.ncl
new file mode 100644
index 0000000..89fed56
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-032.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-032",
+ title = "Layout Consolidation Under .ontoref/ Root — Single Hidden Hierarchy for the Protocol's Consumer Footprint",
+ slug = "032",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/032",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-010", "adr-020", "adr-024", "adr-025", "adr-026", "adr-028", "adr-029", "adr-030", "adr-031"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-033.md b/site/site/content/adr/en/accepted/adr-033.md
new file mode 100644
index 0000000..f128155
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-033.md
@@ -0,0 +1,206 @@
+---
+id: "adr-033"
+title: "Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy"
+slug: "033"
+subtitle: "Accepted"
+excerpt: "ADR-029 (Tier Coexistence as Permanent Design) declared that the protocol"
+author: "ontoref"
+date: "2026-05-27"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-033", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-029 (Tier Coexistence as Permanent Design) declared that the protocol
+admits tier-0, tier-1, and tier-2 simultaneously and indefinitely, with
+per-project tier choice recorded in `.ontoref/config.ncl::ops.tier`.
+Five Hard constraints anchor the property:
+- `no-forced-tier-migration` — silent/automatic tier change forbidden
+ - `tier-0-must-resolve-schemas` — lower-tier operability preserved
+ - `offline-first-default-stack` — no network requirement at any tier
+ - `additive-stack-no-breaking-tier-changes` — no API removal across tiers
+ - `tier-transition-requires-clean-migration-state` — guard against
+ transition with pending migrations
+The fifth constraint's enforcement primitive — `enforce_tier_transition_guard(old, new, pending_migrations)` — is implemented in
+`crates/ontoref-daemon/src/tier_guard.rs` (175 LOC, 8 passing unit tests).
+It is a pure function that returns `Ok(())` or
+`Err(TierGuardError::MigrationsPending)`. It is **not yet wired into any
+caller** because no caller exists: ADR-029 committed the constraint at
+the protocol-semantic level, deferring the operational materialisation
+to a follow-up ADR.
+This ADR is that follow-up. It defines the operational primitive that
+moves a project between tiers — what it is called, where it lives, what
+it does, and what guarantees it offers — without re-opening ADR-029's
+already-settled questions (the three tiers are permanent; transition is
+voluntary; migrations-clean is a precondition).
+CURRENT STATE OF THE SYSTEM ENTERING THIS DECISION
+- ontoref-el-proyecto (the piloto) runs at tier-2 / Phase Progressive
+ *empirically* — 10 catalogued ops, dispatch via inventory, witnesses
+ emitted, replay-deterministic — but does NOT declare
+ `ops.tier = 'Tier2` in its `.ontoref/config.ncl`. The tier is
+ inferred from artefact presence (`catalog/operations/*.ncl`,
+ `crates/ontoref-ops/`), not from a deliberate config statement.
+ - The 18 other onboarded projects (mirador, lian-build, vapora, …)
+ run at tier-0 by config-default (`#[serde(default)]` on `OpsConfig`
+ yields `Tier::Tier0`).
+ - No project today exercises the tier_guard's failure path because
+ no project has crossed a tier boundary since ADR-029 was accepted.
+OPEN QUESTIONS ENTERING THIS SESSION
+1. Op vs CLI vs both — where does `transition_tier` live?
+ 2. Direction asymmetry — what does downgrade actually do?
+ 3. TierGuard wiring point — at config-reload, at op pre-validation,
+ or at both?
+ 4. Pending-migrations definition — strict or scoped?
+ 5. Effect catalog — concrete list of substrate effects per direction.
+ 6. Atomicity — transactional or compensating?
+The decisions below resolve each open question through the ondaod-
+synthesis lens (formalization-vs-adoption, ontology-vs-reflection) and
+preserve the voluntary-adoption axiom invariant=true.
+
+Decision
+
+ONE PRIMITIVE, TWO SURFACES, ONE LOGIC
+The operational primitive is named `transition_tier`. It exists at two
+operational surfaces backed by a single Rust implementation:
+SURFACE A — CLI command (every tier)
+ `ontoref tier set <Tier0|Tier1|Tier2>`
+ Available at every tier including tier-0 (where the op dispatcher
+ is not present). Internally calls
+ `ontoref_daemon::tier_transition::execute(target_tier, ctx)`.
+SURFACE B — tier-2 catalogued operation
+ `catalog/operations/transition_tier.ncl` + Rust handler annotated
+ `#[onto_operation(id = "transition_tier", validation_sla = "Synchronous")]`.
+ Materialises automatically once a project reaches tier-2. Emits a
+ signed witness (Ed25519) covering the tier change. Calls the same
+ `ontoref_daemon::tier_transition::execute` function.
+The CLI is the universally-available path; the catalogued op is the
+witnessable path. Below tier-2 the CLI is the only surface; at tier-2
+both surfaces are available and BOTH route through the same Rust
+function. This preserves ondaod (formalization-vs-adoption Spiral stays
+open) by NOT forcing tier-0/1 projects to bootstrap a dispatcher to
+change tier, AND by NOT letting tier-2 projects bypass witness emission
+when they have the substrate to produce one.
+GUARD WIRING — single entry-point, two reachability paths
+`enforce_tier_transition_guard` fires at exactly one logical entry-point
+— inside `ontoref_daemon::tier_transition::execute` — regardless of
+surface. Both CLI and op converge on this function as the first
+validator. Additionally, the daemon's existing `ConfigWatcher` (which
+observes `.ontoref/config.ncl` changes) consults the same guard before
+swapping the config in its registry. The three reachability paths:
+a. CLI invocation → execute() → guard
+ b. Op dispatch → execute() → guard
+ c. Manual NCL edit + daemon reload → ConfigWatcher → guard
+If the guard returns `Err(MigrationsPending)`, the function returns
+without applying any effect; the daemon emits a `Block`-severity
+notification; the registry retains the old tier value; manual NCL
+edits are NOT swapped in (the daemon does not unilaterally overwrite
+the file — it logs the block and lets the developer revert their edit
+or `ontoref migrate` first).
+PENDING-MIGRATIONS DEFINITION — strict
+The guard treats `ontoref migrate pending` as authoritative. Any pending
+migration blocks any tier change. This matches the current
+`tier_guard.rs` implementation exactly and avoids introducing a new
+optional schema field (`relevant_to_tier_transition`) whose semantics
+would themselves be a Spiral collapse: the protocol cannot reliably
+classify which migrations affect tier mechanics without empirically
+testing every migration against every tier transition, and the cost of
+that classification exceeds the cost of running pending migrations
+first.
+DOWNGRADE — mark dormant, never delete
+`transition_tier` lowering `ops.tier` (e.g. Tier2 → Tier0) does NOT
+delete substrate artefacts. The behaviour matrix:
+| direction | NCL files | oplog | commit_layer | state_root | signing keys |
+ | ---------------- | --------- | ----- | ------------ | ---------- | ------------ |
+ | upgrade N→N+1 | preserved | created if absent | created if absent | initialised | regenerated if absent |
+ | downgrade N→N-1 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) |
+ | downgrade N→N-2 | preserved | preserved (read-only) | preserved (read-only) | frozen (last value retained) | preserved (read-only) |
+This is consistent with `ontoref-tier-downgrade-asymmetry` QA: past
+cryptographic guarantees survive immutable, future guarantees pause, and
+re-upgrade introduces an externally-observable epistemic gap in the
+witness chain (not a protocol bug — an inherent property of cryptographic
+chains).
+EFFECT CATALOG
+The function applies effects per direction in a fixed order, with each
+effect carrying a compensating rollback action (see ATOMICITY below):
+Tier0 → Tier1
+ e1. Create `.ontoref/store/oplog/` if absent
+ e2. Bootstrap commit_layer (write empty root commit if oplog empty)
+ e3. Compute initial state_root from NCL ingestion
+ e4. Register the project's substrate state in daemon registry
+ e5. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1` (atomic
+ file replace via tempfile + rename)
+Tier1 → Tier2
+ e1. Verify or create per-actor Ed25519 keypair
+ (`~/.config/ontoref/keys/<actor>.key`, 0600)
+ e2. Register the project's catalog by reading
+ `.ontoref/catalog/operations/*.ncl` (declarative side)
+ e3. Verify each declared op has a corresponding registered
+ OperationEntry (via `inventory::iter`) OR a declared non-Rust
+ kind per ADR-034
+ e4. Mutate `.ontoref/config.ncl::ops.tier = 'Tier2`,
+ `ops.phase = 'Progressive` (atomic)
+Tier2 → Tier1
+ e1. Emit a final tier-descent witness (Ed25519-signed envelope
+ describing the descent, written to oplog as the last
+ signed entry — the oplog remains valid for verifiers)
+ e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier1`
+ e3. Update daemon registry to stop dispatching ops for this project
+ (return 404 from `/ops/{id}` while preserving witness verifiability)
+Tier1 → Tier0
+ e1. Update daemon registry to stop maintaining the substrate
+ (commit_layer becomes read-only archive; new NCL edits are NOT
+ ingested into the commit_layer)
+ e2. Mutate `.ontoref/config.ncl::ops.tier = 'Tier0`
+Tier2 → Tier0 (compound descent)
+ Decomposed into (Tier2 → Tier1) followed by (Tier1 → Tier0),
+ applied atomically as one transaction. If e1 of the inner descent
+ succeeds but e2 of the outer descent fails, the WHOLE compound
+ rolls back. The function never leaves the project at an
+ intermediate tier value than the one declared.
+ATOMICITY — transactional, all-or-nothing
+The function applies all effects within a single transactional scope.
+Each effect has a recorded compensating action. On any failure:
+- The compensations execute in reverse order
+ - `.ontoref/config.ncl::ops.tier` is restored to its pre-transition
+ value (the file rename is the last effect; if it succeeds, the
+ transition committed)
+ - The daemon's in-memory registry is restored to its pre-transition
+ snapshot
+ - The failed transition is recorded in
+ `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` for
+ operator review (logged, never silently discarded)
+The transactional model means the user sees one of exactly two outcomes:
+the project is at the requested tier OR the project is at its original
+tier; never an intermediate state. Synchronous SLA on the catalogued
+op enforces this externally; the CLI surface has the same guarantee
+because it calls the same Rust function.
+ONTOREF-EL-PROYECTO DECLARES `ops.tier = 'Tier2`
+As part of this ADR — not a separate housekeeping step — ontoref's own
+`.ontoref/config.ncl` gains an explicit `ops = { tier = 'Tier2, phase
+= 'Progressive }` block. This makes the piloto self-host the FIRST
+project to formally declare its tier and the canonical example for
+every future declaration. The Hard constraint `piloto-declares-its-tier`
+(below) pins the declaration so future sessions cannot silently remove
+it.
+NEW SCHEMA FIELDS — none in this ADR
+`transition_tier` operates on the existing `OpsConfig` struct (tier +
+phase). It does not require new schema fields. Migration 0024
+introduces no breaking change — it (a) documents the new CLI command
+and op, (b) instructs ontoref-el-proyecto to add the explicit tier
+declaration, and (c) recommends consumer projects add their own
+declaration when they choose a tier.
+
+Constraints
+
+Hard The `transition_tier` operational primitive (CLI surface, catalogued op, and ConfigWatcher hook) MUST invoke `enforce_tier_transition_guard` from `crates/ontoref-daemon/src/tier_guard.rs` as its first validator. No alternative implementation of the precondition check is permitted; the guard is single-source.Hard The CLI surface (`ontoref tier set <T>`) and the tier-2 catalogued op (`transition_tier`) MUST both route through a single Rust function `ontoref_daemon::tier_transition::execute` (or its successor module). Neither surface may implement the transition logic independently.Hard Every invocation of `transition_tier` MUST produce exactly one of two observable outcomes: (a) the project is at the requested tier with all effects applied OR (b) the project is at the original tier with no effect persisted. The function MUST NOT leave the project at any intermediate tier value, and MUST NOT leave any partially-applied substrate state visible to subsequent reads.Hard Downgrade transitions (any direction where `new_tier < old_tier`) MUST NOT delete, truncate, or otherwise mutate the existing oplog, commit_layer, state_root snapshot, or actor signing key files. The substrate becomes read-only archive; new mutations follow the new tier's rules.Hard When `transition_tier` is invoked with `old_tier = 'Tier2` AND `new_tier != 'Tier2`, the function MUST emit a final Ed25519-signed witness covering the descent before mutating `.ontoref/config.ncl::ops.tier`. The witness payload kind is `tier_descent`; the proof path references the pre-descent state_root.Hard The ontoref repository's own `.ontoref/config.ncl` MUST contain an explicit `ops` section declaring `tier = 'Tier2`. The declaration MUST be a deliberate config statement, not inferred from artefact presence.Hard The daemon's ConfigWatcher MUST consult `enforce_tier_transition_guard` before swapping a project's `OpsConfig` in the registry when the watcher detects a change to `.ontoref/config.ncl::ops.tier`. When the guard returns `Err`, the watcher MUST log a `Block`-severity notification and retain the previous config; it MUST NOT mutate the user's NCL file to revert the change (only refuses to honour it).Hard The guard MUST treat any non-empty result from `ontoref migrate pending` as blocking. The function MUST NOT introduce per-migration opt-out fields (e.g. `relevant_to_tier_transition`) that would relax the constraint.Soft Every failed transition (whether blocked by the guard or by a downstream effect failure) MUST produce a record in `.ontoref/artifacts/transition-log/<timestamp>-failed.ncl` containing: the requested target tier, the original tier, the failure source, and the timestamp. The record MUST NOT contain sensitive substrate state (no private key material, no secret values).
+
+Alternatives considered
+
+CLI-only — `transition_tier` lives in `domains/framework/commands.nu` as a Nushell command calling daemon HTTP routes — rejected: Tier-2 projects need the act to be witnessable per ADR-024 ('operations are the agent's only path to mutate state, with declared preconditions, validators, and witness emission'). A CLI-only implementation either bypasses the catalog (anti-ADR-024) or duplicates witness emission logic outside the catalog (state inconsistency risk). Dual-surface with one-logic is the synthesis that preserves both surfaces' guarantees.Tier-2-op-only — only a catalogued op, no CLI surface — rejected: Tier-0/1 projects don't have the op dispatcher (the op runtime is part of the tier-2 substrate). They cannot climb without first bootstrapping the dispatcher — chicken-and-egg. CLI is the bootstrap surface that breaks this circularity. Removing it would make tier transitions impossible for the lowest tier, violating voluntary-adoption (a tier-0 project must be able to climb when it chooses to).Separate per-direction primitives — `upgrade_tier`, `downgrade_tier`, `set_phase` — rejected: Three primitives where one suffices. The function signature `execute(target_tier, ctx)` covers all directions; the effect catalog branches per direction internally. Splitting introduces three places to apply the guard, three CLI commands, three op declarations — without adding semantic clarity. The brief's `ontoref-tier-downgrade-asymmetry` QA already explained that downgrade has different effects than upgrade; that asymmetry lives inside the effect catalog, not in three separate primitives.Pending-migrations scoped by `relevant_to_tier_transition: true` field — rejected: Adding a per-migration boolean would require the protocol to pre-classify every future migration. The classification is itself a Spiral-collapse decision (which migrations 'matter' for tier mechanics is precisely what ADR-029's constraint refuses to pre-judge). Strict-any matches the current `tier_guard.rs` exactly, requires no schema change, and the operational cost (run migrations before transitioning) is the obvious workaround.Best-effort with rollback log — apply effects, log completions, manual cleanup on failure — rejected: ADR-024 makes operations the agent's only action path with witness emission per op. An op that partially applies state breaks the witness/state correspondence — the Ed25519 signature covers a state the verifier cannot reconstruct from the oplog. Transactional all-or-nothing is the only model compatible with witness-honesty. The cost (compensations per effect) is bounded and one-time.Delete substrate on downgrade — clean slate — rejected: Anti-ADR-029. Past witnesses are immutable per voluntary-adoption; deleting the oplog / signing keys would invalidate them from external verifiers' perspective. The `ontoref-tier-downgrade-asymmetry` QA explicitly states 'past witnesses verify forever against actors.ncl public keys' — that property requires keeping the keys and the oplog. Dormancy preserves the property at the cost of disk space; deletion breaks it.
+
+Related ADRs
+
+ADR-018 · ADR-023 · ADR-024 · ADR-025 · ADR-026 · ADR-029 · ADR-030 · ADR-032
diff --git a/site/site/content/adr/en/accepted/adr-033.ncl b/site/site/content/adr/en/accepted/adr-033.ncl
new file mode 100644
index 0000000..d31e4bd
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-033.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-033",
+ title = "Tier Transition Mechanism — Dual-Surface `transition_tier` with Transactional Effects and Witnessed Substrate Dormancy",
+ slug = "033",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/033",
+ graph = {
+ implements = [],
+ related_to = ["adr-018", "adr-023", "adr-024", "adr-025", "adr-026", "adr-029", "adr-030", "adr-032"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-035.md b/site/site/content/adr/en/accepted/adr-035.md
new file mode 100644
index 0000000..18231a3
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-035.md
@@ -0,0 +1,177 @@
+---
+id: "adr-035"
+title: "Positioning Layer — Marketing as Queryable Protocol Surface"
+slug: "035"
+subtitle: "Accepted"
+excerpt: "Ontoref's thesis is that everything project-defining is queryable NCL:"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-035", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Ontoref's thesis is that everything project-defining is queryable NCL:
+axioms, tensions, practices, ADRs, reflection modes, manifest capabilities,
+config surface, backlog, Q&A, search bookmarks, even the API catalogue
+(via the #[onto_api] proc-macro + inventory). The protocol pushes this
+discipline aggressively into every layer it touches.
+One surface escapes the discipline: the *outward-facing* artefacts of
+the project — the things that explain to a potential adopter, sponsor,
+or contributor *why they should care*. Today these live as:
+- card.ncl — a tagline + features array attached to ProjectCard.
+ Identity-shaped, audience-flat. There is no model of *whom* the
+ project speaks to, *what claim* is being made to each audience,
+ or *which architectural decisions back the claim*.
+- assets/web/ — index.html, personal.html, provisioning.html,
+ architecture-diagram.html. These are the public face of ontoref.
+ They are **hand-edited markdown-and-HTML**, disconnected from
+ the NCL substrate. Every other artefact in ontoref derives from
+ a typed source (ADRs from forms via Jinja, API pages from inventory
+ catalogue, describe outputs from ontology/manifest/qa). Public
+ messaging does not.
+- domains/{personal, framework, provisioning}/ — these are *implicit*
+ audience segments, modelled as technical extension verticals (per
+ ADR-012) rather than as go-to-market audiences. The mapping
+ "platform engineer ↔ provisioning domain" exists in the maintainer's
+ head, nowhere in NCL.
+The gap is real and visible. The user observation that opened this ADR:
+when ontoref reasons about *what to build next* (Wish/Idea backlog,
+ADR drafting, tension synthesis) it has rich machinery; when ontoref
+reasons about *who cares and why* it has card.ncl's tagline string.
+Marketing claims drift from architectural decisions; messaging copy
+ages independently of the substrate that justifies it; the protocol's
+own "everything is NCL" axiom is silently violated at exactly the
+surface where adopters meet the project.
+Two prior sessions have orbited this gap without closing it:
+- The PRD-vs-ADR question (this session, prior turn) — initially
+ framed as "should backlog Wish items carry a PRD block?". That
+ sketch (extend Item with prd field) addresses *internal* product
+ intent. It does not address the outward-facing positioning surface.
+- ADR-031's on+re duality — established that ontology (substance,
+ "what IS") and reflection (act, "what DOES") are constitutive,
+ not optional. Positioning artefacts live primarily on the reflection
+ axis (claims are acts of communication toward an audience) but are
+ *anchored* to substance via evidence_adrs (each claim cites the
+ ADRs that materially back it). Without this anchoring, claims drift
+ into pure narrative and become un-auditable.
+The positioning layer formalises the outward-facing surface as queryable
+NCL — completing the protocol's coverage of the project's externalised
+self — while keeping prose-as-prose via a narrative_path that points to
+markdown bodies. The skeleton is verifiable (audience present, evidence
+ADRs cited when Live, launch criteria executable); the flesh stays human.
+
+Decision
+
+A new layer `positioning` is added to the protocol surface, with three
+artefact kinds — **Audience**, **ValueProp**, **Campaign** — typed by
+schemas installed to the user data dir, and instantiated per-project
+under `.ontoref/positioning/`.
+SCHEMAS (installed to data dir, same install path as project-card.ncl)
+ontology/schemas/positioning.ncl
+ PositioningStatus | [| 'Draft, 'Validated, 'Live, 'Retired |]
+ LaunchCheck | tagged record (Grep | NuCmd | ApiCall | FileExists)
+ Audience | { id, name, description, primary_pain, context,
+ channels[], evidence[], linked_nodes[] }
+ ValueProp | { id, claim, audience_ids[], problem_solved,
+ alternatives[], unique_angle, evidence_adrs[],
+ proof_points[], narrative_path?, launch_criteria[],
+ status, retired_at?, superseded_by? }
+ Campaign | { id, title, audience_ids[], value_prop_ids[], goal,
+ channels[], launch_criteria[], narrative_path?,
+ status, starts_at?, ends_at? }
+ontology/defaults/positioning.ncl
+ make_audience, make_value_prop, make_campaign — apply cross-field
+ contracts:
+ - non_empty_audience_ids (value-prop must target someone)
+ - live_requires_evidence_adrs (Live claims must cite ≥1 ADR)
+ - retired_has_date (Retired requires retired_at)
+ - superseded_implies_retired (cannot supersede while Live)
+ - each_launch_check_well_formed (per-tag field requirements)
+CONSUMER LAYOUT (per-project, opt-in)
+my-project/
+ └── .ontoref/
+ └── positioning/ ← new layer root
+ ├── audiences/
+ │ ├── audience-platform-engineer.ncl
+ │ ├── audience-ai-agent-runtime.ncl
+ │ └── audience-architecture-team.ncl
+ ├── value-props/
+ │ ├── vp-001-structure-that-remembers.ncl
+ │ ├── vp-002-mcp-agent-discoverability.ncl
+ │ └── vp-003-decision-replay.ncl
+ ├── campaigns/
+ │ └── campaign-NNNN-slug.ncl
+ └── narratives/
+ ├── vp-001-self-knowledge.md ← prose for the claim
+ └── audience-platform-engineer.md
+NCL holds the verifiable skeleton (audience, evidence_adrs, launch_criteria,
+status). Markdown narratives hold the messaging body. Generated HTML in
+`assets/web/` derives from the pair via the same Jinja pattern used for
+ADRs.
+CARD.NCL INTEGRATION
+ProjectCard schema gains one optional field:
+ primary_value_prop_id | String | optional
+When present, it MUST resolve to a file at
+ .ontoref/positioning/value-props/<id>.ncl
+card.tagline is preserved (zero migration cost). When primary_value_prop_id
+ is set, downstream renderers (portfolio publication, web assets) MAY
+ prefer the canonical value-prop's claim over the static tagline.
+CHECK TYPE REUSE — ACKNOWLEDGED DUPLICATION
+positioning.LaunchCheck is structurally identical to the ConstraintCheck
+ type in `.ontoref/adrs/adr-schema.ncl` (tags: 'Grep, 'NuCmd, 'ApiCall,
+ 'FileExists; per-tag well-formedness). The duplication is intentional
+ in this ADR: lifting ConstraintCheck into a shared
+ `ontology/schemas/check.ncl` requires editing the ADR schema's import
+ graph (changing what every existing ADR resolves against), which is
+ out of scope for introducing a new optional layer.
+Lift-out is recorded as a soft constraint in this ADR
+ (shared-check-lift-out-pending) and deferred to a follow-up migration.
+ Until then, validators in both layers reference the same tags by name
+ and the test suite checks shape-equivalence on every CI run.
+ASSETS/WEB/ DERIVATION (out of scope but enabled)
+This ADR does not specify the asset generator. It establishes the
+ queryable source from which assets/web/index.html, personal.html, etc.
+ CAN be generated. A follow-up ADR (or migration) replaces hand-edited
+ HTML with Jinja-templated output reading the positioning tree + linked
+ narratives, the same way ADRs are generated from forms today. Until
+ that follow-up lands, assets/web/ remains hand-edited and the
+ positioning layer informs it via reference, not derivation.
+OPT-IN PER PROJECT
+positioning is a tier-orthogonal optional layer (consistent with
+ ADR-029's tier-coexistence design). A project at tier-0 with no
+ positioning/ directory operates exactly as before. Adoption is
+ declared by creating `.ontoref/positioning/` and ≥1 audience + ≥1
+ value-prop; the daemon's describe surface exposes positioning as a
+ category iff the directory is non-empty. No migration is required
+ for existing consumer projects; the layer is purely additive.
+CLI / DESCRIBE SURFACE
+ontoref describe positioning [--audience <id>] [--status Live]
+ → lists value-props (filtered) with their audience, evidence_adrs,
+ and narrative_path
+ontoref positioning validate
+ → runs launch_criteria checks across all value-props and campaigns;
+ reports pass/fail per artefact
+ontoref positioning audit
+ → cross-references evidence_adrs against the ADRs directory;
+ flags claims whose cited ADRs are 'Deprecated, 'Superseded, or
+ missing entirely (drift detection between marketing and architecture)
+
+Constraints
+
+Hard The positioning schema (ontology/schemas/positioning.ncl) and its defaults (ontology/defaults/positioning.ncl) MUST be installed to the user data dir as part of every ontoref install, alongside project-card.ncl and the other layer schemas. Without these files, consumer projects cannot resolve `import "positioning"` and the layer is structurally unavailable.Hard The positioning defaults file (ontology/defaults/positioning.ncl) MUST be installed alongside the schema. Cross-field contracts (live_requires_evidence_adrs, retired_has_date, etc.) are applied via the make_* helpers in this file — without it, value-prop files importing `defaults/positioning.ncl` fail to parse and the structural invariants the layer promises are unenforceable.Hard Audience records MUST live in their own files under `.ontoref/positioning/audiences/<id>.ncl`. Value-prop files MUST reference audiences by id (audience_ids: Array String), NOT inline the audience record. The `primary_pain` field is the canonical signature of an audience record — its presence inside a value-prop file is the violation.Hard When `card.ncl` declares `primary_value_prop_id = "vp-XXX-..."`, the referenced value-prop file MUST exist at `.ontoref/positioning/value-props/vp-XXX-....ncl`. Dangling references between identity (card) and positioning (value-props) silently break downstream renderers (portfolio, web pages, MCP surfaces).Hard Every value-prop with status='Live MUST declare a non-empty evidence_adrs array. This is enforced at nickel-export time by the live_requires_evidence_adrs contract in `ontology/defaults/positioning.ncl`. The Hard runtime check confirms the contract has not been bypassed (e.g. by editing exported JSON directly).Hard When a value-prop or campaign declares `narrative_path = "..."`, the path MUST resolve to an existing file. Markdown narratives outside the NCL ecosystem are referenced, not validated for content, but their existence is the minimum enforceable invariant.Soft The LaunchCheck type in `ontology/schemas/positioning.ncl` is structurally identical to ConstraintCheck in `.ontoref/adrs/adr-schema.ncl`. Both MUST be lifted into a shared `ontology/schemas/check.ncl` in a follow-up migration. Until that migration lands, the duplicated definitions MUST stay synchronised: any new tag added to one MUST be added to the other in the same commit.
+
+Alternatives considered
+
+Extend card.ncl monolithically with positioning fields (audiences, value_props, campaigns inline) — rejected: Mixes identity (what the project is — stable across years) with positioning (claims aimed at specific audiences — evolves per campaign). The schema becomes ambiguous: is ProjectCard.tagline the project's identity statement or its current value-prop claim? Inline arrays also lose the per-artefact reviewability that separate files give (each value-prop change is its own git diff, its own narrative file, its own launch_criteria). Card-as-monolith fails as soon as a project has more than one audience to address.Address only the internal PRD gap — extend backlog Item with a prd block (route A from the prior session turn) — rejected: Solves a different problem (internal product intent for the engineering team) and leaves the outward-facing surface — the one that adopters actually see — untouched. The prior session sketch is complementary, not substitutive: an engineering PRD captured in backlog and a marketing value-prop captured in positioning serve disjoint audiences (team vs. world). Choosing only the PRD route leaves assets/web/ disconnected from the protocol forever.Keep positioning as pure markdown under .ontoref/positioning/*.md — no NCL skeleton — rejected: Loses every property that justifies adding the layer: no evidence_adrs anchor (claims drift from architecture invisibly), no launch_criteria (no executable readiness check), no audience targeting (claims float free of who they address), no MCP queryability (markdown is opaque to structured tooling), no audit surface (drift between marketing and architecture is undetectable). Markdown-only positioning is what every project already has via README and blog posts — adding it to the protocol contributes nothing structural.Per-domain positioning — let each domains/{personal,framework,provisioning}/ carry its own positioning files — rejected: Couples positioning to the technical extension mechanism (domains) when in fact value-props frequently cross domains (a single claim like 'structure that remembers' targets multiple audiences across multiple domains). Per-domain positioning would either duplicate value-props across domain directories (drift risk) or require a cross-domain index that re-introduces the centralised positioning/ directory by another name. The centralised layer with audience_ids as the cross-cutting key is simpler and supports the cross-domain claim case natively.Defer the entire question — write hand-edited HTML now and revisit positioning later when adoption volume justifies it — rejected: The protocol's coherence with its own axiom (everything is NCL) is the value proposition — deferring its application to the outward-facing surface is precisely the pattern (substance-vs-act collapse) that ADR-031 was opened to prevent. Also: the longer the surface stays hand-edited, the more drift accumulates between claims and architecture, and the higher the cost of eventually adopting positioning. The opt-in design means deferral by individual consumers is already supported — what is rejected is the protocol itself deferring the schema.
+
+Anti-patterns
+
+Live Value-Prop Without Evidence ADRs — A value-prop is set to status='Live but evidence_adrs is empty — marketing is published without architectural backing. The live_requires_evidence_adrs contract catches this at nickel-export, but the anti-pattern is the maintainer reasoning that 'we can fill in evidence later'.Audience Record Inlined in Value-Prop — A value-prop file declares the audience inline (name, primary_pain, channels) instead of referencing it by id. Multiple value-props targeting the same audience drift independently.Marketing-Architecture Drift — A value-prop's evidence_adrs cites an ADR that has been superseded or deprecated — the claim is still Live, but its backing is no longer the active architectural decision. The marketing surface and the architecture diverge silently.
+
+Related ADRs
+
+ADR-001 · ADR-009 · ADR-012 · ADR-015 · ADR-024 · ADR-027 · ADR-029 · ADR-030 · ADR-031 · ADR-032
diff --git a/site/site/content/adr/en/accepted/adr-035.ncl b/site/site/content/adr/en/accepted/adr-035.ncl
new file mode 100644
index 0000000..c45bd01
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-035.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-035",
+ title = "Positioning Layer — Marketing as Queryable Protocol Surface",
+ slug = "035",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/035",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-009", "adr-012", "adr-015", "adr-024", "adr-027", "adr-029", "adr-030", "adr-031", "adr-032"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-036.md b/site/site/content/adr/en/accepted/adr-036.md
new file mode 100644
index 0000000..00cd50e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-036.md
@@ -0,0 +1,84 @@
+---
+id: "adr-036"
+title: "Tier Transition Substrate Effects — Deferred Scope from ADR-033"
+slug: "036"
+subtitle: "Accepted"
+excerpt: "ADR-033 (Tier Transition Mechanism) enumerated a six-effect catalog per"
+author: "ontoref"
+date: "2026-05-29"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-036", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-033 (Tier Transition Mechanism) enumerated a six-effect catalog per
+transition direction. ADR-033 §FULL EFFECTS implemented exactly one of
+those effects: the atomic config file replace (the last step of every
+direction). The other fifteen effects — oplog bootstrap, commit_layer
+initialization, state_root computation, daemon registry mutation,
+Ed25519 keypair generation, catalog verification, daemon registry
+de-registration, and tier-descent witness emission — were deferred
+because they require coordinated implementation across the workspace
+substrate crates (`ontoref-oplog`, `ontoref-commit`, `ontoref-ops`),
+which were still being built out — not because of any external
+dependency. The oplog is redb-backed and lives entirely in the
+workspace; no stratumiops crate is required to persist the witness
+chain.
+The Hard constraints `downgrade-preserves-substrate-immutable` and
+`tier-2-descent-emits-final-witness` from ADR-033 are documented as
+DEFERRED in the tier_transition module preamble. This ADR is that
+follow-up.
+CURRENT STATE ENTERING THIS DECISION
+Tier transition declaratively works via three reachability paths
+ (CLI, ConfigWatcher, catalogued op) that all converge on
+ `ontoref_ops::tier_transition::execute`. The execute function mutates
+ ONLY the config.ncl file atomically. No substrate is bootstrapped on
+ upgrade; no witness is emitted on descent; no oplog is created.
+Tier-2 today is therefore declarative-only — projects can declare
+ `ops.tier = 'Tier2` and the protocol honours the declaration, but
+ the ADR-024 promises of cryptographic provenance and replay-
+ deterministic operation logs remain unfulfilled at runtime.
+
+Decision
+
+ACCEPTED. The substrate effects are implemented behind a `substrate`
+feature flag on ontoref-ops (default: off), coordinated across
+ontoref-ops (effect handlers), ontoref-oplog (redb-backed oplog
+persistence), and ontoref-commit (commit_layer / state_root). The
+oplog persistence backend is redb, in-workspace — NOT stratumiops
+stratum-db. This ADR fixes the scope and the binding constraints; the
+implementation is tracked as backlog item bl-014.
+SHAPE OF THE NEXT SCOPE
+Substrate effects will be implemented behind a `substrate` feature
+ flag on ontoref-ops (default: off). When enabled, execute runs the
+ full effect catalog; when disabled, only the atomic config rewrite
+ runs — preserving ADR-029 offline-first-default-stack for tier-0
+ consumers.
+Per-actor Ed25519 keys: generated on first Tier1→Tier2 transition,
+ stored at ~/.config/ontoref/keys/<actor>.key (mode 0600), public
+ key published in .ontoref/actors.ncl.
+Oplog: append-only via ontoref-oplog (redb-backed, in-workspace),
+ mirrored as NCL render output for human inspection.
+Tier-descent witness: payload_kind = 'tier_descent', written as
+ the last signed entry of the project's oplog before the config
+ rewrite to a lower tier.
+Compensating rollback: each substrate effect registers its undo
+ into the transaction scope established by tier_transition::execute.
+ On any failure, compensations run in reverse order. Failures are
+ recorded in .ontoref/artifacts/transition-log/<ts>-failed.ncl
+ (already implemented in ADR-033 §FULL EFFECTS).
+
+Constraints
+
+Hard Substrate effects (oplog bootstrap, Ed25519 keypair, witness emission) MUST be gated behind a `substrate` feature flag on ontoref-ops. Builds without the feature MUST still produce a working `tier_transition::execute` that performs the atomic config rewrite. Preserves ADR-029 offline-first-default-stack.Hard When substrate is enabled and `tier_transition::execute` is invoked with old_tier=Tier2 AND new_tier!=Tier2, the function MUST emit a witness with payload_kind='tier_descent' to the project oplog before applying the config rewrite. Closes the deferred half of ADR-033 `tier-2-descent-emits-final-witness`.
+
+Alternatives considered
+
+Leave the gap as implicit Rust source TODOs — rejected: Implicit TODOs are not queryable. ADR-031 on+re duality requires protocol gaps to live on the ontology axis. Cost of this ADR is small; cost of every consumer rediscovering the gap grows linearly with the consumer count.Implement substrate effects directly in ADR-033 §FULL EFFECTS instead of splitting — rejected: ADR-033 §FULL EFFECTS was a self-contained PR-sized scope: tier_transition module + tests + CLI surface + watcher + holder + handler. Including substrate would have ballooned the scope across multiple substrate crates and pushed the ship date indefinitely. Splitting lets ADR-033 ship its value (working ore tier set + guard + tests) immediately.Persist the oplog via stratumiops stratum-db — rejected: An earlier draft of this ADR assumed stratum-db for oplog persistence. The implemented `ontoref-oplog` is redb-backed and self-contained in the workspace, enforcing signature + parent-existence + monotonic-HLC checks on every append. Using stratum-db would reintroduce an external path dependency for tier-2, contradicting ADR-001's minimal-adoption posture and ADR-029's offline-first default. redb keeps the witness chain dependency-free.
+
+Related ADRs
+
+ADR-023 · ADR-024 · ADR-029 · ADR-031 · ADR-033
diff --git a/site/site/content/adr/en/accepted/adr-036.ncl b/site/site/content/adr/en/accepted/adr-036.ncl
new file mode 100644
index 0000000..d8436d5
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-036.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-036",
+ title = "Tier Transition Substrate Effects — Deferred Scope from ADR-033",
+ slug = "036",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/036",
+ graph = {
+ implements = [],
+ related_to = ["adr-023", "adr-024", "adr-029", "adr-031", "adr-033"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-037.md b/site/site/content/adr/en/accepted/adr-037.md
new file mode 100644
index 0000000..62ed14c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-037.md
@@ -0,0 +1,107 @@
+---
+id: "adr-037"
+title: "Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding"
+slug: "037"
+subtitle: "Accepted"
+excerpt: "An agent or human session produces a stream of reasoning, decisions, file"
+author: "ontoref"
+date: "2026-05-28"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-037", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+An agent or human session produces a stream of reasoning, decisions, file
+touches, and outcomes. Today that stream survives only as prose: the
+transcript, or at best a `.coder/` markdown entry whose substance lives in a
+free-text `content` field (coder.ncl::Record). Extracting "what decisions did
+we make across the last ten sessions", "which sessions touched the validator
+layer", or "which decisions are ADR candidates" means mining prose by hand —
+exactly the cost the rest of the protocol was built to eliminate. Every other
+project-defining artefact in ontoref is queryable NCL; the record of what an
+agent actually DID in a session is not.
+The opening framing of this decision was stronger than what was adopted: make
+the agent emit its interaction in a *typed and validated* language — a contract
+over the reasoning itself. That framing was consciously narrowed. The goal is
+not to validate reasoning (undecidable, and an invitation to schema theatre
+where agents fill fields to satisfy a contract rather than to record signal);
+the goal is for the output to be *parseable and filterable* so a review need
+not escarbar through prose. The distinction is load-bearing and drives every
+constraint below.
+Three adjacent decisions touch this surface but none owns it:
+- ADR-024 (operations layer — agent action boundary) governs the *act*: the
+ operation an agent invokes and the witness it emits. It says nothing about
+ the agent's narration of the act.
+ - ADR-026 (validation architecture — three planes) defines structural vs
+ contextual validators. It is the plane this decision lives in, but it does
+ not define an artefact for agent interaction.
+ - ADR-031 (on+re constitutive duality) establishes the witness as the seam
+ where reflection-axis acts bind to ontology-axis substance. This decision
+ asks whether the agent's *interaction record* sits on that seam — and the
+ answer is: optionally.
+The user's reframe added a second property: the record need not relate to the
+witness at all. Absent a witness it still serves a review without deep digging.
+That makes the witness binding optional, which in turn makes the layer usable
+at every tier with different uses, rather than gated behind operations (tier-2).
+
+Decision
+
+Introduce the InteractionRecord — a parse-first trace of an agent/human
+session segment, stored as JSONL (one record per line), shipped as the
+reflection schema `reflection/schemas/interaction.ncl` (migration 0028).
+DESIGN PROPERTIES (each is a constraint below):
+1. Parse-first, not prose-analytic. Every dimension a review filters on is
+ an enum or a typed array (events[], decisions[], actions[], insights[],
+ files_touched[], tensions_engaged[], outcome). Free prose is confined to
+ three fields — `summary`, `Decision.rationale`, `Event.note` — and those
+ fields are NEVER a filtering axis. `jq` and `nu where` answer review
+ questions without reading the prose.
+2. Structural-only validation. The validator (ADR-026 structural plane)
+ checks shape: required fields present, enum members in their allowed set,
+ arrays shaped correctly. It MUST NOT inspect the truthfulness of the free
+ prose. Asking a validator to certify that a rationale is honest or a
+ summary is faithful is a Hard biconditional on a question core.ncl names
+ as a Spiral tension — an ondaod forbidden pattern. Validation of prose
+ content is therefore explicitly out of scope, permanently.
+3. Optional witness binding — the layer is tier-orthogonal. The `witness`
+ field (op_id, state_root, signature) is optional. Absent → tier-0 review
+ aid (no substrate required). Present → tier-1/2, where op_ref on events
+ and actions links the record to witnessed substrate acts; the record
+ becomes the reflection-side companion of those acts. This is the ADR-031
+ seam, made optional so the layer is not gated behind operations adoption.
+4. Vocabulary reuse, not forking. The schema reuses coder.ncl's canonical
+ enums (ActorKind, Kind, Domain, optional Category) via `import "coder.ncl"`
+ rather than redeclaring parallel vocabularies that would drift. The
+ runtime validator parses enum members FROM the schema files at runtime, so
+ the allowlist can never diverge from the contract.
+5. Two emission paths, both supported. `ore interaction record` validates a
+ record structurally before appending (the forced path; malformed records
+ are rejected). Direct JSONL append by the agent is also permitted (the
+ free path), audited post-hoc by `ore interaction validate` (pre-commit /
+ CI gate). The `--no-validate` flag bridges the two on the same command.
+6. Decisions materialise the adr? criteria as data. `Decision` carries
+ `alternatives_rejected`, `reversible`, `adr_candidate`, and
+ `tensions_engaged` (core.ncl node ids). `ore interaction decisions
+ --adr-candidates` surfaces ADR candidates across sessions in one query,
+ feeding the ADR lifecycle from observed decisions rather than recollection.
+The layer is opt-in per ADR-029: a project that never emits interaction JSONL
+remains valid and pays zero adoption cost.
+
+Constraints
+
+Soft Validation of InteractionRecord MUST remain structural (shape, enum membership, array shape) and MUST NOT inspect the truthfulness or quality of the free-prose fields (`summary`, `Decision.rationale`, `Event.note`). Adding a Hard check that gates on prose content is forbidden — it is a biconditional on a Spiral tension (reality-vs-intent) per ondaod.Hard The `witness` field on InteractionRecord MUST remain optional. Making it required gates the layer behind tier-2 (operations) adoption and discards the tier-0 review-aid use. The optional binding is what makes the layer tier-orthogonal per ADR-029.Hard The InteractionRecord schema MUST reuse coder.ncl's canonical vocabularies (ActorKind, Kind, Domain, Category) via `import "coder.ncl"` rather than redeclaring parallel enums. The runtime validator MUST derive enum membership from the schema files, not hardcode it.Hard The interaction schema (reflection/schemas/interaction.ncl) MUST be shipped to consumer data dirs as part of the reflection/ tree by install.nu, so adopting projects can resolve it. Migration 0028 is its propagation mechanism.Soft Free prose in InteractionRecord MUST stay confined to `summary`, `Decision.rationale`, and `Event.note`. New filterable facets MUST be added as enums or typed arrays, never as free-text fields that a review would have to parse. The schema marks `summary` as the only free-prose top-level field.
+
+Alternatives considered
+
+Typed AND validated contract over the agent's reasoning (the opening framing) — rejected: Validating reasoning is undecidable and invites schema theatre: agents fill fields to satisfy the contract, not to record signal — the exact H7 dysfunction ADR-024 names. A Hard contract over prose truthfulness is also a biconditional on a Spiral question (reality-vs-intent), an ondaod forbidden pattern. The adopted design keeps the typing for parseability and drops the validation of content.Extend coder.ncl::Record instead of a new schema — rejected: Record's substance is a free-text `content` field — it is the prose artefact this layer is meant to complement, not the parse-first one. Bolting events/decisions/actions arrays onto Record would overload one type with two opposed purposes (prose memory vs structured trace) and muddy `coder triage`/`coder record` semantics. A sibling schema that REUSES Record's enums keeps both coherent and distinct.Require the witness — bind every interaction record to the substrate — rejected: Gates the layer behind operations adoption (tier-2) and discards the user's explicit requirement that the record serve a review even with no witness present. Optional binding delivers the tier-0 review-aid use AND the tier-2 witnessed-companion use from one schema; mandatory binding delivers only the latter.Store instances as per-record NCL files validated by nickel-export — rejected: NCL enums are tags ('AgentClaude), JSONL stores them as strings — applying the NCL contract to imported JSON fails on every enum field, and per-file NCL is the wrong shape for streaming filters at scale. JSONL + a structural Nushell validator (that reads enum members from the NCL schema) is the correct split: NCL is the contract, JSONL is the queryable instance store.Validate the free-prose fields with heuristics (length, keyword presence, sentiment) — rejected: Any check that gates on prose content is a proxy for truthfulness that the layer explicitly disclaims. Heuristic prose checks produce false confidence (a record passes because it is verbose, not because it is honest) and re-open the Spiral collapse this ADR closes. Prose quality stays a human review concern.
+
+Anti-patterns
+
+Validating the Content of Free Reflection — A contributor adds a check that gates on the content of `summary`, `Decision.rationale`, or `Event.note` — length thresholds, required keywords, sentiment, or a claim that the rationale 'must be honest'. This is a biconditional on the reality-vs-intent Spiral and produces false confidence: a record passes because it is verbose, not because it is truthful.New Filterable Facet Added as Free Text — A new dimension a review wants to filter on (e.g. 'severity', 'subsystem') is added as a free-text field instead of an enum or typed array. Reviews must then string-match prose, re-introducing the prose-mining cost the layer exists to eliminate.Parallel Enum Vocabulary for Sessions — InteractionRecord redeclares its own ActorKind/Kind/Domain enums instead of importing coder.ncl, or the runtime validator hardcodes the allowlist. The two vocabularies drift the first time one is extended, and the structured trace silently disagrees with the prose Record about what an actor or kind is.
+
+Related ADRs
+
+ADR-024 · ADR-025 · ADR-026 · ADR-029 · ADR-031 · ADR-032
diff --git a/site/site/content/adr/en/accepted/adr-037.ncl b/site/site/content/adr/en/accepted/adr-037.ncl
new file mode 100644
index 0000000..732eca5
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-037.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-037",
+ title = "Interaction Trace — Parse-First Session Records with Structural-Only Validation and Optional Witness Binding",
+ slug = "037",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/037",
+ graph = {
+ implements = [],
+ related_to = ["adr-024", "adr-025", "adr-026", "adr-029", "adr-031", "adr-032"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-038.md b/site/site/content/adr/en/accepted/adr-038.md
new file mode 100644
index 0000000..94bf048
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-038.md
@@ -0,0 +1,103 @@
+---
+id: "adr-038"
+title: "OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer"
+slug: "038"
+subtitle: "Accepted"
+excerpt: "Until now ontoref could only be installed from a source checkout: `just"
+author: "ontoref"
+date: "2026-05-29"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-038", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Until now ontoref could only be installed from a source checkout: `just
+install-daemon` runs `cargo build --release` then `install/install.nu`, which
+lays out the binary, the bootstrapper, the CLI wrapper (with ONTOREF_ROOT
+baked), and the data layer (reflection/, ontology/, domains/, templates/) across
+the platform bin/data/config dirs. There was no way to install on a machine that
+does not have the source, and no way to run the daemon as a container.
+Two install paths are wanted, sharing ONE artifact origin in the OCI registry
+(`reg.librecloud.online`, already declared in manifest.ncl registry_provides):
+1. A runnable multi-arch image — `docker run …/ontoref/ontoref-daemon:VERSION`
+ — for deploying the daemon as a service/cache.
+ 2. `curl … | sh` — an installer that detects OS/arch, pulls the matching OCI
+ bundle (binary + data layer + wrappers), extracts it, and installs into a
+ chosen prefix, reproducing install.nu's layout.
+The mechanism is NOT a pile of standalone scripts. The workflow layer (the
+NCL-first CI/build/distribution model) already had the exact seam: `build_kind`
+carried 'Container, `distribution_kind` carried 'ContainerRegistry | 'Package |
+'Artifact, and the `distributions` catalog was empty with no generator. The
+infrastructure was also already declared: `oras`, `cosign`, `sops`, `age` are
+Hard dependencies in manifest.ncl, and ADR-017 models the registry credential
+vault. So the coherent move was to populate the workflow layer's distribution
+catalog and add the missing distribution generator, not to invent a parallel
+packaging path.
+The load-bearing risk is the "Protocol, Not Runtime" axiom: shipping a runnable
+daemon image could be read as making ontoref a runtime dependency. The framing
+that resolves it is ADR-029 (tier coexistence): the daemon is a cache, not a
+hard dependency. The installer therefore installs a CLI + data layer that work
+WITHOUT the daemon; the image is an optional accelerator for those who want the
+service.
+
+Decision
+
+Distribute ontoref through the OCI registry along two paths from one build,
+modeled entirely in the workflow layer (catalog in reflection/defaults/workflow.ncl,
+a `release` layer triggered OnTag in ontology/workflow.ncl, and a distribution
+generator in reflection/modules/workflow.nu). Migration 0029 propagates the
+schema/catalog additions to consumers.
+DESIGN PROPERTIES (each is a constraint below):
+1. Modeled in the workflow layer, not hand-maintained scripts. The 'Bundle
+ build kind, the build/distribution catalog entries, and the generator that
+ emits .woodpecker/release.yml + install/install.sh + justfiles/release.just
+ all live in the NCL workflow model. Distribution is therefore self-describing
+ and queryable via `ore workflow`, like every other build/CI artifact.
+2. Build-once; the image is assembled FROM the binary, never recompiled. The
+ musl binary is cross-compiled once per arch (`cross`). Both the install
+ bundle AND the runnable image consume that same binary. The image is
+ assembled with buildah from the cross output + the data layer — no
+ `docker buildx`, no QEMU, no docker-in-docker on the runner.
+3. The daemon image is an OPTIONAL accelerator (ADR-029). The curl|sh installer
+ installs a CLI + data layer that operate without the daemon. The runnable
+ image serves container deployments only. Adopting ontoref never requires
+ running the daemon — the "Protocol, Not Runtime" axiom is preserved.
+4. The bundle reproduces install.nu's layout. assemble-bundle.nu packages the
+ binary, bootstrapper, CLI wrapper, a target-arch nickel, the data layer, and
+ a config skeleton; install.sh places them exactly as install.nu does,
+ including the baked ONTOREF_ROOT in the wrapper. The bundle's config skeleton
+ is taken ONLY from install/resources/ — never from .ontoref/config.ncl.
+5. curl|sh needs nothing but POSIX tools. Bundles are .tar.gz (gzip is
+ universal; zstd is not assumed). install.sh prefers `oras` and falls back to
+ plain `curl` against the OCI Distribution API; it verifies the .sha256
+ sidecar and, if present, a cosign signature. The bundle namespace
+ (ontoref/dist) is published for anonymous pull, so no credentials are needed.
+ The installer itself is served at a plain HTTPS URL — never as an oras
+ artifact, which a bare `curl | sh` could not fetch.
+6. A publish gate. The `smoke-bundle` validation extracts the freshly-assembled
+ native bundle and runs the daemon binary before any distribution step
+ publishes. A broken artifact fails the pipeline rather than reaching users.
+7. Supply chain. The image is cosign-signed and the SBOM cosign-attested. Since
+ the self-hosted Woodpecker has no Fulcio, signing is key-based; the cosign
+ key and the registry RW credential come from the src-vault (ADR-017).
+Platform scope is Linux amd64 + arm64 (static musl). macOS has no published
+build — install.sh detects Darwin and prints source-build / container guidance.
+
+Constraints
+
+Hard OCI distribution MUST be declared in the workflow layer — build/distribution catalog entries in reflection/defaults/workflow.ncl and the generator in reflection/modules/workflow.nu — and the published artifacts (.woodpecker/release.yml, install/install.sh, justfiles/release.just) MUST be generated from it, never hand-maintained. The `distributions` catalog MUST be non-empty.Hard The installer MUST install a CLI + data layer that operate WITHOUT the daemon. The runnable daemon image MUST remain an optional accelerator. Making the daemon a required runtime for adoption is forbidden — it would violate the Protocol-Not-Runtime axiom (ADR-029: the daemon is a cache, not a hard dependency).Hard The bundle and installer MUST reproduce install.nu's layout: the binary, bootstrapper, CLI wrapper with a baked ONTOREF_ROOT, a bundled nickel, the data layer, and a config skeleton, placed in the same bin/data/config locations. Divergence silently breaks the installed CLI.Hard The published runnable image MUST be assembled from the already cross-compiled musl binary (buildah copy), NOT recompiled in CI and NOT built with docker buildx/QEMU. The bundle and the image MUST consume the same per-arch binary.Hard The bundle's config skeleton MUST be taken only from install/resources/ — never from the live .ontoref/config.ncl. A corrupt or environment-specific project config must never leak into a published bundle.Soft The bundle namespace (ontoref/dist) SHOULD be published for anonymous pull so the curl|sh installer needs no registry credentials. The installer script itself MUST be served at a plain HTTPS URL, not as an oras artifact.
+
+Alternatives considered
+
+Plain GitHub/Gitea-release tarballs, no OCI — rejected: ontoref already standardizes on OCI: oras/cosign/sops are Hard deps and the registry topology is declared in manifest.ncl (ADR-017). Plain tarballs would need a separate hosting and integrity story and would not reuse the existing registry credentials or signing. The installer keeps a curl fallback for hosts without oras, but the artifact origin stays OCI.Extract the binary + data from the runnable image's layers — rejected: Couples the installer to the image's internal layer layout — a fragile contract that breaks whenever the image build changes. Dedicated per-arch bundle artifacts are arch-addressable (:VERSION-linux-amd64), robust to image changes, and let the bundle carry exactly the install layout without the image's runtime concerns.docker buildx multi-arch (recompile or QEMU emulation) — rejected: Duplicates the cross build and requires QEMU/binfmt and docker-in-docker on the runner. Since the musl binaries are already cross-compiled, assembling the image from them with buildah is single-origin, faster, and daemonless on the runner. buildx would rebuild what cross already produced.Standalone install.sh + Dockerfile, not modeled in the workflow layer — rejected: Distribution would not be self-describing or queryable and would diverge from the PAP — every other build/CI artifact is generated from the NCL workflow model. The DistributionSpec/'Container kinds exist precisely so distribution is declared, not scripted. Hand-maintained scripts drift from the model.Bundle nushell into the artifact too — rejected: Nushell is large and is the CLI's runtime; bundling it bloats every download. nickel (small, static musl, needed by the bootstrapper) IS bundled; nushell is detected at install time and the user is given install guidance. The daemon image needs neither — only the daemon binary + nickel + data.
+
+Anti-patterns
+
+Treating the Daemon Image as a Required Runtime — A change makes the CLI or adoption depend on a running daemon — e.g. the installer starts/expects the daemon, or commands fail without it. This turns the optional accelerator into a runtime dependency and breaks Protocol-Not-Runtime.Editing the Generated Distribution Artifacts Directly — A contributor edits .woodpecker/release.yml, install/install.sh, or justfiles/release.just by hand instead of changing the workflow catalog and regenerating. The artifacts drift from the NCL model that is supposed to be their source of truth.Recompiling to Build the Multi-Arch Image — The image is built by recompiling per arch (docker buildx + QEMU, or cargo inside the Dockerfile) instead of assembling from the already cross-compiled binary. This duplicates the build, reintroduces emulation/docker-in-docker, and can make the image binary differ from the bundle binary.Shipping the Live Project Config in the Bundle — The assembler copies .ontoref/config.ncl (environment-specific, possibly corrupt) into the bundle instead of the install/resources skeleton, leaking local state into a published artifact.
+
+Related ADRs
+
+ADR-001 · ADR-010 · ADR-017 · ADR-029 · ADR-032 · ADR-035
diff --git a/site/site/content/adr/en/accepted/adr-038.ncl b/site/site/content/adr/en/accepted/adr-038.ncl
new file mode 100644
index 0000000..a0bffa1
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-038.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-038",
+ title = "OCI Distribution and Installer — Multi-Arch Runnable Image plus curl|sh Bundles, Modeled in the Workflow Layer",
+ slug = "038",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/038",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-010", "adr-017", "adr-029", "adr-032", "adr-035"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-039.md b/site/site/content/adr/en/accepted/adr-039.md
new file mode 100644
index 0000000..2c11b18
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-039.md
@@ -0,0 +1,113 @@
+---
+id: "adr-039"
+title: "Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog"
+slug: "039"
+subtitle: "Accepted"
+excerpt: "Projects accumulate many just recipes (and loose scripts). ontoref already"
+author: "ontoref"
+date: "2026-05-29"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-039", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+Projects accumulate many just recipes (and loose scripts). ontoref already
+INVENTORIES recipes — describe.nu::scan-just-recipes runs `just --list` and
+categorizes each recipe by a name-prefix heuristic (categorize-recipe), and
+`ore describe tools` lists them. But the inventory is shallow: it knows a
+recipe's name, its trailing `#` comment, and a guessed bucket. It cannot answer
+"which is the canonical way to run all checks", "show me the deploy tasks", or
+"what does build-standalone do and why" — because the recipe carries no declared
+INTENT and no real categorization, only a prefix guess.
+Two adjacent layers already exist and must not be duplicated:
+- Recipes (justfiles) are the MECHANISM layer — the concrete command. Single
+ source of truth for HOW.
+ - Modes (reflection/modes/*.ncl) are the PROCEDURE layer — multi-step gated
+ DAGs with actors/steps/depends_on, executed by `ore run`.
+What is missing is not a third store ("devtask" is a target-side abstraction,
+not ontoref vocabulary). What is missing is for a recipe to SELF-DESCRIBE — to
+declare its intent and categorization in place — exactly as Rust handlers do
+with #[onto_api(method, path, description, tags)]: the annotation sits next to
+the code, a proc-macro parses it, and inventory aggregates a zero-cost catalog
+that `ore describe api` / GET /api/catalog / MCP all read. The same shape is
+wanted for recipes: an annotation next to the recipe, parsed by ontoref, that
+makes justfiles queryable.
+The qa routing fix in the same session (ore qa show/list/search/add) added the
+seeding primitive — a tag-categorized, nickel-validated write path. The question
+this ADR settles is the GRAMMAR and the BOUNDARIES of seeding/enriching the tool
+surface: how an annotation is written, how it is mutated, and how a project that
+already has its own justfiles adopts compliance without losing its recipes.
+The load-bearing tension is the named Spiral `formalization-vs-adoption`: richer
+formalization (typed annotations on every recipe) buys ecosystem visibility but
+raises adoption cost. The tension's own resolution in core.ncl is "schemas are
+optional layers, not mandatory gates" — which constrains this design directly.
+
+Decision
+
+Make justfiles self-describing through a typed comment annotation, expose a
+transversal `+`/`-` mutation verb to write/remove annotations and other store
+entries, and ship a NON-DESTRUCTIVE compliance/migration mechanism so projects
+adopt the surface while keeping their own recipes. Migration 0030 propagates the
+grammar, the recommended mode taxonomy, and the new surface to consumers.
+DESIGN PROPERTIES (each is a constraint below):
+1. Annotation grammar — `# @onto key=val ...`. A single comment line directly
+ above the recipe carries typed metadata:
+# Run all CI checks locally
+ # @onto mode=ci labels=ci,gate intent="all checks before push"
+ ci-full:
+ ...
+Keys: `mode` (one token — the domain category: tools|dev|build|dev-build|
+ dply-build|test|docs|dist|secrets|…), `labels` (csv — free tags), `intent`
+ (quoted free text — the "best way to X" sentence the inventory cannot
+ derive). The recipe name and its plain `#` doc comment remain unchanged;
+ `@onto` enriches, it does not replace.
+2. The recipe is the single source of truth. Annotations live IN the justfile,
+ above the recipe — never in a parallel devtasks store. ontoref parses them
+ (scan-just-recipes gains an @onto parser) and DERIVES a catalog, exactly as
+ #[onto_api] derives artifacts/api-catalog-*.ncl. `ore tool export` writes the
+ derived catalog; `ore describe tools --mode <m> --label <l>` reads it.
+3. Mutation via a transversal verb. `ore + <noun> <args>` upserts and
+ `ore - <noun> <args>` removes, across stores:
+ore + tool ci-full --mode ci --labels ci,gate --intent "all checks"
+ ore - tool ci-full # removes the @onto line only
+ ore + qa "<q>" "<a>" --tags devtool,ci # the qa add path
+ ore - qa <id>
+`+ tool` / `- tool` edit the `@onto` comment ABOVE the named recipe in
+ place, validated against `just --list`; they NEVER touch the recipe body.
+ `+`/`-` are first-level dispatcher tokens taking `<noun> <args>`, so new
+ stores plug in without new top-level verbs.
+4. Annotations are OPTIONAL enrichment, never a gate. An un-annotated recipe
+ stays valid, runnable, and still inventoried by name+category (today's
+ behavior). Annotation raises a recipe from guessed to declared; it is never
+ required to run a recipe. This is the `formalization-vs-adoption` balance
+ made literal — "optional layers, not mandatory gates".
+5. Non-destructive compliance/migration. A `compliant` mode (and `ore tool
+ migrate`) scans a project's existing justfiles, proposes `@onto` annotations
+ (seeding `mode` from categorize-recipe + name, leaving `intent` for the
+ author), and — opt-in — reorganizes recipes into justfiles/<mode>.just. It
+ MUST preserve every project-owned recipe and its semantics: no recipe is
+ deleted or renamed, reorg is opt-in and reversible, and the project's own
+ recipes are first-class (the mechanism annotates them, it does not supplant
+ them with ontoref-blessed ones).
+6. Drift-checkable round-trip. `ore tool audit` reports orphan annotations
+ (an `@onto` whose recipe is gone from `just --list`) and, at compliance
+ level, un-annotated recipes. Mirrors docs-drift: the annotation and the
+ recipe must stay in sync because they are co-located.
+A multi-step task that deserves gating/steps graduates to a MODE, not an
+annotation; `@onto` is for single-recipe intent+categorization. Modes and
+annotated recipes are complementary, not competing.
+
+Constraints
+
+Hard A recipe in a justfile is the single source of truth for its command. @onto metadata MUST live in a comment co-located with the recipe; there MUST NOT be a parallel store of recipe command strings. Any tool catalog is DERIVED from the annotations and MUST be regenerated, not hand-edited.Hard The annotation MUST be a single comment line `# @onto key=val ...` immediately above the recipe. Recognized keys: `mode` (one token), `labels` (comma-separated), `intent` (double-quoted free text). Unknown keys MUST be ignored, not error. The recipe name and its plain `#` doc comment MUST be left intact.Hard Annotation/entry mutation MUST go through `ore + <noun> <args>` (upsert) and `ore - <noun> <args>` (remove). For noun `tool`, the verb edits/removes ONLY the @onto comment above the named recipe, validated against `just --list`, and MUST NOT modify the recipe body. New stores MUST be added as nouns, not as new top-level verbs.Hard An un-annotated recipe MUST remain valid, runnable, and inventoried by name+category. Annotation MUST NOT be a precondition for running or listing a recipe. Compliance is reported by audit, never enforced as a gate.Hard The compliance/migration mechanism MUST NOT delete or rename any project-owned recipe. It annotates in place and may propose reorganization into justfiles/<mode>.just only as an opt-in, semantics-preserving, reversible step. A project's own recipes are first-class and MUST survive migration unchanged in behavior.Soft `ore tool audit` MUST report orphan annotations (an @onto whose recipe is absent from `just --list`) and, at compliance level, un-annotated recipes. Every @onto MUST reference an existing recipe.Soft The recommended `mode` taxonomy (tools/dev/build/test/ci/deploy/docs/dist/secrets/…) is advisory guidance, not an enforced enum; `labels` are free-form. Validation MUST NOT reject an unrecognized mode or label.
+
+Alternatives considered
+
+A standalone devtasks.ncl store holding command strings + labels — rejected: Third source of truth alongside justfiles and modes; the stored command drifts from the real recipe, and it re-imports the loose-script problem. The recipe must stay the single source; the annotation co-locates metadata with it.Promote every dev/deploy recipe into a full NCL mode — rejected: Modes are the procedure layer for multi-step gated DAGs. Forcing single-command recipes through mode authoring is heavy and collapses the mechanism/procedure distinction. Multi-step tasks DO graduate to modes; single-recipe intent belongs in an annotation.Make annotations mandatory (a compliance gate that fails un-annotated recipes) — rejected: Collapses the named `formalization-vs-adoption` Spiral toward the Yang pole, contradicting its own resolution ('optional layers, not mandatory gates'). Annotations must enrich an always-valid baseline; audit reports compliance, it does not block.Glued verb `+tool` / per-store `tool add` instead of a transversal `+ <noun>` — rejected: Per-store verbs do not project to new stores without new top-level commands, and `+tool` glued is a one-off. A transversal `+ <noun> <args>` is the uniform, extensible idiom the user asked for.Destructive reorg — ontoref owns justfiles/ and rewrites them to a canonical layout — rejected: Adoption-hostile: projects have their own recipes and conventions. Compliance must be non-destructive — annotate in place, propose reorg opt-in, never delete or rename a project's recipes.
+
+Anti-patterns
+
+A Standalone Devtasks Store Duplicating Recipe Commands — A parallel devtasks.ncl stores copied command strings + labels alongside the justfiles. It becomes a third source of truth that drifts from the real recipe and re-imports the loose-script problem.Making Annotations Mandatory / Gating on Them — Compliance fails un-annotated recipes or blocks running them. This collapses the formalization-vs-adoption Spiral toward Yang, contradicting its own 'optional layers, not mandatory gates' resolution.+/- Editing the Recipe Body or Destroying Project Recipes — `+ tool` rewrites the recipe command, or migration deletes/renames a project-owned recipe. Adoption-hostile; violates recipe-as-single-source and non-destructive-migration.Hand-Editing the Derived Tool Catalog — Someone edits the generated tool catalog artifact directly. It is derived from the annotations and overwritten on regeneration, so the edit drifts from source.Forcing Single-Command Recipes Through Mode Authoring — Every dev/deploy recipe is promoted to a full NCL mode. Modes are for multi-step gated DAGs; over-applying them collapses the mechanism/procedure distinction and burdens adoption.
diff --git a/site/site/content/adr/en/accepted/adr-039.ncl b/site/site/content/adr/en/accepted/adr-039.ncl
new file mode 100644
index 0000000..b2fbfc4
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-039.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-039",
+ title = "Typed Recipe Annotations and the Transversal +/- Mutation Verb — Self-Describing Justfiles, Queryable Like the API Catalog",
+ slug = "039",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/039",
+ graph = {
+ implements = [],
+ related_to = [],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-040.md b/site/site/content/adr/en/accepted/adr-040.md
new file mode 100644
index 0000000..ff2c317
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-040.md
@@ -0,0 +1,140 @@
+---
+id: "adr-040"
+title: "Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism"
+slug: "040"
+subtitle: "Accepted"
+excerpt: "A project's working conventions are NOT captured by ontoref today, even though"
+author: "ontoref"
+date: "2026-05-29"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-040", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+A project's working conventions are NOT captured by ontoref today, even though
+they are load-bearing for every contributor and agent:
+- GUIDELINES — per-language coding rules. In this ecosystem they are SHARED:
+ .claude/guidelines/<lang>/ is a real directory whose individual *.md files are
+ PER-FILE symlinks to /Users/Akasha/Tools/dev-system/languages/<lang>/guidelines/*.md
+ (verified: e.g. .claude/guidelines/rust/ACTIX.md → dev-system/.../rust/guidelines/ACTIX.md).
+ dev-system is the single origin; many projects symlink the same source. The same
+ origin also feeds NON-Claude consumers (just-modules/, ci/, workflow.md), so the
+ guidelines are project conventions of which Claude Code is only one consumer —
+ not Claude-specific config. A project's OWN guideline is simply a real .md file
+ sitting beside the symlinked ones in the same directory.
+ - CI — already modeled: the workflow layer (ADR-038 et al.) declares
+ validations/builds/distributions and generates .woodpecker/*, pre-commit,
+ justfiles. CI is declared; it is just not cross-referenced from a single
+ "what conventions does this project use" surface.
+ - CONFIG conventions — how a project's config is structured (config.ncl shape,
+ .cargo layout, env wiring). Partly surfaced by `describe config`.
+ontoref already has adjacent surface: `describe guides` reads `language_guides`
+from the .claude/guidelines/*/ glob, `card.ncl` is the project beacon ("what IS
+this project"), and `describe config` reports the runtime config. What is missing
+is (a) a DECLARATION of which guideline/convention sources a project binds and at
+what version, and (b) a propagation/SYNC mechanism so a shared change — e.g. a
+new Rust version bumping the guideline — reaches every project, and a project's
+own refinement can flow back, without hand-managing symlinks per project.
+The propagation need is the heart of this ADR. Symlinks propagate transparently
+but only on one machine, are invisible to ontoref's queryable surface, break on
+clone/CI/containers, and cannot be versioned or audited. The protocol already
+owns the right mechanism for "a change consumers must adopt": the migration
+system (ADR-010) plus the `update_ontoref` mode. Convention bindings should ride
+that mechanism, not a second ad-hoc symlink convention.
+Two named Spiral Tensions from `.ontoref/ontology/core.ncl` are engaged:
+- formalization-vs-adoption — declaring conventions raises formalization;
+ forcing every project to vendor/copy them raises adoption cost. Its
+ resolution ("optional layers, not mandatory gates") constrains the design.
+ - ontology-vs-reflection — the binding declaration is Yin (what conventions
+ the project IS bound to); the sync/update action is Yang (what BECOMES when
+ the shared origin moves). Both must coexist: a declaration with no sync drifts
+ silently; a sync with no declaration has nothing to reconcile against.
+This ADR is the meta-convention sibling of ADR-039 (which made the project's OWN
+recipes self-describing). ADR-039 is about intent the project authors; ADR-040 is
+about conventions the project BINDS from a shared origin and keeps in sync.
+
+Decision
+
+Make a project DECLARE its convention bindings, hold the convention CONTENT
+authoritatively under `.ontoref/` (protocol-first, harness-independent), expose
+both as a queryable surface, and SYNC from the shared origin through the existing
+update/migration mechanism — never through hand-managed symlinks as the source of
+truth.
+DESIGN PROPERTIES (each is a constraint below):
+1. Declaration in card.ncl. The beacon gains a `conventions` block:
+conventions = {
+ guidelines = [
+ { lang = "rust", source = "dev-system", version = "2024" },
+ { lang = "nushell", source = "dev-system", version = "0.111" },
+ ],
+ ci = { workflow_layer = "ci" }, # reference, not a copy (ADR-038)
+ config = { schema = ".ontoref/config.ncl", profile = "default" },
+ }
+card.ncl is chosen over a new catalog because conventions are part of "what
+ IS this project" (the beacon already answers that and is migration-versioned),
+ and `describe guides`/`describe config` already read this neighborhood. The
+ declaration NAMES sources and versions; it does NOT inline guideline content.
+2. Content authoritative under .ontoref/, harness as materialized view
+ (protocol-first). The guideline CONTENT lives at .ontoref/conventions/<lang>/
+ — the protocol surface, addressable independently of any harness (cf.
+ ontology-decoupled-from-project). .claude/guidelines/<lang>/ becomes a
+ MATERIALIZED VIEW: a symlink pointing INTO .ontoref/conventions/<lang>/, so
+ Claude Code keeps reading its expected path while ontoref owns the content.
+ The conventions survive with no .claude/ at all (CI, other agents, plain
+ tooling read .ontoref/ directly). Within .ontoref/conventions/<lang>/, each
+ file is either a symlink to the shared origin (dev-system) or a real
+ project-OWN file beside them — the per-file model the current layout already
+ uses, just relocated to the protocol root.
+3. CI is referenced, never re-declared. The `ci` binding points at a workflow
+ layer id (ADR-038's model). ontoref does not duplicate CI definition into the
+ conventions surface; it cross-links so `describe conventions` can show "CI =
+ workflow layer 'ci'" and jump to the workflow model.
+4. Shared origin, resolvable PER-FILE mode (symlink | vendored | local-own).
+ Inside .ontoref/conventions/<lang>/ each file resolves explicitly: `symlink`
+ to the shared origin (transparent dev-loop), `vendored` (a copied snapshot,
+ clone/CI/container-safe), or `local-own` (a real project-authored file with no
+ origin). The per-file granularity is what lets a project mix shared guidelines
+ with its own in one directory. The DECLARATION (card.ncl) plus the .ontoref/
+ content are the source of truth; the .claude/ symlink is a derived view.
+5. Propagation rides the update/migration mechanism, not bespoke symlink edits.
+ `ore conventions sync` (and the `update_ontoref` mode) reconcile each declared
+ binding against the shared origin: detect version drift (origin moved, e.g.
+ new Rust guideline), report it, and — opt-in — re-link or re-vendor to the
+ declared version. A shared change reaches every binding project through this
+ one path. Project-local refinement flows back by updating the origin and
+ bumping the declared version, never by editing a vendored copy in place and
+ losing the link to origin.
+6. Bidirectional but origin-authoritative. Sync is two-directional in intent
+ (origin → projects for shared bumps; project → origin for refinements) but
+ the ORIGIN is authoritative: a project does not silently fork shared
+ conventions. A divergence is reported by audit; promoting a local change
+ means writing it to the origin and re-syncing, so all binding projects can
+ adopt it.
+7. Optional, audit-not-gate. A project may declare zero conventions and remain
+ valid. `ore conventions audit` reports: bindings whose origin is unreachable,
+ version drift (declared != origin), and resolution-mode mismatches (declared
+ symlink but found vendored, or vice versa). It REPORTS; it does not block —
+ the formalization-vs-adoption resolution.
+These are MODES/utilities, not new stores: per the user's framing, guidelines/CI/
+config bindings are implementation-framework utilities (templates, examples,
+sync). `ore conventions sync|audit|describe` and the `update_ontoref` extension
+are the operational surface; card.ncl carries the declaration.
+
+Constraints
+
+Hard A project's convention bindings (guidelines per language, CI reference, config conventions) MUST be declared in .ontoref/card.ncl as a `conventions` block naming source + version. Guideline CONTENT MUST NOT be inlined; the declaration names sources, content resolves from the shared origin.Hard Convention CONTENT MUST live authoritatively under .ontoref/conventions/<lang>/ (protocol-first). .claude/guidelines/<lang>/ MUST be a materialized view (a symlink into .ontoref/conventions/), never the authoritative location. The conventions MUST resolve with no .claude/ present. The card.ncl declaration plus the .ontoref/ content are the source of truth; per-file resolution mode (symlink to origin | vendored | local-own) is recorded, and a project's own guideline is a real file beside the shared ones.Hard Convention propagation/sync MUST be implemented as an extension of the existing migration system (ADR-010) and the update_ontoref mode — e.g. `ore conventions sync` and an update_ontoref step — NOT as a separate symlink-specific propagation path. A shared-origin change reaching consumers MUST be expressible as / accompanied by a migration.Hard The shared origin MUST be authoritative for shared conventions. A project MUST NOT silently fork a bound convention by editing a vendored copy in place. Local refinements are promoted by writing to the origin and bumping the declared version; divergence MUST be reported by audit, not auto-merged.Soft Convention bindings MUST be optional: a project may declare none and remain valid. `ore conventions audit` MUST report unreachable origins, version drift, and resolution-mode mismatches, but MUST NOT block builds or commands.Soft The `ci` convention binding MUST reference a workflow layer id (ADR-038 model), not re-declare CI steps in the conventions surface. `describe conventions` cross-links to the workflow model rather than duplicating it.
+
+Alternatives considered
+
+Keep symlinks as the mechanism, add nothing — rejected: Symlinks are invisible to ontoref, unversioned, and break on clone/CI/container. They cannot answer 'what version' or 'is this in sync', and a shared bump requires manual per-project symlink work. The whole point is to make bindings declared, queryable, and synced.Declare conventions in a new .ontoref/catalog/conventions.ncl — rejected: Fragments 'what the project is bound to' away from the card.ncl beacon and duplicates the read surface that describe guides/config already provide. A catalog suits kind-extensible operation inventories (ADR-034), not the small, identity-level binding declaration. Chosen against, but the catalog remains the right home if bindings later grow rich enough to need their own kind system.A bespoke convention-sync tool independent of the migration system — rejected: Duplicates ADR-010's propagation mechanism and update_ontoref, and stays outside `ore migrate`/`describe`. Convention propagation is the same event shape as protocol propagation; it must reuse that one path.Vendor (copy) conventions only, drop symlinks entirely — rejected: Loses the transparent dev-loop where editing the origin is instantly reflected. Both modes have a place: symlink for local dev, vendored for clone/CI/container. The declaration records which mode applies; forcing one collapses a useful choice.Inline guideline content into card.ncl / the project — rejected: Bloats the beacon, defeats sharing, and guarantees drift from the origin. Bindings must name sources+versions and resolve content from the shared origin, never inline it.
+
+Anti-patterns
+
+Treating .claude Symlinks as the Authoritative Content — Convention content lives authoritatively under .claude/guidelines/* (harness-specific) with no card.ncl declaration and nothing under .ontoref/. The binding is invisible to ontoref, unversioned, harness-coupled, and vanishes on clone/CI/container or when there is no Claude Code.A Separate Sync Path Outside the Migration System — Convention propagation is built as its own tool/script independent of the migration system and update_ontoref, so it is invisible to `ore migrate` and duplicates the protocol's propagation mechanism.Editing a Vendored Convention In Place — A project edits its vendored copy of a shared guideline directly, forking the shared origin silently. Other binding projects never see the change and the origin fractures.Failing Builds on Convention Drift — `ore conventions audit` (or a hook) blocks commands/builds when a binding is drifted or undeclared, turning an optional layer into a mandatory gate.Re-Declaring CI in the Conventions Surface — CI steps are copied into card.ncl's conventions block instead of referencing the workflow layer, creating a second drifting CI source.
+
+Related ADRs
+
+ADR-010 · ADR-038 · ADR-039
diff --git a/site/site/content/adr/en/accepted/adr-040.ncl b/site/site/content/adr/en/accepted/adr-040.ncl
new file mode 100644
index 0000000..29d0d40
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-040.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-040",
+ title = "Convention Bindings and Shared-Source Propagation — Declared Guidelines/CI/Config, Synced From a Shared Origin Through the Update Mechanism",
+ slug = "040",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/040",
+ graph = {
+ implements = [],
+ related_to = ["adr-010", "adr-038", "adr-039"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-041.md b/site/site/content/adr/en/accepted/adr-041.md
new file mode 100644
index 0000000..815c58a
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-041.md
@@ -0,0 +1,69 @@
+---
+id: "adr-041"
+title: "Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge"
+slug: "041"
+subtitle: "Accepted"
+excerpt: "ADR-027 declares per-domain CRDT merge strategies (HlcLastWriterWins,"
+author: "ontoref"
+date: "2026-05-30"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-041", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-027 declares per-domain CRDT merge strategies (HlcLastWriterWins,
+OrSet, TextMerge, GSet) as load-bearing, and ontoref-sync ships them as
+tested `CrdtMergeStrategy` impls. bl-018 wired multi-actor convergence,
+but only at the commit-layer cell level via HlcLastWriterWins: the
+highest-HLC assertion to a cell wins. Investigation while attempting
+bl-029 found the substrate cannot express the other three strategies:
+- `OpPayload` has only `EntityAssert` (and the state-neutral marker
+ variants). There is NO retract/remove — so OR-Set add/remove cannot
+ be represented at all.
+ - The commit layer stores opaque value bytes per cell and resolves
+ concurrent writes by last-writer-wins. There is no place for a cell
+ to declare a richer merge (set union, text merge), so OrSet/TextMerge
+ have nothing to bind to.
+ - Grow-only/append-only domains (GSet-like) are already covered by the
+ distinct-entity cell model and need no new machinery.
+Without a richer op model the four CRDT strategies remain library code
+no domain can use; convergence is correct only where last-writer-wins is
+the right semantics.
+
+Decision
+
+ACCEPTED. Extend the substrate op model along two axes, both behind the
+existing tier-2/`substrate` posture (tier-0/1 unaffected):
+1. RETRACT OP. Add `OpPayload::Retract { attrs, entity }` — the inverse
+ of `EntityAssert`, removing the named (entity, attribute) cells. A
+ retract is a witnessed, signed, DAG-anchored op like any mutation;
+ it is the precondition for remove-based domains (OR-Set).
+2. PER-CELL TYPED MERGE. A cell resolves its value through a CRDT type
+ selected by the cell's DOMAIN (derived from the attribute's declared
+ domain in the catalog), not by unconditional last-writer-wins. The
+ commit layer's replay FOLDS every op touching a cell through that
+ domain's `CrdtMergeStrategy` rather than taking the last applied
+ value. `HlcLastWriterWins` remains the DEFAULT for any cell whose
+ domain declares no strategy, so the current behaviour and all
+ existing state roots are preserved exactly.
+The fold preserves determinism: CRDT merges are commutative, associative,
+and idempotent, and ties resolve by the same HLC+payload order the oplog
+already imposes — so the rebuilt state root is independent of arrival
+order, which bl-016's replay determinism and bl-018's convergence both
+require. Witnesses are unchanged: a Retract is witnessed exactly as an
+EntityAssert is, keeping every mutation on the witness-as-axis-seam.
+
+Constraints
+
+Hard A Retract operation MUST be a signed, DAG-anchored oplog entry subject to the same append-time validation (signature, parents, monotonic HLC) as EntityAssert. Deletion MUST NOT have an unwitnessed side channel.Hard The per-cell merge MUST default to HlcLastWriterWins for any cell whose domain declares no CRDT strategy, so state roots computed before this ADR are reproduced exactly. The fold MUST be commutative, associative, and idempotent for every strategy.
+
+Alternatives considered
+
+Keep cell-LWW only; never wire the richer strategies — rejected: Leaves OrSet/TextMerge as dead library code and convergence correct only for LWW domains — contradicting ADR-027, which makes per-domain CRDT load-bearing.Model deletion as an EntityAssert of a tombstone sentinel value — rejected: Overloads the value space (a real value could collide with the sentinel) and still gives LWW semantics, not add-wins. A typed Retract is unambiguous and composes with the fold.Per-domain merge in a layer above the commit layer (post-process the root) — rejected: The state root must be the merge result; post-processing it would make the witnessed root and the merged state diverge — exactly the seam tear ADR-044 and bl-018 guard against. The merge must be the commit-layer reduce itself.
+
+Related ADRs
+
+ADR-023 · ADR-026 · ADR-027 · ADR-036
diff --git a/site/site/content/adr/en/accepted/adr-041.ncl b/site/site/content/adr/en/accepted/adr-041.ncl
new file mode 100644
index 0000000..eeb43b0
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-041.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-041",
+ title = "Richer Op Model — Retract Operations and Per-Cell Typed CRDT Merge",
+ slug = "041",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/041",
+ graph = {
+ implements = [],
+ related_to = ["adr-023", "adr-026", "adr-027", "adr-036"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-042.md b/site/site/content/adr/en/accepted/adr-042.md
new file mode 100644
index 0000000..79c1d78
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-042.md
@@ -0,0 +1,67 @@
+---
+id: "adr-042"
+title: "SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports"
+slug: "042"
+subtitle: "Accepted"
+excerpt: "ADR-027 defines `SyncBackend` as a SYNCHRONOUS trait (`announce`,"
+author: "ontoref"
+date: "2026-05-30"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-042", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-027 defines `SyncBackend` as a SYNCHRONOUS trait (`announce`,
+`fetch_op`, `announced_ops`, `subscribe_peer` return `Result` directly).
+The default `FilesystemSync` is naturally synchronous, and bl-018's
+`Substrate::ingest` drives it from synchronous code. Implementing the
+first real transport, NatsSync over JetStream (bl-031), surfaced an
+impedance mismatch: `platform-nats`/JetStream is async (tokio), so an
+async client cannot satisfy a synchronous trait method without bridging,
+and calling `block_on` from within a tokio worker thread panics. The
+trait shape must be settled before NatsSync can be written.
+A second, related point: the trait's verbs (`announce(op_id)` +
+`fetch_op(op_id)` + `announced_ops`) are filesystem-shaped (announce an
+id, fetch a body by id). A pull-consumer transport like JetStream more
+naturally publishes and pulls whole op messages. The contract must stay
+expressible by both without forcing one model onto the other.
+
+Decision
+
+ACCEPTED. `SyncBackend` STAYS synchronous — the synchronous trait is the
+contract, and synchrony is preserved at the call site (`Substrate::ingest`
+and the embedded/filesystem path stay runtime-free, honouring
+protocol-not-runtime and offline-first).
+Async network transports bridge INTERNALLY: a backend such as NatsSync
+owns its own current-thread tokio runtime and `block_on`s its async
+client inside each trait method. The contract is that synchronous
+`SyncBackend` methods are invoked from a BLOCKING context — never from a
+tokio worker — so the daemon drives a sync round via `spawn_blocking` (or
+a dedicated sync thread). The backend's runtime is an implementation
+detail invisible to the trait and to `ontoref-core`.
+The verb mapping for pull-consumer transports is fixed as: `announce`
+publishes the op id (and, for transports without a separate body store,
+the canonical op bytes) to the project subject; `fetch_op` retrieves the
+op bytes by id; `announced_ops` drains/pulls the durable consumer to list
+ids seen. A backend MAY publish body-with-announcement in one message and
+serve `fetch_op` from a local index of pulled messages — the trait does
+not mandate a separate body channel.
+Backend selection stays trigger-deferred and feature-gated: NatsSync is
+compiled only under the daemon `nats` feature and is an OPTIONAL
+accelerator that degrades to absent; FilesystemSync remains the
+zero-dependency default.
+
+Constraints
+
+Hard The `SyncBackend` trait MUST remain synchronous; async transports MUST bridge internally (backend-owned runtime) and MUST NOT require ontoref-core or FilesystemSync to depend on an async runtime. Sync rounds invoking a network backend MUST run on a blocking context, never a tokio worker.Hard A network SyncBackend (e.g. NatsSync) MUST be feature-gated and degrade to absent when its service is unavailable; the zero-dependency FilesystemSync MUST remain the default. No tier may be forced to require a broker.
+
+Alternatives considered
+
+Make SyncBackend async (async fn in trait / async-trait) — rejected: Forces an async runtime onto ontoref-core and every substrate consumer, including offline/embedded uses. Contradicts protocol-not-runtime and ADR-029 offline-first for the sake of one network backend.Add a parallel async trait and a bridge layer — rejected: Doubles the contract surface and the maintenance burden before any second async backend exists. The backend-owned-runtime bridge achieves the same with no new public trait.Spawn the async client on the daemon's shared runtime and block_on there — rejected: block_on inside a tokio worker thread panics. The backend must own a separate runtime (or run on spawn_blocking) — a shared-runtime block_on is unsound.
+
+Related ADRs
+
+ADR-027 · ADR-014 · ADR-029 · ADR-002
diff --git a/site/site/content/adr/en/accepted/adr-042.ncl b/site/site/content/adr/en/accepted/adr-042.ncl
new file mode 100644
index 0000000..f7454ac
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-042.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-042",
+ title = "SyncBackend Sync/Async Reconciliation — Synchronous Trait, Backend-Owned Runtime for Network Transports",
+ slug = "042",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/042",
+ graph = {
+ implements = [],
+ related_to = ["adr-027", "adr-014", "adr-029", "adr-002"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-043.md b/site/site/content/adr/en/accepted/adr-043.md
new file mode 100644
index 0000000..62ab0f0
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-043.md
@@ -0,0 +1,84 @@
+---
+id: "adr-043"
+title: "Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules"
+slug: "043"
+subtitle: "Accepted"
+excerpt: "ADR-035 established the positioning layer as a queryable protocol surface, but"
+author: "ontoref"
+date: "2026-06-02"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-043", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-035 established the positioning layer as a queryable protocol surface, but
+its schema entangled the axes of positioning: `ValueProp` carried the value
+difference inline (`unique_angle`), the target (`audience_ids`) and the backing
+(`evidence_adrs`) all in one record, and `Campaign` was the only model of
+outward action. There was no first-class home for "what is our differential
+value", competitors lived only as prose strings inside `ValueProp.alternatives`,
+difusión collapsed onto "campaign" (a website is difusión but not a campaign),
+and there was no model of validation — the success cases where all of it
+converges and becomes credible.
+Working through the positioning of ontoref against Knowledge-Graph competitors
+surfaced that positioning has three orthogonal concerns — the same orthogonality
+ADR-009 already established for manifest self-interrogation (capabilities answer
+"what / why / how" as three orthogonal axes): WHAT (what differential value),
+FOR WHOM (which audience), HOW (how we diffuse). They are orthogonal much of
+the time and cross only at named seams. A fourth element — VALIDATION — is not a
+fourth axis but the seam (seam) that binds the other three and keeps them
+credible: a success case is the convergence of a differentiator, an audience and
+a difusión mechanism, signed by a real outcome.
+The load-bearing risk is formalization-vs-adoption: adding four typed kinds and
+a coherence audit to the marketing surface could impose ceremony before value.
+The framing that resolves it is ADR-029 (tier-orthogonal, opt-in) plus
+all-defaulted fields: a project that writes zero differentiators pays zero
+adoption cost, and existing positioning files validate unchanged.
+
+Decision
+
+Model the positioning layer as four elements. Three orthogonal axes, each a
+first-class typed kind, bound by a Proof seam:
+WHAT — `Differentiator` (one file per area of differential value) +
+ `Competitor` (what they do / how ontoref differs).
+ FOR WHOM — `Audience` (gains `status`: Hypothesis until a Proof references it).
+ HOW — `DifusionMechanism` (kind: Web | DocsSite | McpServer | Repo |
+ Content | Talk | Event; persistent vs event). Difusión is the
+ category; `Campaign` is ONE time-boxed coordination within it.
+ VALIDATION — `Proof`: the seam. `converges = { differentiator_ids, audience_ids,
+ mechanism_ids }` — references the other three; nothing references
+ Proof. A Proof is the triple convergence signed by a real outcome.
+The crossings are explicit: `ValueProp` is the WHAT×WHOM seam (gains
+`differentiator_ids`); `Campaign` and `Proof` bind HOW and VALIDATION.
+The marketing surface is governed by the same mechanism it sells. Five coherence
+rules run in `ore positioning audit` (local-first, daemon-independent):
+1. AnchorDrift — a differentiator/proof evidence_adr must be Accepted.
+ 2. UnprovenValidatedAudience — an Audience marked 'Validated needs >=1 Proof.
+ 3. Live value-prop — must converge four elements (differentiator +
+ audience + a difundiendo mechanism + a proven diff).
+ 4. DanglingRef — a Proof's converges ids must all resolve.
+ 5. CoverageGap (warn) — a differentiator with no Proof is an explicit
+ reminder, never a silent gap.
+ADR-drift severity is gated by value-prop status: a Draft claim citing a
+Proposed/Missing ADR is work-in-progress (informational, non-blocking); only
+Validated/Live claims must cite Accepted ADRs — mirroring the
+`_live_requires_evidence_adrs` contract. Coverage warnings never fail CI.
+
+Constraints
+
+Hard The positioning schema MUST keep WHAT (Differentiator), FOR WHOM (Audience) and HOW (DifusionMechanism) as separate typed kinds. Cross-axis binding MUST happen only through the seam kinds (ValueProp for what×whom; Campaign and Proof). Collapsing an axis back into another kind is forbidden.Hard Proof MUST reference the other three axes (converges.differentiator_ids, audience_ids, mechanism_ids each non-empty) and MUST NOT be referenced by any other kind. The make_proof contract (`_proof_converges_all`) enforces the triple convergence.Hard Every Differentiator MUST cite >=1 ontology node and >=1 evidence ADR, and name >=1 competitor category it beats (`vs`). The make_differentiator contracts (`_diff_requires_anchor`, `_diff_requires_vs`) enforce this.Hard `ore positioning audit` MUST run the five coherence rules over the positioning layer and gate CI by value-prop status: hard ADR-drift on Validated/Live value-props and any coherence violation fail; Draft drift and coverage gaps are informational.Soft A value-prop with status 'Live MUST bind >=1 differentiator (the WHAT axis); the `_live_requires_differentiator` contract enforces the local half, and the audit enforces the rest (a difundiendo mechanism and a proven differentiator).
+
+Alternatives considered
+
+Keep the entangled ValueProp model — value difference stays inline in unique_angle — rejected: Entangling what (value), for-whom (audience) and backing in one record means any change to the differentiation story churns every claim, and competitors/differentiators are never queryable on their own. Orthogonal axes with a ValueProp seam keep each concern independently evolvable.Competitors and validation as Markdown narratives, no typed kinds — rejected: Markdown is not queryable and cannot be drift-checked. ADR-035's whole thesis is marketing-as-typed-protocol-surface; competitors and proofs belong as typed NCL with cross-references, not as prose the audit cannot see.Validación as a fourth orthogonal axis (a fourth folder co-equal to the three) — rejected: Validation is not parallel to what/for-whom/how — it is where they converge and get signed. Modeling it as a fourth axis implies a false fourth pole; modeling it as the Proof seam (references the three, referenced by none) is faithful to witness-as-axis-seam (ADR-031).Fail CI on any ADR-drift regardless of value-prop status — rejected: A Draft claim citing a Proposed ADR is legitimate work-in-progress; hard-failing it forces deleting true citations or fabricating ratification. Gating severity by status keeps the hard gate on Live/Validated claims where _live_requires_evidence_adrs already places it.
+
+Anti-patterns
+
+Collapsing an Axis Back into Another Kind — A contributor inlines the value difference back into ValueProp.unique_angle, or folds competitors into alternatives[] strings, instead of using the Differentiator/Competitor kinds. The axes re-entangle and stop being independently queryable.A Proof That Does Not Converge Three Axes — A success case is written as a testimonial that names an outcome but does not reference a differentiator, an audience and a mechanism. It is no longer the seam — it is unanchored marketing the audit cannot trace to the model.Asserting a Validated Audience Without a Proof — An Audience is marked status='Validated to look credible, but no Proof references it. The validation is asserted, not earned — the exact failure mode the framework exists to prevent.Routing Marketing Through Untyped Prose to Skip the Audit — A claim is published on the website or docs without a corresponding typed Live value-prop, so it escapes the coherence audit and can drift freely — re-introducing the prose-that-rots failure ADR-035 closed.
+
+Related ADRs
+
+ADR-035 · ADR-031 · ADR-009 · ADR-029 · ADR-012
diff --git a/site/site/content/adr/en/accepted/adr-043.ncl b/site/site/content/adr/en/accepted/adr-043.ncl
new file mode 100644
index 0000000..16c5445
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-043.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-043",
+ title = "Positioning as a Four-Element Framework — Orthogonal Axes (Qué / Para-Quién / Cómo) Bound by a Proof Seam with Self-Applied Coherence Rules",
+ slug = "043",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/043",
+ graph = {
+ implements = [],
+ related_to = ["adr-035", "adr-031", "adr-009", "adr-029", "adr-012"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-044.md b/site/site/content/adr/en/accepted/adr-044.md
new file mode 100644
index 0000000..ef4f80a
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-044.md
@@ -0,0 +1,67 @@
+---
+id: "adr-044"
+title: "Actor Key Succession — Unforgeable Old→New Rotation Records"
+slug: "044"
+subtitle: "Accepted"
+excerpt: "ADR-023/024 make the witness the unit of provenance: every authoritative"
+author: "ontoref"
+date: "2026-05-30"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-044", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-023/024 make the witness the unit of provenance: every authoritative
+mutation is signed by an actor's Ed25519 key, and external verifiers
+check the signature against the actor's published public key
+(`.ontoref/actors.ncl`). Until now actor keys were static — there was no
+way to rotate a key without stranding every witness it had ever signed.
+Two failure modes follow from static keys:
+- A compromised or lost key has no recovery path. Abandoning it
+ abandons the verifiability of all prior witnesses signed under it.
+ - Routine hygiene (periodic re-keying) is impossible without breaking
+ the audit chain.
+The seam at risk is `witness-as-axis-seam` (Axiom, invariant): the witness
+binds the ontology axis (what IS — the signed state) to the reflection
+axis (the act). A key change that orphans prior witnesses tears that seam
+for every deposit made under the old key.
+
+Decision
+
+ACCEPTED. Key rotation is recorded as an unforgeable succession entry in
+the oplog, modeled as `OpPayload::KeySuccession { actor_id, new_key,
+old_key }`. The record MUST be signed by the OLD key: the enclosing
+`OpBody.actor` equals `old_key`, and the operation signature verifies
+under it. Only the holder of the current key can therefore authorize its
+successor. The record is anchored in the oplog DAG (parents = current
+heads, monotonic HLC) and is state-neutral — the commit, triple, and
+substrate reducers assert nothing from it.
+Verification chains trust backwards from the actor's current trusted key
+(`actors.ncl`) through the succession records: a key is valid for an
+actor if it is the current key or a verified predecessor of a valid key.
+A record only extends the chain when its declared `old_key` equals its
+signer AND its signature verifies — a forged record (signed by anyone but
+the declared predecessor) is silently ignored. A witness signed by a
+superseded key therefore still verifies: the chain proves that key was
+once authoritative for the actor.
+The trust-chain resolver (`ontoref_types::succession::valid_keys_for_actor`)
+is a pure function over `&[Operation]` — no IO, no oplog handle — so any
+verifier (daemon, CLI, remote peer) applies it offline. Emission (generate
+new key, append the succession, republish the public key) is a tier-2
+substrate act, gated behind the `substrate` feature; tier-0/1 projects pay
+no cost.
+
+Constraints
+
+Hard A KeySuccession record MUST be signed by its declared old_key (OpBody.actor == old_key) and that signature MUST verify; chain resolution MUST reject any record where the signer is not the declared predecessor. Only the holder of the current key may authorize its successor.Hard Verification MUST treat a witness signed by a superseded key as valid when a chain of verified succession records links that key to the actor's current key. Reversing this (invalidating prior witnesses on rotation) breaks the witness-as-axis-seam.
+
+Alternatives considered
+
+Static keys (status quo) — rejected: No recovery path; a compromised key strands every witness it signed. Rejected because it leaves the witness-as-axis-seam brittle across the inevitable key lifecycle.Sign the succession with the NEW key — rejected: Anyone can mint a new key and claim to be an actor's successor. Authorization must come from the party being superseded, so the record is signed by the old key.A central key-authority service that issues/revokes actor keys — rejected: Reintroduces a trusted third party and a runtime dependency, contradicting ADR-001 minimal adoption and ADR-027 P2P-pure direction. The oplog-anchored, self-authorizing chain needs no authority.
+
+Related ADRs
+
+ADR-023 · ADR-024 · ADR-031 · ADR-036
diff --git a/site/site/content/adr/en/accepted/adr-044.ncl b/site/site/content/adr/en/accepted/adr-044.ncl
new file mode 100644
index 0000000..7cf0391
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-044.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-044",
+ title = "Actor Key Succession — Unforgeable Old→New Rotation Records",
+ slug = "044",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/044",
+ graph = {
+ implements = [],
+ related_to = ["adr-023", "adr-024", "adr-031", "adr-036"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-045.md b/site/site/content/adr/en/accepted/adr-045.md
new file mode 100644
index 0000000..daf3d15
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-045.md
@@ -0,0 +1,37 @@
+---
+id: "adr-045"
+title: "Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting"
+slug: "045"
+subtitle: "Accepted"
+excerpt: "ADR-012 introduced a domain extension system; ADR-018 formalized a three-level hierarchy (base, domain, instance) with an absolute level.index in {1,2,3} and a single-valued level.parent declared in ma"
+author: "ontoref"
+date: "2026-06-02"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-045", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-012 introduced a domain extension system; ADR-018 formalized a three-level hierarchy (base, domain, instance) with an absolute level.index in {1,2,3} and a single-valued level.parent declared in manifest.ncl. ADR-020 plus the qa entry ontoref-three-layer-model describe three CONTENT layers (Layer 1 self-management, Layer 2 integration surface, Layer 3 caller-side wiring) and flag (bl-009) the unresolved interaction with ADR-018's levels as a '3-layer x 3-level matrix, likely orthogonal but unresolved until the ADR drafts'. A third field case forces resolution. Rustelo builds a domain over a Torred domain over ontoref-base, and Rustelo's implementation mode produces website instances; those website instances are then hosted inside a Provisioning workspace, itself the terminal instance of Provisioning's own provisioning-domain chain. Two facts break ADR-018's absolute triad. (1) DEPTH > 3: the specialization chain base -> Torred -> Rustelo -> website-mode -> website-instance is depth 4-5, but level.index is capped at 3 with fixed semantic names. (2) DOMAIN CO-TENANCY: a single hosting repo (Provisioning) participates in two distinct chains from two distinct projects at once, and one chain's terminal instance re-hosts another chain's instance (instance-as-host). ADR-018's level.parent is single-valued and manifest.ncl declares exactly one level — neither expresses depth > 3, co-residency of chains, nor instance-as-host. The genuinely hard part — cross-project reference — is already designed: ADR-018 makes level.parent a name reference (not a path), ADR-028 makes the ontology addressable independently of the project filesystem (verify by witness, not clone), and ADR-030 provides catalog discovery across projects. What is missing is the generalization of the level axis itself and an explicit declaration for hosting.
+
+Decision
+
+Generalize ADR-018's absolute triad into a relative parent chain and add a co-tenancy declaration, both opt-in and backward-compatible, via four mechanisms. (1) RELATIVE LEVEL CHAIN — level identity is the parent chain, not an absolute index. A manifest declares level.parent (a name reference, exactly as ADR-018) and an optional level.role label; level.index becomes a DERIVED depth (walk parents to a base) that is no longer capped at 3 and no longer authoritative. The names base/domain/instance are reinterpreted as ROLES in a sliding window of three relative to an observation point, not global coordinates. Existing index 1/2/3 declarations remain valid as the depth<=3 case; deeper chains simply keep walking. The terminal node of one chain may itself be the parent-root of another (instance-as-host is a well-formed shape, not an error). (2) DOMAIN CO-TENANCY — a repo may declare manifest.ncl::hosts, an array of { chain (name reference to a foreign domain artifact), mount (where in this repo the hosted chain's Layer-3 wiring lives), digest (optional witness pin) }. The foreign chain is resolved by name through catalog discovery (ADR-030) and verified by witness (ADR-028), NEVER cloned. The host carries only Layer-3 wiring for the hosted chain (ADR-020), tagged layer-3-boundary. (3) MUTATION SOVEREIGNTY UNDER CO-TENANCY — a host's runtime MUST NOT mutate the authoritative state of any hosted chain. Hosting is placement plus wiring; each chain's authoritative state mutates only through its own declared operations with its own witness (ADR-024). Co-tenancy is co-residency of wiring, not transfer of state ownership. (4) OBSERVABLE CROSS-CHAIN RESOLUTION — ore mode resolve extends to report the full traversal PATH (every level visited, not a single hop) and the CHAIN SCOPE (which chain answered) when multiple chains are co-resident; ore describe state disambiguates FSM dimensions by chain scope. No cross-chain resolution is silent.
+
+Constraints
+
+Hard Every manifest.ncl::hosts entry must name a foreign chain resolvable by catalog discovery (name, optional digest) — resolution is by witness, never by cloneHard A host must not declare operations that mutate a hosted chain's authoritative state; hosted state mutates only through the hosted chain's own declared operationsHard Every level.parent chain must terminate at a base (a node with no parent); an unresolvable or cyclic parent reference is an errorSoft Wiring under a hosts mount should be tagged layer-3-boundary; untagged hosted wiring is a Soft warningSoft When level.index is declared explicitly it must equal the derived parent-walk depth; a mismatch is a Soft warning during the transition to derived indices
+
+Alternatives considered
+
+Keep ADR-018's absolute index and cap depth at 3 — rejected: The Rustelo chain is already depth 4-5. Capping forces artificial flattening that hides real specialization boundaries — the exact invisibility ADR-018 set out to eliminate. A depth cap turns a true parent/child relation into a lossy projection.Allow depth > 3 but forbid co-tenancy (one chain per repo) — rejected: Provisioning hosting a website inside a workspace is an actual deployment shape, not a hypothetical. Forbidding co-residency pushes the hosting wiring into untyped, untagged territory, producing exactly the Layer-3-in-Layer-1 contamination ADR-020 warns against. The relationship exists whether or not the model can name it; refusing to name it loses the boundary check.Let the host own the hosted chain's state (single runtime per repo) — rejected: Collapses ADR-024 internal-coherence-enforced. The hosted chain's validators and witness would be bypassed by the host runtime, making cross-domain mutation unverifiable and reintroducing the unwitnessed-mutation hazard the operations layer exists to close.Model co-tenancy as ordinary Layer-3 with no manifest declaration — rejected: Layer-3 wiring does live in the caller, but the hosting RELATIONSHIP (which foreign chain, pinned to which digest) is then implicit. The boundary cannot be validated, and the witness pin (ADR-028) has nowhere to live. An explicit hosts array is the minimal addition that makes the relationship checkable.
+
+Anti-patterns
+
+Host Owns Hosted State — A hosting repo's runtime mutates the authoritative state of a chain it merely hosts, treating co-residency as ownership. The hosted chain's validators and witness are bypassed; cross-domain mutation becomes unverifiable.Absolute Level Flattening — Forcing a chain deeper than three into the absolute base/domain/instance triad by collapsing real specialization steps, so distinct boundaries become invisible.Implicit Hosting — A repo hosts a foreign chain's Layer-3 wiring without declaring manifest.ncl::hosts, so the hosting relationship, the foreign chain identity, and the witness pin are invisible and unverifiable.
+
+Related ADRs
+
+ADR-012 · ADR-018 · ADR-020 · ADR-024 · ADR-028 · ADR-030
diff --git a/site/site/content/adr/en/accepted/adr-045.ncl b/site/site/content/adr/en/accepted/adr-045.ncl
new file mode 100644
index 0000000..02aec86
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-045.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-045",
+ title = "Recursive Level Chains and Domain Co-Tenancy — Relative Levels and Multi-Chain Hosting",
+ slug = "045",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/045",
+ graph = {
+ implements = [],
+ related_to = ["adr-012", "adr-018", "adr-020", "adr-024", "adr-028", "adr-030"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-046.md b/site/site/content/adr/en/accepted/adr-046.md
new file mode 100644
index 0000000..7480023
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-046.md
@@ -0,0 +1,37 @@
+---
+id: "adr-046"
+title: "Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace"
+slug: "046"
+subtitle: "Accepted"
+excerpt: "ADR-032 consolidated the project layout under .ontoref/ and ADR-035 added the positioning layer, so a project's knowledge now spans many heterogeneous levels (ontology, adrs, reflection, positioning, c"
+author: "ontoref"
+date: "2026-06-02"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-046", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-032 consolidated the project layout under .ontoref/ and ADR-035 added the positioning layer, so a project's knowledge now spans many heterogeneous levels (ontology, adrs, reflection, positioning, catalog, assets, private strategy, diffusion). Holding all of it at once is not workable for a working session — 'todo junto no es gestionable'. Three prior facts shape the resolution. (1) Cross-project work has repeatedly failed in agent sessions: the failure mode was the agent NAVIGATING into a foreign repository, losing its thread and mutating where it should not. (2) The project chose persona/discipline as a first-class axis ORTHOGONAL to the trust actor of api-catalog.ncl — the work AREA a session operates in. (3) ADR-045 generalized levels to relative parent chains and added domain co-tenancy resolved BY WITNESS, never by clone (ADR-028), reusing host_entry_type (chain, mount, digest). What was missing is a single primitive that, given a discipline, provisions only the slice of levels that area needs — intra-project AND cross-project — presents it as one coherent working context, routes every mutation back to the authoritative home of the level it belongs to, and is consumed identically by CLI sessions, agent sessions, and the daemon interfaces (UI / MCP / API / GraphQL). The governing analogy is jj over git: a content-addressed truth store (the levels — ADR-023/025) with an operation-centric working layer above it (the view + engine) that records every operation in an op log (the interaction trace — ADR-037, and the oplog — ADR-036), composes and recomposes on demand, and colocates without forcing migration (ADR-029 tier coexistence). Two further requirements were stated explicitly. First, a mounted level must provide usable knowledge AND a means of verification that, in many cases, works WITHOUT complete knowledge of the level's content — the cross-project case cannot hold the whole foreign substance. Second, the engine must learn from the generated interaction — use the loop FROM THE START and adjust as it consumes real data, not prepare a seam for later.
+
+Decision
+
+Establish the substrate view (schema .ontoref/reflection/schemas/substrate-view.ncl) as the unit of dynamic context provisioning, via five mechanisms. (1) DISCIPLINE-SCOPED COMPOSITION — a view is f(discipline, context, actor): the discipline (the work AREA; area equals discipline) selects which levels mount, the actor filters what is visible and mutable. A view composes only the slice the area needs, never the whole project, and is decomposed on exit. (2) PERSISTENCE ROUTING — each mounted level declares persistence ('Inline / 'LiftedOut / 'Remote) and the engine routes every mutation back to that level's authoritative home. 'Inline composes from local files with no daemon (ADR-029 local fallback); 'LiftedOut and 'Remote resolve by witness through catalog discovery (ADR-028/030/045), NEVER cloned. Cross-project knowledge enters as a 'Remote level — the agent never leaves its repo. (3) VERIFICATION UNDER PARTIAL KNOWLEDGE — each level declares a verify means ('Local / 'Digest / 'Validator / 'Commitment). An out-of-repo level MUST use a partial-knowledge means: a content-addressed digest (ADR-045 source.digest), a declared validator answering consistency queries without exposing full content (ADR-026), or a cryptographic commitment proving a claim against committed-but-unrevealed content (ADR-023 G5 witness). Verification by holding the complete foreign substance is forbidden. (4) LEARNED TUNING FROM THE INTERACTION TRACE — a view's declared levels and actions are its AUTHORITY ENVELOPE. Elements marked 'Learned are tuned LIVE (selection, order, salience) by the engine as it consumes the interaction-trace stream named in learns_from (ADR-037), starting from the declared prior. The active recipe is the declared prior composed with a chain of witnessed adjustments — prior plus ops, exactly as a VCS working state is base plus operations. (5) INTERFACE PARITY — the same primitive backs CLI sessions, agent sessions, and the daemon interfaces: a UI panel or an MCP tool IS a mounted substrate view, declared by the surfaces field, not bespoke code. The learning loop is constitutive and used from the start; a view that declares no learns_from is its declared prior verbatim, so the adoption floor pays nothing.
+
+Constraints
+
+Hard The engine composes only levels and authorizes only mutations the substrate view declared; 'Learned tunes selection, order and salience strictly within the declared envelope and never adds a level or a mutation routeHard Every learned adjustment to a view's active recipe is recorded as a witnessed operation in the interaction trace and is reversible; no silent mutation of the recipe in effectHard The active recipe at any time equals the declared prior composed with the witnessed adjustment chain, and must be reconstructable and replayable from the declaration plus the interaction trace; declared salience is never overwritten by learned deltasSoft With no interaction data or no daemon, a view mounts its declared prior verbatim; learning is the accelerated path, not a precondition for compositionHard Every level with persistence 'LiftedOut or 'Remote must declare a witness source and a partial-knowledge verify means ('Digest, 'Validator or 'Commitment); it is never verified by holding complete content
+
+Alternatives considered
+
+Prepare the seam for learning but defer the engine (D2 deferred) — rejected: The decision is to use the loop from the start and adjust in consumption of real data. Deferring freezes the reflection axis and discards the prior-to-posterior loop, collapsing ontology-vs-reflection toward static ontology — the very Yang freeze ondaod exists to prevent. The prior-as-declared-view framing makes 'from the start' safe, so there is no risk-based reason to defer.Let the engine learn freely, composing levels beyond the declared envelope — rejected: Collapses ontology-vs-reflection toward reflection-dominates: the engine deciding authority bypasses ADR-024 internal-coherence-enforced, because a level or mutation route the view never declared would enter without a declared, witnessed path. C1 forbids it — authority is declared, only sintonia is learned.Require human acceptance of every learned adjustment — rejected: Kills 'adjust in consumption' with prohibitive friction. Safety does not require pre-acceptance: the jj op-log property — every adjustment is a witnessed, reversible operation (C2) and the recipe is reconstructable from prior plus ops (C3) — gives auditability and undo without a human in every loop.Verify remote levels by cloning the foreign project — rejected: Reintroduces the cross-project-in-sessions failure and does not scale across the ecosystem. ADR-028 verify-by-witness-not-clone exists precisely to avoid this; C5 makes partial-knowledge verification mandatory for out-of-repo levels.A flat view recipe with no envelope/tuning distinction — rejected: Makes C1 inexpressible: if declaration and learning are indistinguishable, the authority boundary cannot be protected and the engine can silently widen it. The eligibility ('Always/'Learned) split is the minimal addition that makes the authority line a typed, checkable fact.
+
+Anti-patterns
+
+Engine expands authority — The engine composes a level or authorizes a mutation the substrate view never declared, on the grounds that the interaction data suggested it. Learning is allowed to widen what is authoritative, not merely to tune within a declared envelope.Silent recipe drift — The engine mutates the active recipe without recording a witnessed, reversible operation, so the recipe in effect cannot be reconstructed or undone and diverges from the declared prior without a trace.Engine decides truth — The learned recipe is treated as authoritative ontology and the declared prior is discarded as stale, inverting the prior-to-posterior relationship so that reflection becomes the source of truth.Clone to verify — A remote or lifted-out level is verified by materializing its complete content rather than by digest, validator or commitment — reintroducing the navigational cross-project failure and forfeiting partial-knowledge verification.Learning as a gate — Composition is blocked until enough interaction data exists or the daemon is running; cold start fails instead of falling back to the declared prior, turning the engine into a hard dependency at the adoption floor.
+
+Related ADRs
+
+ADR-045 · ADR-028 · ADR-024 · ADR-023 · ADR-026 · ADR-037 · ADR-036 · ADR-029 · ADR-013 · ADR-031 · ADR-035 · ADR-018
diff --git a/site/site/content/adr/en/accepted/adr-046.ncl b/site/site/content/adr/en/accepted/adr-046.ncl
new file mode 100644
index 0000000..d692ddb
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-046.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-046",
+ title = "Substrate Views — Dynamic Context Provisioning by Composing Levels, Verified Under Partial Knowledge, Tuned From the Interaction Trace",
+ slug = "046",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/046",
+ graph = {
+ implements = [],
+ related_to = ["adr-045", "adr-028", "adr-024", "adr-023", "adr-026", "adr-037", "adr-036", "adr-029", "adr-013", "adr-031", "adr-035", "adr-018"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-047.md b/site/site/content/adr/en/accepted/adr-047.md
new file mode 100644
index 0000000..39aec7b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-047.md
@@ -0,0 +1,37 @@
+---
+id: "adr-047"
+title: "Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In"
+slug: "047"
+subtitle: "Accepted"
+excerpt: "The protocol signs witnesses (ADR-024 acting-binding, ADR-023 G5 commitment) and, since ADR-046, substrate-view learning adjustments, with Ed25519. Ed25519 is a classical scheme whose security rests on"
+author: "ontoref"
+date: "2026-06-02"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-047", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The protocol signs witnesses (ADR-024 acting-binding, ADR-023 G5 commitment) and, since ADR-046, substrate-view learning adjustments, with Ed25519. Ed25519 is a classical scheme whose security rests on the elliptic-curve discrete-log problem, which Shor's algorithm breaks on a cryptographically-relevant quantum computer. Unlike encryption, signatures have no harvest-now-decrypt-later exposure — but they have a worse one for an append-only record: the day Ed25519 falls, every historical witness in the oplog (ADR-036) becomes FORGEABLE, collapsing the non-repudiation of the entire record retroactively. The witness is precisely the project's long-lived integrity substrate, so it is exactly where algorithm longevity matters. NIST standardised post-quantum signatures in 2024 — FIPS 204 (ML-DSA, derived from CRYSTALS-Dilithium), FIPS 205 (SLH-DSA), FIPS 206 (FN-DSA). Some consumer deployments will additionally face a FIPS mandate independent of the quantum timeline. Hardcoding Ed25519 into the witness format would force a breaking, record-invalidating migration the moment either pressure arrives. ADR-044 already gives key succession (rotating an actor's KEY); what is missing is agility in the ALGORITHM itself. ML-DSA-65 (NIST level 3, ~AES-192) is the natural PQC target, but its artefacts are ~50x larger (public key 1952 B, signature 3309 B vs Ed25519 32/64) and signing is materially slower, so it cannot be a blanket replacement — it must be a plug-in for the deployments that need it.
+
+Decision
+
+Make the signature scheme a first-class, tagged dimension of every witness, via five mechanisms. (1) SCHEME TAG — every signed witness and every signed adjustment carries a `scheme` field; an unsigned digest commitment is the tier-0/1 fallback (ADR-029), a signed witness names the algorithm that produced it. (2) DISPATCH, FAIL CLOSED — verification dispatches on the declared scheme. A recognised-but-unbuilt scheme and an unknown scheme both FAIL (distinct non-zero exits); verification never silently accepts a signature whose scheme it cannot evaluate, and never coerces an unknown tag to Ed25519. (3) ED25519 DEFAULT, PQC OPT-IN — Ed25519 is the default for everyone (fast, 32/64-byte artefacts); a PQC scheme is selected per deployment or per actor, never mandated globally. (4) VARIABLE-LENGTH ENCODING — signatures and public keys are stored as variable-length hex, so a 3309-byte ML-DSA signature needs no format change; the witness record must not assume fixed artefact sizes. (5) ML-DSA-65 AS THE FIPS PLUG-IN — `ml-dsa-65` is the recognised PQC scheme slot, built behind a `pqc` Cargo feature backed by a CMVP-validated module (aws-lc-rs in FIPS mode), not an unvalidated pure-Rust implementation; a deployment may claim FIPS only when bound to the validated module. The seam ships now (scheme tag + dispatch); the ML-DSA backend is the deferred plug-in.
+
+Constraints
+
+Hard Every signed witness and signed adjustment carries a `scheme` tag identifying the algorithm that produced the signatureHard Verification dispatches on the declared scheme and fails (non-zero exit) on a recognised-but-unbuilt or unknown scheme; it never silently accepts or downgrades to Ed25519Soft Ed25519 is the default scheme; a PQC scheme is selected per deployment or actor and is never mandated globallyHard Signatures and public keys are stored as variable-length hex; the witness record must not assume fixed artefact sizesSoft A deployment may claim FIPS only when the PQC scheme is bound to a CMVP-validated crypto module, not an unvalidated pure-Rust implementation
+
+Alternatives considered
+
+Keep Ed25519 hardcoded with no scheme tag — rejected: Forces a breaking, record-invalidating migration the moment quantum or FIPS pressure arrives, and offers no way to keep historical witnesses verifiable across the transition. The witness format would have to change under the whole oplog at once.Adopt ML-DSA globally now, replacing Ed25519 — rejected: 50x larger artefacts, materially slower signing, and a validated-module dependency imposed on every adopter — including those with no quantum or FIPS requirement. Collapses the formalization-vs-adoption Spiral toward mandatory heavyweight crypto.Separate witness formats per scheme — rejected: N formats means N verify paths and no uniform record. A single format with a scheme tag and variable-length artefacts expresses the same agility with one code path and one stored shape.Treat an unknown or absent scheme as Ed25519 (lenient verify) — rejected: A forgery surface: a signature under a scheme the verifier silently downgrades would be accepted. Verification must fail closed on anything it cannot evaluate.
+
+Anti-patterns
+
+Hardcoded signature scheme — Signing and verifying with a compile-time-fixed algorithm and no scheme tag on the witness, so migrating the algorithm requires rewriting every historical record.Silent scheme fallback — A verifier that accepts a signature whose scheme it does not recognise, or coerces an unknown/missing tag to Ed25519, creating a downgrade forgery surface.PQC mandated globally — Forcing a heavyweight PQC scheme on every deployment regardless of quantum or FIPS pressure, imposing 50x artefacts and slower signing where they buy nothing.Unvalidated FIPS claim — Claiming FIPS compliance while bound to an unvalidated implementation of ML-DSA — satisfying the algorithm name but not the mandate.
+
+Related ADRs
+
+ADR-044 · ADR-024 · ADR-023 · ADR-046 · ADR-036 · ADR-029
diff --git a/site/site/content/adr/en/accepted/adr-047.ncl b/site/site/content/adr/en/accepted/adr-047.ncl
new file mode 100644
index 0000000..2f1f992
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-047.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-047",
+ title = "Cryptographic Agility — Scheme-Tagged Witnesses with Ed25519 Default and ML-DSA-65 FIPS Plug-In",
+ slug = "047",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/047",
+ graph = {
+ implements = [],
+ related_to = ["adr-044", "adr-024", "adr-023", "adr-046", "adr-036", "adr-029"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-048.md b/site/site/content/adr/en/accepted/adr-048.md
new file mode 100644
index 0000000..acda46d
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-048.md
@@ -0,0 +1,37 @@
+---
+id: "adr-048"
+title: "Constellation Layout — Spine at Parent, Code as Sub-Repo"
+slug: "048"
+subtitle: "Accepted"
+excerpt: "As ontoref grew it accumulated a second git-tracked surface beyond the Rust implementation: brand assets, web site source, outreach presentations, private strategy notes, and the protocol spine itself "
+author: "ontoref"
+date: "2026-06-02"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-048", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+As ontoref grew it accumulated a second git-tracked surface beyond the Rust implementation: brand assets, web site source, outreach presentations, private strategy notes, and the protocol spine itself (.ontoref/). All lived inside one flat git repo. The problems: (1) publication boundaries — code has one remote/visibility, the web site another, private strategy a third; forcing all three through one repo required either over-exposing private content or fragmenting git history with submodules. (2) spine ownership — with .ontoref/ inside code/, every change to the protocol spine became a Rust-repo commit, coupling protocol evolution to the implementation release cadence. (3) cross-area navigation — an agent scoped to code/ could not reach outreach/ or vault/ without leaving the repo; paths like '../outreach' were outside git scope. Three layout options were evaluated: (A) monorepo with subdirectories (code/, outreach/, vault/ all under one git root), (B) constellation (non-versioned parent holding independent git sub-repos), (C) external paths (each sub-repo anywhere, cross-references via absolute paths). Option B was chosen because it provides clean per-area publication boundaries — each folder is its own repo with its own remote and visibility — while keeping the spine at a level that governs all sub-repos without fragmenting it across satellites.
+
+Decision
+
+Adopt a constellation layout: a non-versioned parent directory (no .git/) holds three to five independent git sub-repos — code/, outreach/, vault/, and an assets/ directory that is not itself a git repo. The .ontoref/ protocol spine lives at the parent level, not inside code/. The bash wrapper (code/ontoref) derives SPINE_DIR from SCRIPT_DIR/../.ontoref/; env.nu guards the ONTOREF_ROOT assignment with an is-empty check so the bash wrapper's value is not overridden at module load time; nickel_import_paths in config.ncl uses ../.ontoref/... (relative to ONTOREF_ROOT=code/); NCL files in .ontoref/ that import the data-dir companion (code/ontology/) use ../../code/ontology/... relative to the spine location. Cross-project Rust path dependencies that previously resolved via ../../../ now resolve via ../../../../ (one extra hop through code/). install.nu derives repo_root from $env.CURRENT_FILE rather than $env.PWD so it is robust to invocation from an arbitrary CWD.
+
+Constraints
+
+Hard The .ontoref/ spine lives at the constellation parent, not inside code/; code/ontoref derives SPINE_DIR as SCRIPT_DIR/../.ontoref/Hard env.nu assigns ONTOREF_ROOT only when it is not already set, preserving the bash wrapper's valueHard nickel_import_paths entries for .ontoref/ content use ../.ontoref/... (relative to ONTOREF_ROOT=code/), not .ontoref/...Hard NCL files in .ontoref/ that import the data-dir companion use ../../code/ontology/... not ../../ontology/...
+
+Alternatives considered
+
+Monorepo subdirectories (option A): one git repo, code/ outreach/ vault/ as subdirs — rejected: Cannot provide independent git remotes with different visibility per area without submodules or sparse-checkout. All areas share the same git history and commit graph, which pollutes code/ history with brand/outreach changes and prevents per-area release tagging.External paths (option C): each sub-repo anywhere on the filesystem, cross-references via absolute paths — rejected: Absolute paths are machine-specific and cannot be committed to the repo. Cross-repo navigation from an agent scoped to one repo requires the user to set environment variables per machine. The spine would have to live inside one of the repos (satellite problem: others reference it via absolute path and drift when it moves).Keep spine inside code/, model outreach/vault as satellite repos referencing it — rejected: Forces outreach/ and vault/ to carry a reference to code/'s absolute path for spine resolution. Moving code/ to a new machine or renaming it breaks the satellites. The spine's authority extends beyond code/; its location should reflect that.
+
+Anti-patterns
+
+Spine as satellite — Keeping .ontoref/ inside one sub-repo (code/) and referencing it from sibling repos (outreach/, vault/) via absolute or relative cross-repo paths. The spine's authority extends to all sub-repos; scoping it to one creates a satellite dependency where moving or renaming that repo breaks all siblings.Unconstrained env.nu ONTOREF_ROOT override — env.nu unconditionally sets ONTOREF_ROOT from the file-derived path at module load time, silently overriding the bash wrapper's correctly computed value. In constellation layout this produces a wrong ONTOREF_ROOT (.ontoref/ instead of code/) that causes sync, install, and asset commands to target the wrong directory.Three-hop cross-project Cargo path dependencies — Using ../../../<peer> in Cargo.toml path deps when the crate is inside code/crates/<crate>/. With constellation, three hops reach the constellation parent (ontoref/), not the development root (Development/); peer projects are siblings of ontoref/, so four hops are required.
+
+Related ADRs
+
+ADR-032 · ADR-001 · ADR-029 · ADR-031 · ADR-028
diff --git a/site/site/content/adr/en/accepted/adr-048.ncl b/site/site/content/adr/en/accepted/adr-048.ncl
new file mode 100644
index 0000000..628ba77
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-048.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-048",
+ title = "Constellation Layout — Spine at Parent, Code as Sub-Repo",
+ slug = "048",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/048",
+ graph = {
+ implements = [],
+ related_to = ["adr-032", "adr-001", "adr-029", "adr-031", "adr-028"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-049.md b/site/site/content/adr/en/accepted/adr-049.md
new file mode 100644
index 0000000..f09dadf
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-049.md
@@ -0,0 +1,37 @@
+---
+id: "adr-049"
+title: "Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic"
+slug: "049"
+subtitle: "Accepted"
+excerpt: "The constellation-memory work introduces criteria: the conditions a project holds itself to — release gates, quality bars, coherence rules. They arrive from two directions. Some are NORMATIVE: declar"
+author: "ontoref"
+date: "2026-06-08"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-049", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The constellation-memory work introduces criteria: the conditions a project holds itself to — release gates, quality bars, coherence rules. They arrive from two directions. Some are NORMATIVE: declared requirements an author states up front ('every Accepted ADR cites a Proof'). Others are DISCOVERED: they emerge from harvested insights (the coder harvest, plan component A) and the interaction trace (ADR-037) as patterns that turned out to matter in practice. ADR-046 established the substrate view as the unit of dynamic context provisioning — a discipline mounts only the slice of levels it needs, and a level is consumed identically by CLI, agent, and daemon interfaces via the surfaces field, with eligibility 'Always or 'Learned. ADR-018 fixed level-hierarchy and describe-projection mechanics. What is missing is where criteria sit in that structure. Two errors are available. If criteria are a flat undifferentiated list, a pattern someone NOTICED (discovered, reflection axis — what BECAME salient) is indistinguishable from a requirement someone DECLARED (normative, ontology axis — what IS required), and the first silently acquires the authority of the second. If, conversely, discovered criteria can auto-promote to normative once they recur, the reflection axis starts minting requirements without a declared, witnessed act — the engine deciding what is authoritative. The project's core identity tension, ontology-vs-reflection, is engaged directly, and formalization-vs-adoption is engaged because adding a criteria level is more schema that a minimal adopter must not be forced to carry.
+
+Decision
+
+Establish criteria as a first-class describe level mounted through the substrate-view engine (ADR-046), distinguished by a typed provenance taxonomy and a gated promotion path. Four mechanisms. (1) TYPED PROVENANCE TAXONOMY — every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag, not free text. 'Normative criteria are declared requirements (ontology axis); 'Discovered criteria are harvested from insights/interaction (reflection axis) and MUST record provenance: the source insight or interaction id they were graduated from. The tag is the legibility seam: a reader always knows whether a criterion is asserted or observed. (2) CRITERIA AS A 'Describe LEVEL — register criteria in levels.ncl with kind = 'Describe and eligibility = 'Learned, added to the relevant disciplines[].levels (coding, positioning, difusion); `describe criteria --discipline <D>` projects the discipline's slice, mirroring the existing constraints projection (ADR-018). (3) PROMOTION IS GATED, NEVER AUTOMATIC — a discovered criterion may be PROPOSED for promotion to normative, but promotion is gated behind novelty-check --threshold plus human approval (the backlog propose-status/approve path, ADR-046 witnessed-reversible); recurrence alone never promotes. The reverse — a normative criterion being demoted because reality drifted — is likewise a proposal, never silent. (4) OPT-IN ABSENCE — eligibility 'Learned and an opt-in migration mean a project that declares no criteria mounts no criteria level and pays nothing; the level is absent, not empty-and-mandatory.
+
+Constraints
+
+Hard Every criterion carries kind : [| 'Normative, 'Discovered |] as a typed tag; a 'Discovered criterion additionally records the source insight or interaction id it was graduated fromHard A 'Discovered criterion becomes 'Normative only through novelty-check plus human approval via propose-status/approve; recurrence alone never promotes, and demotion of a 'Normative criterion is likewise a proposal, never silentSoft The criteria level is registered with kind 'Describe and eligibility 'Learned; a project that declares no criteria mounts no criteria level and carries no mandatory criteria schemaHard criteria are projected through `describe criteria` and consumed identically across CLI/agent/UI/MCP/GraphQL via the surfaces field, with no bespoke per-surface code
+
+Alternatives considered
+
+A flat untyped criteria list — rejected: Collapses ontology-vs-reflection: a discovered pattern becomes indistinguishable from a declared requirement and silently acquires its authority. The typed normative/discovered tag is the minimal addition that keeps both poles legible.Auto-promote discovered criteria to normative once they recur N times — rejected: Lets reflection mint requirements without a declared, witnessed act — reflection-dominates, the collapse ADR-046 C1 forbids. Recurrence is evidence; only a gated, approved act confers normative authority.A standalone criteria store outside the substrate-view engine — rejected: Reinvents discipline-scoped composition, persistence routing, partial-knowledge verification, and interface parity that ADR-046 already provides, and would need its own cross-project story. Criteria are a level; mounting them through the view engine inherits all of it.A new typed criterion_kind on qa_entry_type instead of tags, requiring a schema-field migration immediately — rejected: Premature: tagging normative/discovered on the existing qa entry type needs no required-field migration and keeps the adoption floor flat. A typed criterion_kind is a later move only if a Spiral-poled criterion field is needed (bl-009 graduation), and that is itself an ondaod-gated decision.Make criteria mandatory for all adopters to maximize ecosystem visibility — rejected: Collapses formalization-vs-adoption toward mandatory gates, violating the core.ncl node invariant 'schemas are optional layers, not mandatory gates'. eligibility 'Learned plus opt-in migration keep absence free.
+
+Anti-patterns
+
+Discovered criterion wears normative authority — A criterion harvested from insights or interaction is recorded without a provenance tag and is read as a declared requirement, silently acquiring authority it was never granted.Auto-promotion by recurrence — A discovered criterion is promoted to normative automatically once it recurs enough times, letting reflection mint authoritative requirements with no declared, witnessed act.Criteria mandatory at the adoption floor — The criteria level is made mandatory or empty-but-required for every adopter, putting new schema at the adoption floor instead of the top of the curve.
+
+Related ADRs
+
+ADR-046 · ADR-018 · ADR-024 · ADR-037 · ADR-029 · ADR-003 · ADR-050
diff --git a/site/site/content/adr/en/accepted/adr-049.ncl b/site/site/content/adr/en/accepted/adr-049.ncl
new file mode 100644
index 0000000..5ac98f6
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-049.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-049",
+ title = "Criteria as a Substrate-View Describe Level — Normative and Discovered as Typed Provenance, Promotion Gated Not Automatic",
+ slug = "049",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/049",
+ graph = {
+ implements = [],
+ related_to = ["adr-046", "adr-018", "adr-024", "adr-037", "adr-029", "adr-003", "adr-050"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-050.md b/site/site/content/adr/en/accepted/adr-050.md
new file mode 100644
index 0000000..01bb1bb
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-050.md
@@ -0,0 +1,37 @@
+---
+id: "adr-050"
+title: "Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness"
+slug: "050"
+subtitle: "Accepted"
+excerpt: "The constellation-memory work (plan 2026-06-08-constellation-memory-chronicle-criteria) introduces criteria — release/quality/coherence conditions a project holds itself to — harvested from insight"
+author: "ontoref"
+date: "2026-06-08"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-050", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The constellation-memory work (plan 2026-06-08-constellation-memory-chronicle-criteria) introduces criteria — release/quality/coherence conditions a project holds itself to — harvested from insights and the interaction trace (ADR-037) or declared as requirements. ADR-026 already established validators as first-class, three-plane, SLA-bound, with a pluggable commitment backend, and ADR-024 fixed the agent-action boundary: authoritative state mutates only through declared operations carrying a witness. What is unresolved is the boundary question that decides whether criteria-backed validation stays coherent with the protocol's identity: WHICH criteria may become executable validators, and what they are allowed to read. Two failure modes are in scope. First, a criterion that asserts TRUTHFULNESS ('the positioning is honest', 'the architecture is sound') is not structurally decidable — turning it into a validator makes the reflection axis (judgment, an act) masquerade as the ontology axis (an invariant, what IS), and a passing/failing verdict on such a claim is the engine deciding truth. Second, a validator that reads unbounded state (the whole graph, every level) is an adoption cost: it is slow, it couples validation to the daemon, and it breaks the ADR-029 local-fallback guarantee that every read has a no-daemon Nushell path. The witness seam (ADR-031/023) names the exact point where an act that mutates substance is bound to a commitment; a validator over a criterion must live on the ontology side of that seam (verify structure) while being invoked by a reflection-side act (run + witness).
+
+Decision
+
+A criterion is eligible to become an executable validator ONLY if it reduces to a bounded, structurally-decidable slice query; truthfulness criteria are rejected at the eligibility gate and remain QA entries (normative/discovered, ADR-049), never validators. Establish this via four mechanisms. (1) DECIDABILITY ELIGIBILITY GATE — a ValidatorDecl is well-formed only if its predicate is a pure function of a declared, bounded slice plus the state_root commitment; a criterion whose satisfaction depends on a judgment not reducible to structure (honesty, soundness, quality-in-the-abstract) cannot declare a ValidatorDecl and is recorded as a discovered/normative criterion only. (2) BOUNDED SLICE — every ValidatorDecl declares a slice_query naming exactly the nodes/edges/fields it reads; the Rust #[onto_validator] predicate reads ONLY that slice plus state_root and is forbidden from widening its read at runtime. The slice is the validator's read envelope, the structural analogue of ADR-046's authority envelope. (3) STRUCTURE-NOT-TRUTHFULNESS SEVERITY RULE — a validator over a criterion that core.ncl does NOT name as a Spiral tension may carry severity 'Hard (structure is decidable, so pass/fail is meaningful); a validator whose criterion touches a 'Spiral-poled question MUST be 'Soft and report direction of motion, never a 'Hard biconditional — the ondaod prohibition on hard-biconditionals on Spiral questions, applied to validators. (4) NCL-RUST COHERENCE WITNESS — the ValidatorDecl (NCL, the declared structural claim) and the #[onto_validator] predicate (Rust, the executable check) must agree; a coherence check (runtime O6) fails the inventory if a declared validator has no registered predicate or a registered predicate reads beyond its declared slice, and each validator run emits a witness binding the verdict to the state_root it was computed against (ADR-024).
+
+Constraints
+
+Hard A criterion may declare a ValidatorDecl ONLY if its predicate is a pure function of a declared bounded slice plus state_root; criteria asserting truthfulness (not reducible to structure) are rejected and remain QA entries, never validatorsHard Every ValidatorDecl declares a slice_query naming the nodes/edges/fields it reads; the #[onto_validator] predicate reads only that slice plus state_root and never widens its read at runtimeHard A validator whose underlying criterion touches a question core.ncl names as a 'Spiral tension must be severity 'Soft and report direction of motion; 'Hard biconditional pass/fail is forbidden for Spiral-touching criteriaHard The inventory fails if a declared ValidatorDecl has no registered #[onto_validator] predicate, or a registered predicate reads beyond its declared slice; every validator run emits a witness binding the verdict to the state_root it was computed against
+
+Alternatives considered
+
+Allow any criterion to become a validator, let authors choose severity freely — rejected: Collapses ontology-vs-reflection toward reflection-dominates: a truthfulness criterion with a Hard pass/fail makes the engine adjudicate truth, and a verdict on an undecidable claim circulates as if it were an invariant. The decidability gate exists precisely to keep validators on the ontology axis.Let validators read whole state for simplicity, optimize later — rejected: Breaks ADR-029 local-fallback and Protocol-not-Runtime: an unbounded validator couples validation to the daemon and to full-graph materialization, putting infrastructure at the adoption floor. Bounded slices are the same authority-envelope discipline ADR-046 established for reads.Use a check_hint string per criterion (the deprecated constraint pattern) — rejected: check_hint is untyped and unexecutable — it cannot enforce the slice bound, cannot be coherence-checked against a Rust predicate, and cannot emit a witness. The typed ValidatorDecl is what makes the structural claim and its execution bindable at the witness seam.Make every criterion-validator Soft to be safe — rejected: Over-collapses toward never-blocking: a structurally-decidable criterion (no over-read, NCL↔Rust coherent, bounded) is genuinely Hard-checkable, and forcing it Soft discards a real guarantee. The severity rule draws the line by whether the criterion touches a Spiral question, not by blanket caution.Adjudicate truthfulness via an LLM-judge validator — rejected: Maximally collapses ontology-vs-reflection: a model verdict on honesty/soundness is pure reflection issuing an ontology-grade pass/fail, unreproducible and unwitnessable against a state_root. Truthfulness criteria stay human-facing QA entries; no mechanical adjudication.
+
+Anti-patterns
+
+Validator adjudicates truth — A criterion asserting truthfulness (honesty, soundness, quality-in-the-abstract) is promoted to an executable validator that issues a pass/fail verdict, making reflection's judgment circulate as an ontology-grade invariant.Unbounded validator read — A validator predicate reads the whole graph or arbitrary levels rather than a declared bounded slice, coupling validation to full-graph materialization and the daemon and breaking local fallback.Hard biconditional on a Spiral criterion — A validator over a criterion that touches a named Spiral tension is declared 'Hard with an A ⟺ B pass/fail, collapsing the tension by fiat instead of reporting direction of motion.Declared but unwitnessed verdict — A ValidatorDecl exists with no registered predicate, or a predicate over-reads its slice, or a run produces a verdict not bound to a state_root — a verdict that cannot be reproduced or audited.
+
+Related ADRs
+
+ADR-026 · ADR-024 · ADR-023 · ADR-031 · ADR-029 · ADR-046 · ADR-037 · ADR-049
diff --git a/site/site/content/adr/en/accepted/adr-050.ncl b/site/site/content/adr/en/accepted/adr-050.ncl
new file mode 100644
index 0000000..f093e2a
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-050.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-050",
+ title = "Witnessed Validators for Hard Criteria — Structural Decidability and Bounded Slices, Never Truthfulness",
+ slug = "050",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/050",
+ graph = {
+ implements = [],
+ related_to = ["adr-026", "adr-024", "adr-023", "adr-031", "adr-029", "adr-046", "adr-037", "adr-049"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-051.md b/site/site/content/adr/en/accepted/adr-051.md
new file mode 100644
index 0000000..44c452f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-051.md
@@ -0,0 +1,37 @@
+---
+id: "adr-051"
+title: "Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human"
+slug: "051"
+subtitle: "Accepted"
+excerpt: "The constellation-memory work folds a chronicle (plan component B): companion-NCL milestones, harvested insights, the interaction trace (ADR-037), ADRs-by-date, and migrations-by-number woven into a re"
+author: "ontoref"
+date: "2026-06-08"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-051", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The constellation-memory work folds a chronicle (plan component B): companion-NCL milestones, harvested insights, the interaction trace (ADR-037), ADRs-by-date, and migrations-by-number woven into a retrospective timeline of what the project has actually DONE. Independently, ADR-043 established positioning as a four-element framework — orthogonal axes (Qué / Para-Quién / Cómo) bound by a Proof seam — and ADR-035 made positioning a queryable protocol surface; positioning.nu already runs five coherence rules, including that a differentiator or value-prop marked proven must converge on a Proof whose evidence_adr is Accepted. There is an obvious and dangerous adjacency: the chronicle KNOWS what shipped (accepted ADRs, completed milestones), and positioning NEEDS proofs that what it claims is real. The temptation is to let the chronicle auto-populate positioning Proofs — every accepted ADR becomes a proof of the value-prop it touches. That collapses ontology-vs-reflection: the chronicle is reflection (what BECAME, a record of history) and a positioning Proof is an ontology-side claim about what the project IS and delivers; auto-asserting proofs from history makes reflection decide the truth of outward claims. The reality-collapses-intent hazard is also present in mirror form — a milestone that shipped is evidence FOR a proof, but a milestone with no positioning home is not automatically a defect to be erased; it may simply be internal work the positioning layer never intended to claim. What is missing is a seam that lets history INFORM positioning without history ASSERTING positioning.
+
+Decision
+
+Establish a one-directional propose-only seam from the chronicle to the positioning Proof layer, verified by the existing positioning audit and accepted only by a human. Three mechanisms. (1) PROOF CANDIDATES, NOT PROOFS — `coder chronicle --as-proof-candidates` emits accepted ADRs and completed milestones as CANDIDATE proofs wired against the ADR-043 four-element structure (which differentiator/value-prop each could support); a candidate is a proposal, never an accepted Proof, and is materialized only through the positioning layer's own acceptance path, never written directly by the chronicle. (2) THE AUDIT VERIFIES, THE CHRONICLE DOES NOT — a proof candidate is checked by positioning audit's existing five rules (ADR-043): a candidate that would create a DanglingRef, an UnprovenValidatedAudience, or an AnchorDrift is surfaced as such and cannot be accepted until the inconsistency is resolved. The chronicle proposes; the audit is the verifier; structural verification is positioning's job, not the chronicle's. (3) ORPHAN MILESTONES WARN, NEVER ERASE — a `positioning roadmap` (sibling to backlog roadmap) lays mechanisms against dated milestones, and an orphan-milestone rule in positioning audit WARNS (warn-only severity) when a shipped milestone has no positioning home — it never deletes the milestone nor auto-creates a claim, because internal work the positioning layer never intended to claim is a legitimate half-state, not a defect. G reads outreach/.coder only and writes nothing into outreach (no spine there).
+
+Constraints
+
+Hard The chronicle emits proof CANDIDATES only; a candidate is never written as an accepted Proof and is materialized solely through the positioning layer's own human acceptance pathHard Proof candidates are verified by positioning audit's existing five coherence rules (ADR-043); the chronicle does not run its own proof-coherence checksSoft A shipped milestone with no positioning home is surfaced as a warning by positioning audit; it is never deleted and never auto-claimedHard The chronicle-to-positioning seam is one-directional and confined to the spine; G reads outreach/.coder and writes nothing into outreach
+
+Alternatives considered
+
+Auto-create positioning Proofs from every accepted ADR / completed milestone — rejected: Collapses ontology-vs-reflection toward reflection-dominates: history would assert the truth of outward claims with no human acceptance. Proofs are ontology-side claims; only a declared act on the positioning side confers proof status.Let the chronicle run its own proof-coherence checks — rejected: Forks verification away from positioning audit (ADR-043), risking divergent rules and two sources of positioning truth. The audit already encodes the five coherence rules; candidates must be verified there.Treat orphan milestones as defects and auto-remove or auto-claim them — rejected: The reality-collapses-intent violation: a shipped milestone with no positioning home is a legitimate half-state (internal work), not a defect. Warn-only describes the gap and leaves the judgement to a human.Write proof candidates directly into outreach/ where the difusion site lives — rejected: outreach has no spine and is a separate repo; writing into it reintroduces the cross-project mutation hazard ADR-046 resolves. The seam reads outreach as evidence and writes only within the spine.
+
+Anti-patterns
+
+History asserts a proof — An accepted ADR or completed milestone is auto-written as an accepted positioning Proof, so the chronicle (reflection) decides the truth of an outward claim (ontology) with no human acceptance.Chronicle self-verifies claims — The chronicle runs its own proof-coherence logic instead of routing candidates through positioning audit, creating a second, divergent source of positioning truth.Orphan milestone erased or force-claimed — A shipped milestone with no positioning home is treated as a defect and deleted or auto-claimed, erasing legitimate internal-work intent because it lacks an outward claim.Write into a spineless repo — The seam writes proof candidates or claims into outreach/, a separate repo with no spine, reintroducing the cross-project mutation hazard.
+
+Related ADRs
+
+ADR-043 · ADR-035 · ADR-024 · ADR-046 · ADR-037 · ADR-048 · ADR-052
diff --git a/site/site/content/adr/en/accepted/adr-051.ncl b/site/site/content/adr/en/accepted/adr-051.ncl
new file mode 100644
index 0000000..05c615e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-051.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-051",
+ title = "Chronicle-to-Proof-Candidate Seam — History Proposes Positioning Proofs, the Audit Verifies, Acceptance Stays Human",
+ slug = "051",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/051",
+ graph = {
+ implements = [],
+ related_to = ["adr-043", "adr-035", "adr-024", "adr-046", "adr-037", "adr-048", "adr-052"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-052.md b/site/site/content/adr/en/accepted/adr-052.md
new file mode 100644
index 0000000..6cd9b87
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-052.md
@@ -0,0 +1,37 @@
+---
+id: "adr-052"
+title: "Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them"
+slug: "052"
+subtitle: "Accepted"
+excerpt: "The constellation-memory work closes a loop: harvest insights (component A), fold them with the interaction trace into a chronicle (B), graduate them into criteria (C/ADR-049), and surface proof candid"
+author: "ontoref"
+date: "2026-06-08"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-052", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The constellation-memory work closes a loop: harvest insights (component A), fold them with the interaction trace into a chronicle (B), graduate them into criteria (C/ADR-049), and surface proof candidates (G/ADR-051). The question this ADR answers is what happens at the END of a working session — at `mode complete` of the coder-workflow (ADR-011 mode guards/convergence). The accumulated reflection (chronicle, criteria, trace) now contains signals that bear on authoritative ONTOLOGY state: an FSM dimension in state.ncl may have met its transition condition; a backlog item may be completable; a proof candidate may be citable. The reflexive temptation — and the thing that would make the loop feel 'intelligent' — is to have the loop APPLY these: advance the FSM dimension, close the backlog item, attach the proof. That is the precise collapse the project's identity tension forbids. The whole memory loop is reflection (what BECAME — memory, drift); state.ncl, the backlog status, and accepted proofs are ontology (what IS — authoritative state). ADR-024 fixed that authoritative state mutates ONLY through declared operations carrying a witness; ADR-046 fixed authority as declared and learning as tuning within a declared envelope (C1) recorded as witnessed, reversible operations (C2). backlog.nu already ships propose-status and approve — a propose/approve seam built for exactly this. What is missing is the rule binding the loop to that seam: the loop is allowed to NOTICE and PROPOSE, and forbidden to APPLY.
+
+Decision
+
+At `mode complete` the memory feedback loop emits PROPOSED deltas and never applies them; authoritative state changes only through the existing witnessed approve path. Four mechanisms. (1) PROPOSE-ONLY HOOK — a `mode complete` proposal hook in run.nu surfaces candidate deltas of three kinds — FSM-transition proposals against state.ncl dimensions, backlog-close proposals, and proof-citation proposals (ADR-051) — each as a proposal record via backlog propose-status, NEVER as a direct mutation of state.ncl, the backlog status, or the proof set. (2) APPROVE IS THE ONLY MUTATION PATH — a proposal becomes an authoritative state change only through approve, which is a declared operation carrying a witness (ADR-024); the loop has no write path to authoritative state that bypasses approve. (3) PARTIAL REALIZATION IS DESCRIBED, NOT COLLAPSED — when harvested reality diverges from a declared state or claim (a dimension's blocker is gone but its catalyst is unmet; a milestone shipped that no proof claims), the loop DESCRIBES the half-state and proposes the delta that would move it; it never drops the declared intent because reality differs, and it never fabricates a transition to make reality and intent agree. (4) IGNORING THE LOOP IS ALSO A FAILURE MODE — the hook always RUNS at mode complete and always SURFACES its proposals (even if none are accepted), so the opposite collapse — Yang decide-and-commit blind to drift — is structurally prevented: the proposals are presented, the human disposes.
+
+Constraints
+
+Hard The mode-complete hook emits FSM-transition, backlog-close and proof-citation deltas only as proposals via propose-status; it has no path to mutate state.ncl, backlog status, or the proof set directlyHard An FSM transition, backlog close, or proof citation proposed by the loop becomes authoritative only through approve, a declared operation carrying a witness (ADR-024); no silent or timeout-based applicationHard When harvested reality diverges from a declared state or claim, the loop describes the half-state and proposes the delta; it never drops declared intent because reality differs nor fabricates a transition to reconcile themSoft The mode-complete hook always runs and always surfaces its proposals (even when none are accepted); proposals cannot be silently skipped
+
+Alternatives considered
+
+Auto-apply deltas the loop is confident about (advance FSM, close backlog, attach proof) — rejected: The defining collapse: reflection (the loop) mutating authoritative ontology state directly, bypassing the ADR-024 witnessed-operation boundary. Confidence is not authority; only approve confers it.Drop or rewrite a declared state/claim when harvested reality contradicts it — rejected: The reality-collapses-intent ondaod violation: the half-state is a location on the flow, not a defect. The loop describes partial realization and proposes; it never erases declared intent to match reality.Let the loop's proposals be optional / skippable at mode complete — rejected: Permits the mirror collapse toward Yang freeze — the project silently ignoring real drift. The hook always runs and always surfaces proposals so the reflection signal is never invisible, even when declined.Build a dedicated proposal store and approval UI outside backlog — rejected: Duplicates the propose-status/approve seam backlog.nu already ships and the ADR-024 witnessed-operation path. Reusing them keeps a single audited mutation channel rather than a second one to secure.Require human acceptance but apply silently if not declined within a window — rejected: A timeout-to-apply is auto-apply with a delay — it still lets reflection mutate authoritative state without a positive witnessed act. Approve must be an explicit declared operation, never a default-on-silence.
+
+Anti-patterns
+
+Loop applies a delta — The mode-complete loop advances an FSM dimension, closes a backlog item, or attaches a proof directly, mutating authoritative ontology state without a declared, witnessed approve.Intent erased to match reality — A declared state or claim is dropped or rewritten because harvested reality contradicts it, collapsing a legitimate half-state into a fabricated agreement between intent and reality.Drift silently ignored — The loop's proposals can be skipped without ever being surfaced, so real divergence between reflection and ontology is invisible and the project commits blind to drift.Timeout-to-apply — A proposal is applied automatically if not declined within a window, making silence a positive authorization and reintroducing unwitnessed mutation through the back door.
+
+Related ADRs
+
+ADR-024 · ADR-046 · ADR-011 · ADR-025 · ADR-037 · ADR-029 · ADR-049 · ADR-051
diff --git a/site/site/content/adr/en/accepted/adr-052.ncl b/site/site/content/adr/en/accepted/adr-052.ncl
new file mode 100644
index 0000000..60c8733
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-052.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-052",
+ title = "Memory Feedback Loop — On Mode Completion the Loop Proposes State, Backlog and Proof Deltas, Never Applies Them",
+ slug = "052",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/052",
+ graph = {
+ implements = [],
+ related_to = ["adr-024", "adr-046", "adr-011", "adr-025", "adr-037", "adr-029", "adr-049", "adr-051"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-053.md b/site/site/content/adr/en/accepted/adr-053.md
new file mode 100644
index 0000000..eda3701
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-053.md
@@ -0,0 +1,37 @@
+---
+id: "adr-053"
+title: "Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend"
+slug: "053"
+subtitle: "Accepted"
+excerpt: "The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-04"
+author: "ontoref"
+date: "2026-06-08"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-053", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The constellation-memory work produces several things a project will want to SEE: a chronicle/timeline (component B), a roadmap (backlog roadmap + positioning roadmap, ADR-051), a criteria view (ADR-049), history and dependency graphs. The naive path is to write a bespoke Tera page, handler, and route per view, cloning graph.html each time and hard-coding the data source and the link wiring in Rust. That path has two costs the protocol has repeatedly refused. First, it puts presentation logic in Rust, so a DOMAIN project that wants its own panel (a difusion roadmap, a custom board) must fork the engine — violating Protocol-not-Runtime and the voluntary, additive adoption model (ADR-029). Second, it makes the rendered surface drift from the declared knowledge: the panel becomes a second source of truth in Rust rather than a projection of NCL. ADR-046 established the precedent that resolves this — a level, a UI panel, and an MCP tool are all the same declared substrate-view consumed via the surfaces field, not bespoke code — and the existing graph.html already renders an NCL-derived graph generically through the NclCache pipeline (NCL → JSON → {{json|safe}} → Cytoscape). ADR-034 (Proposed) generalizes the catalog with a kind discriminator so non-Rust operation kinds are declarable. What is missing is the analogous declarative model for PRESENTATION: a spec the engine reads generically so chronicle/roadmap/criteria/graph panels render across CLI/UI/GraphQL/md without per-panel Rust, and a domain project extends by shipping its own panel NCL file.
+
+Decision
+
+Establish a presentation-spec (schema .ontoref/reflection/schemas/presentation.ncl) as the single declarative panel model the engine reads generically, so adding a panel is dropping an NCL file, never editing Rust. Four mechanisms. (1) ONE PANEL MODEL — a Panel declares { id, title, kind ([| 'Timeline, 'Gantt, 'Graph, 'Table, 'Board |]), data_source ({op, params} naming a catalog operation, ADR-024/030), surfaces (which interfaces expose it), reference_links ([{from_field, to_kind, resolver}] for click-through wiring), filters, actors }; the engine renders any panel from this declaration through the existing NclCache → JSON pipeline (graph.html precedent) with no panel-specific code. (2) GENERIC RENDER, NO PER-PANEL RUST — the render path branches on kind, not on panel identity; the day a specific panel needs bespoke Rust is the day the model has failed, so a new panel-*.ncl appears in CLI, UI, and GraphQL with zero Rust changes (the keystone verification). (3) DUAL-PATH BRIDGE, ONE LIST — panels may be declared either as dedicated .ontoref/presentation/panel-*.ncl files OR referenced from config.ncl quick_actions via a panel_ref, OR surfaced from a substrate-view surface; all three NORMALIZE to one panel list, so there is exactly one resolved set of panels regardless of declaration site. (4) DOMAIN EXTENSION WITHOUT FORK — a consumer project adds panels by shipping its own .ontoref/presentation/*.ncl; the protocol's engine renders them unchanged, and a project that ships none gets the base panels and pays nothing. The data_source MUST name a declared catalog operation, so a panel is a projection of declared knowledge, never an independent Rust query.
+
+Constraints
+
+Hard The engine renders any panel generically from its presentation-spec declaration through the NclCache→JSON pipeline; the render path branches on kind, never on panel identity, so a new panel-*.ncl requires zero Rust changesHard Every Panel.data_source names a declared catalog operation (ADR-024/030); a panel is a projection of declared knowledge and never an independent Rust queryHard Panels declared as dedicated panel-*.ncl, referenced via quick_actions.panel_ref, or surfaced from a substrate-view surface all normalize to exactly one resolved panel list; no declaration site forms a separate registrySoft A consumer project adds panels solely by shipping .ontoref/presentation/*.ncl rendered by the unchanged engine; a project that ships none gets the base panels and carries no mandatory presentation schema
+
+Alternatives considered
+
+A bespoke Tera page + handler + route per view, cloning graph.html each time — rejected: Puts presentation logic in Rust, forcing a domain project to fork the engine to add a panel (violating Protocol-not-Runtime and additive adoption) and letting the rendered surface drift from declared NCL. The spec keeps panels declarative and generically rendered.Let each panel's data_source be an arbitrary Rust query rather than a declared catalog operation — rejected: Makes the panel an independent source of truth in Rust, drifting from the declared knowledge graph. Requiring a declared catalog operation (ADR-024/030) keeps every panel a projection of ontology.Allow only dedicated panel-*.ncl files; drop the quick_actions / view-surface paths — rejected: Ignores that panels are legitimately referenced from quick_actions and substrate-view surfaces today; forbidding those paths fragments the surface. The dual-path bridge normalizes all three into one list instead of forbidding two.Branch the render path on panel id with per-panel special cases — rejected: Reintroduces per-panel Rust by the back door and guarantees drift as panels accumulate. Branching only on kind keeps the engine generic; a new panel needs no engine change, a new visualization needs only a new kind.Make the presentation-spec mandatory for all adopters — rejected: Raises the adoption floor, collapsing formalization-vs-adoption toward mandatory gates. A project that ships no panels gets the base set and pays nothing; the spec is opt-in expressiveness.
+
+Anti-patterns
+
+Per-panel Rust — A panel's data or click-through links are hard-coded in Rust, or the render path branches on panel identity, so adding or changing a panel requires editing the engine and the surface drifts from declared NCL.Panel as an independent query — A panel's data_source is an arbitrary Rust query rather than a declared catalog operation, making the panel a second source of truth that can diverge from the knowledge graph.Divergent panel registries — Panels declared via dedicated files, quick_actions, and view surfaces are resolved by separate code paths into separate lists, so the set of panels depends on where you look.Presentation mandatory at the floor — The presentation-spec is required of every adopter, raising the adoption floor instead of adding opt-in expressiveness at the top of the curve.
+
+Related ADRs
+
+ADR-046 · ADR-034 · ADR-024 · ADR-030 · ADR-029 · ADR-018 · ADR-049 · ADR-051
diff --git a/site/site/content/adr/en/accepted/adr-053.ncl b/site/site/content/adr/en/accepted/adr-053.ncl
new file mode 100644
index 0000000..c0ef032
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-053.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-053",
+ title = "Presentation-Spec as the Config-Driven Rendering Surface — One Declared Panel Model, Many Surfaces, No Engine Fork to Extend",
+ slug = "053",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/053",
+ graph = {
+ implements = [],
+ related_to = ["adr-046", "adr-034", "adr-024", "adr-030", "adr-029", "adr-018", "adr-049", "adr-051"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-054.md b/site/site/content/adr/en/accepted/adr-054.md
new file mode 100644
index 0000000..9db2708
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-054.md
@@ -0,0 +1,37 @@
+---
+id: "adr-054"
+title: "Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component"
+slug: "054"
+subtitle: "Accepted"
+excerpt: "The ontoref-daemon already exposes the full surface a native host would wrap: an axum HTTP /ui/ with rendered pages (graph, api_catalog, actions, login), a unified key-to-session auth model (ADR-005, A"
+author: "ontoref"
+date: "2026-06-09"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-054", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The ontoref-daemon already exposes the full surface a native host would wrap: an axum HTTP /ui/ with rendered pages (graph, api_catalog, actions, login), a unified key-to-session auth model (ADR-005, ADR-021 UI lift-out into ontoref-ui), a NATS event fabric (platform-nats, feature-gated), an actor notification barrier (ADR-002), a passive drift watcher that emits exactly the events worth surfacing to a user (doc drift vs ontology node descriptions, gate-state changes), a multi-project daemon (ONTOREF_PROJECT_ROOT + remote-projects.ncl), and a Quick Actions catalog. The sibling project provisioning ships provisioning-desktop: ~3500 LOC of thin Tauri 2 host that wraps its daemon's /ui/ in a webview and adds the four things a browser cannot — system tray, native OS notifications, supervision of the local daemon child process (graceful SIGTERM), and native keyring — while the same lib compiles for iOS/Android via tauri::mobile_entry_point. The question is whether ontoref should grow an equivalent ontoref-desktop. The prerequisites already exist, so the decision is not feasibility but placement and identity: where such a crate lives, what it may depend on, and which named tension it occupies. Three options were evaluated: (A) no native host — users open the daemon UI in a browser; (B) a native host placed inside the protocol adoption surface, linking protocol internals or bundled into the daemon; (C) a from-scratch native UI (egui/iced) reimplementing the views in Rust. Option D — a thin Tauri shell as a separate consumer that wraps the daemon /ui/ and surfaces drift-watcher events as native notifications — is chosen.
+
+Decision
+
+Permit a native shell (ontoref-desktop, with the same Tauri lib targeting desktop and mobile) as a strictly separate consumer of the daemon, modeled on provisioning-desktop. It MUST consume the daemon over HTTP and NATS only — wrapping the existing /ui/ in a webview, bridging NATS/drift-watcher events to native OS notifications and a system tray, supervising the local daemon as a child process, and managing one local-spawn plus N remote-connect connection profiles against the multi-project daemon. It MUST NOT be a dependency of ontoref-ontology, ontoref-reflection, or the daemon: the dependency arrow points shell -> daemon, never the reverse, and never into the protocol-minimal adoption surface (ADR-001). It MAY depend on ontoref-ui for shared presentation. It lives either as its own constellation sub-repo (sibling to code/, per ADR-048) or as a workspace crate explicitly excluded from the installed/adopted set — in both cases outside the protocol footprint a consumer project receives. Within positioning (ADR-035/043) the desktop/mobile surface is declared as a consumer audience and the candidate occupant of the openness-vs-sustainability capture seam, while the protocol itself stays unmetered.
+
+Constraints
+
+Hard ontoref-ontology must never list a desktop/native-shell crate as a dependencyHard ontoref-reflection must never list a desktop/native-shell crate as a dependencyHard ontoref-daemon must never depend on the desktop/native-shell crate; the dependency arrow points shell -> daemon onlyHard The native shell lives as its own constellation sub-repo or as a workspace crate explicitly excluded from the installed/adopted protocol footprint, consuming the daemon over HTTP/NATS onlySoft When the native surface occupies the openness-vs-sustainability seam, it does so as a declared consumer audience in positioning; the protocol surface itself is never metered
+
+Alternatives considered
+
+No native host — users open the daemon /ui/ in a browser (option A) — rejected: Forfeits the four browser-impossible capabilities, chief among them native notifications for the drift watcher and ADR-002 barrier. The daemon already produces the events; leaving them trapped behind a manually-refreshed page wastes the notification-barrier design.Native host inside the protocol adoption surface — linking protocol internals or bundled into the daemon (option B) — rejected: Violates Protocol-Not-Runtime and ADR-001: it would make a GUI consumer part of the surface a consumer project adopts, coupling the minimal protocol crates to a heavy platform toolchain. The dependency arrow would point the wrong way (protocol -> host).From-scratch native UI in egui/iced reimplementing the views (option C) — rejected: Duplicates the daemon /ui/ and ontoref-ui, creating two view surfaces that drift apart. High maintenance, no single source of truth for the graph/catalog/actions pages, and it discards the reason a thin Tauri host stays small.
+
+Anti-patterns
+
+Native host inside the adoption surface — Placing the desktop/mobile crate beside the protocol-minimal crates, or making ontoref-ontology/-reflection/the daemon depend on it, or bundling it into the daemon. This inverts the shell -> daemon arrow and drags a platform/webview toolchain into the surface a consumer project adopts, breaking Protocol-Not-Runtime.Native view-surface fork — Reimplementing the daemon /ui/ views (graph, api_catalog, actions, qa) natively in egui/iced instead of wrapping the existing HTML in a webview. Creates two view surfaces that drift apart and discards the single-source-of-truth that keeps the host thin.Metering the protocol to capture value — Putting the openness-vs-sustainability capture on the protocol surface itself — gating tier-0 adoption, schemas, or describe queries behind payment — instead of on a consumer surface. Collapses the Spiral toward Yang by tolling the gift that produces the visibility funnel.
+
+Related ADRs
+
+ADR-002 · ADR-001 · ADR-021 · ADR-016 · ADR-038 · ADR-029 · ADR-035 · ADR-043 · ADR-048 · ADR-031
diff --git a/site/site/content/adr/en/accepted/adr-054.ncl b/site/site/content/adr/en/accepted/adr-054.ncl
new file mode 100644
index 0000000..399d4fc
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-054.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-054",
+ title = "Desktop/Mobile Native Shell as a Consumer Surface — Tauri Host over the Daemon, Never a Protocol Component",
+ slug = "054",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/054",
+ graph = {
+ implements = [],
+ related_to = ["adr-002", "adr-001", "adr-021", "adr-016", "adr-038", "adr-029", "adr-035", "adr-043", "adr-048", "adr-031"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-055.md b/site/site/content/adr/en/accepted/adr-055.md
new file mode 100644
index 0000000..4f564b9
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-055.md
@@ -0,0 +1,86 @@
+---
+id: "adr-055"
+title: "Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis"
+slug: "055"
+subtitle: "Accepted"
+excerpt: "ADR-043 modelled positioning as three orthogonal axes (WHAT / FOR WHOM / HOW)"
+author: "ontoref"
+date: "2026-06-09"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-055", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-043 modelled positioning as three orthogonal axes (WHAT / FOR WHOM / HOW)
+bound by a single Proof seam. That framework has a structural silence: it can
+say what the project claims, to whom, how it spreads, and whether the claim is
+real — but it has no place to say whether any of it SUSTAINS the maker. There
+was no model of compensation, direct or indirect. Decisions about pricing,
+metered surfaces, visibility-as-funnel, or a commercial consumer were therefore
+reasoned entirely outside the graph and improvised.
+This silence was named in `.ontoref/ontology/core.ncl` as the Spiral tension
+`openness-vs-sustainability`: the protocol gives value away to maximise reach
+(voluntary-adoption, the personal-ontology audience, difusión as visibility)
+while the project that maintains it must capture enough value to sustain itself.
+The Yin pole (openness) is the engine of visibility; the Yang pole
+(sustainability) is the engine of compensation; they feed one loop. With the
+tension named but no positioning element to attach it to, the tension stayed
+claim-only.
+The load-bearing risk is identical to ADR-043's and one step sharper: a money
+dimension on the marketing surface invites classifying every claim by its
+business model — the Yang pole-collapse the named tension exists to prevent. The
+framing that resolves it is geometric: compensation is not a facet of a claim
+(an axis) but an outcome of convergence (a seam).
+
+Decision
+
+Model viability as a SECOND seam, `ViabilityPath`, parallel to `Proof` — not a
+fourth orthogonal axis. Where Proof answers "is the claim real?" (validation seam
+over the three axes), a ViabilityPath answers "does the claim sustain its maker?"
+(sustainability seam over the same three axes). Both reference the axes via
+`converges` and are referenced by nothing — self-similar to witness-as-axis-seam
+(ADR-031) and to Proof (ADR-043).
+Kind — `ViabilityPath` (one file per declared sustaining flow), under
+ `.ontoref/positioning/viability/`.
+ converges — { audience_ids, mechanism_ids, value_prop_ids } each non-empty:
+ FOR WHOM × HOW × (WHAT×WHOM). A sustaining flow is an audience
+ reached by a difusión mechanism monetising a value-prop, or it is
+ not a path.
+ directness — 'Direct | 'Indirect, mapping to the two poles of
+ openness-vs-sustainability. 'Indirect is the Yin/open pole
+ (visibility, funnel, reputation); 'Direct is the Yang/governance
+ pole (metered payment for the witness/audit surface).
+ flow_kind — HostedService | Support | Consulting | DualLicense | Sponsorship
+ | Funnel | Reputation. Descriptive vocabulary of compensation
+ forms, NOT a per-claim business-model decision.
+ status — 'Draft (a hypothesis about how positioning could sustain the
+ project) until a real, measured `realized_outcome` promotes it —
+ exactly as an Audience is 'Hypothesis until a Proof validates it.
+The realized-status gate is one-directional: a 'Validated|'Live path MUST carry
+`realized_outcome`; a 'Draft path may omit it. It is deliberately NOT a Hard
+biconditional (`outcome ⟺ realized`) on the Spiral question — that would be an
+ondaod forbidden pattern. Capture lands on a commercial consumer or the
+witness/audit tier, never on the open protocol surface: protocol-not-runtime and
+voluntary-adoption are preserved by construction.
+The kind is additive and opt-in (ADR-029): a project with no `viability/`
+directory pays zero adoption cost, and every existing positioning file validates
+unchanged. No migration is required (same precedent as ADR-043's additive kinds).
+
+Constraints
+
+Hard Viability MUST be modelled as a seam: `ViabilityPath` references the three axes via `converges` and MUST NOT be referenced by any other kind, and MUST NOT be promoted into a fourth orthogonal axis co-equal to WHAT / FOR WHOM / HOW. The schema keeps it parallel to Proof.Hard Every ViabilityPath MUST converge >=1 audience, >=1 mechanism and >=1 value-prop. The make_viability_path contract (`_viability_converges_all`) enforces the triple convergence.Hard Every ViabilityPath MUST cite >=1 linked_node — typically `openness-vs-sustainability`. The make_viability_path contract (`_viability_anchored`) enforces this.Soft The realized-status gate MUST stay one-directional: a 'Validated|'Live ViabilityPath requires `realized_outcome`, but the presence of an outcome MUST NOT be tightened into a Hard biconditional (`outcome ⟺ realized`) on the Spiral question. The `_realized_requires_outcome` contract enforces the implication only.Soft A 'Direct ViabilityPath MUST land compensation on a commercial consumer or the witness/audit tier, never on the open protocol surface itself. Metering the protocol would break protocol-not-runtime and voluntary-adoption.
+
+Alternatives considered
+
+Viability as a fourth orthogonal axis (a fourth folder co-equal to WHAT / FOR WHOM / HOW) — rejected: Compensation is not parallel to the three axes — it is where they converge and return a flow. A fourth axis implies a false fourth pole and invites classifying every claim by its business model, the exact Yang pole-collapse openness-vs-sustainability exists to prevent. The Proof-parallel seam is faithful to witness-as-axis-seam.Viability as an attribute on existing kinds (a compensation_kind field on Audience, or an outcome variant on Proof) — rejected: Scatters the sustainability question across kinds and couples it to validation. The seam property — references the axes, referenced by none, with its own Draft→realized lifecycle — is lost. A path that monetises an audience×mechanism×value-prop convergence needs to be a first-class artefact, not a field bolted onto one axis.Reuse Proof with a new proof_kind = 'Revenue — rejected: Proof answers 'is the claim real?'; viability answers 'does the claim sustain its maker?'. Overloading Proof conflates validation with viability and breaks the clean question each seam owns. Two questions, two costuras.Make the realized_outcome gate a Hard biconditional (outcome present ⟺ status realized) — rejected: openness-vs-sustainability is a Spiral tension; a Hard biconditional on it is an ondaod forbidden pattern. The one-directional gate (realized ⇒ outcome) reports direction of motion without collapsing the Spiral — a Draft path is a legitimate hypothesis that may carry no outcome yet.
+
+Anti-patterns
+
+Modelling Compensation as a Fourth Orthogonal Axis — A contributor adds a fourth positioning folder co-equal to WHAT / FOR WHOM / HOW and starts tagging each claim with a business model. The axis count silently becomes four, the seam geometry is lost, and money becomes a per-claim classification — the Yang pole-collapse openness-vs-sustainability exists to prevent.A Realized Viability Path Without a Measured Outcome — A ViabilityPath is promoted to 'Validated|'Live to look like the project is sustained, but no real measured `realized_outcome` backs it. Sustenance is asserted, not earned — the same failure mode as a Validated Audience with no Proof.Putting Direct Compensation on the Open Protocol Surface — A 'Direct viability path meters the protocol itself (the schemas, the CLI, the adoption surface) rather than a commercial consumer or the witness/audit tier. This collapses openness-vs-sustainability onto the Yang pole and voids protocol-not-runtime and voluntary-adoption.Tightening the Realized Gate into a Hard Biconditional — A contributor 'strengthens' the realized gate to outcome ⟺ realized, so that any path carrying an outcome string is forced to a realized status (and vice versa). This is a Hard biconditional on a Spiral question — it collapses the continuous flow and forbids the legitimate Draft-with-early-evidence half-state.
+
+Related ADRs
+
+ADR-043 · ADR-035 · ADR-031 · ADR-029 · ADR-054
diff --git a/site/site/content/adr/en/accepted/adr-055.ncl b/site/site/content/adr/en/accepted/adr-055.ncl
new file mode 100644
index 0000000..60ffb8e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-055.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-055",
+ title = "Positioning Viability Seam — Compensation as a Second Costura Parallel to Proof, Not a Fourth Axis",
+ slug = "055",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/055",
+ graph = {
+ implements = [],
+ related_to = ["adr-043", "adr-035", "adr-031", "adr-029", "adr-054"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-056.md b/site/site/content/adr/en/accepted/adr-056.md
new file mode 100644
index 0000000..5568fa0
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-056.md
@@ -0,0 +1,85 @@
+---
+id: "adr-056"
+title: "Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust"
+slug: "056"
+subtitle: "Accepted"
+excerpt: "The protocol's trust model has, until now, been distributed across four"
+author: "ontoref"
+date: "2026-06-10"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-056", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The protocol's trust model has, until now, been distributed across four
+decisions without ever being named as one: the verifiable substrate and its
+Merkle/witness commitments (ADR-023), the operations layer that binds every
+authoritative mutation to a signed acting-binding (ADR-024), the three
+validation planes (ADR-026), and the ontology/project layer separation that
+lets one project verify another by witness rather than by cloning it (ADR-028).
+Each of these embodies the same underlying stance, but the stance itself was
+implicit, citable only by reading four ADRs and inferring the common thread.
+The thread is this: a project's knowledge can never be complete, and even if it
+could, total context overflows — recording everything produces more confusion
+than clarity, and no single actor (human or AI) ever holds the whole. A trust
+model that depends on completeness therefore fails twice: it is unreachable, and
+it is self-defeating. The only viable basis is local, partial verification of
+sufficient knowledge — but "sufficient" is dangerous if it means "approximately
+enough", because a well-typed-but-stale graph manufactures false certainty worse
+than stale prose (the tier-0-false-certainty anti-pattern, ADR-029).
+The sharpening that makes sufficiency safe is accreditation. What makes a partial
+slice usable is not that it roughly suffices but that it is signed and checkable.
+This becomes load-bearing in the AI moment: an agent's GENERATED output, and any
+DEDUCED conclusion, is not usable knowledge until it is accredited. Hallucination
+is exactly generated content entering the authoritative set unsigned. The stance
+needs to be explicit and citable because the difusión surface already sells it
+(differentiator diff-accredited-knowledge) and the agent-integration surface
+depends on it.
+
+Decision
+
+Name the stance as a constitutive axiom — `sufficient-verification`,
+"Sufficient Verification over Complete Knowledge" (pole 'Spiral, invariant=true
+in .ontoref/ontology/core.ncl) — and fix its operative consequences as typed
+constraints.
+1. Verification is always LOCAL and PARTIAL. Any actor can verify that a slice
+ (an op, a node, a change) is coherent with what is declared WITHOUT holding
+ or loading global knowledge of the whole. Cross-project verification is by
+ witness, not by clone (ADR-028).
+2. The protocol NEVER requires complete or global knowledge as a precondition
+ for a valid act. Any gate of the form "you may not change X until you
+ understand the whole system" is forbidden by design.
+3. Knowledge is ACCREDITED, not deduced or generated. Deduced or generated
+ output — explicitly including an AI agent's — is NOT usable authoritative
+ knowledge until it is signed/witnessed. At tier-2 this is enforced: the
+ witness is the only mutation path and validators reject ill-typed state
+ (internal-coherence-enforced). At tier-0, where no witness exists, such
+ knowledge is unaccredited by construction and MUST NOT be treated as
+ authoritative — it is a draft to be accredited, not a fact.
+4. "Sufficient" means accredited-and-checkable, never "approximately enough".
+ The floor is the signature, not a tolerance band.
+This is a Spiral commitment, not a Yin concession: it does not give up certainty,
+it fixes the third thing between impossible-complete-certainty and dangerous-blind-
+trust — verification of the sufficient — as invariant law. The axiom is the
+substance-axis statement; the witness/validators (ADR-023/024/026) are the
+reflection-axis enforcement; tier coexistence (ADR-029) is what lets a project
+choose how much it commits to accredit, never how much it must know.
+
+Constraints
+
+Hard Verification MUST operate on a slice (an op, a node, a change) without requiring the whole graph; cross-project verification MUST be by witness, never by clone (ADR-028). No verification path may demand global load.Hard The protocol MUST NOT introduce any gate that requires complete or global knowledge as a precondition for a valid act ('you may not change X until you understand the whole system' is forbidden).Hard Deduced or generated output — explicitly including an AI agent's — MUST NOT enter the authoritative set until signed/witnessed. At tier-2 the witness path enforces this (ADR-024); at tier-0, where no witness exists, such output MUST be treated as an unaccredited draft, never as authoritative fact.Soft 'Sufficient' MUST mean accredited-and-checkable (the floor is a signature), never 'approximately enough' (a tolerance band). No surface may relax sufficiency into laxity.
+
+Alternatives considered
+
+Leave the stance implicit across ADR-023/024/026/028 — rejected: The stance is load-bearing for difusión and agent integration and was already being sold (diff-accredited-knowledge) without a citable decision behind it. Implicit-across-four-ADRs means no single thing to cite, constrain against, or defend — and the global-knowledge gate could creep in unchallenged.Model it as a differentiator only (diff-accredited-knowledge), not an axiom — rejected: A differentiator positions outward; it does not constrain inward. The principle is a constitutive renunciation that must bind all future design, not a competitive talking point. The repo precedent is both: witness-as-axis-seam is an axiom AND diff-witness-seam cites it. Accreditation gets the same two-layer treatment.Model it as a Yin axiom (a concession: we accept knowing less) — rejected: Yin frames incompleteness as the price of ontoref — an apology. The honest framing is Spiral: holding the third thing between impossible certainty and blind trust, fixed as law. Incompleteness is the FORM, not the price; that distinction sets the tone of the whole difusión surface (affirmation, not apology).
+
+Anti-patterns
+
+Treating Generated or Deduced Output as Authoritative Without a Signature — An agent's generated output, or a deduced conclusion, is written into authoritative state (or trusted as fact) without being witnessed. Hallucination enters the set unmarked; the accredited/generated line is erased. This is the precise failure the axiom exists to forbid.Requiring Whole-System Understanding Before a Valid Act — A mode, validator, or adoption step demands that an actor understand or load the whole project before changing a part. This reinstates the unreachable, self-defeating completeness trust model and blocks the local partial action the axiom guarantees.Resting Trust on Completeness Instead of Verification — A project tries to earn trust by documenting/knowing everything and keeping it all current — the model that overflows context and drifts, producing more confusion than clarity. Trust should rest on accredited sufficient verification, not on an unreachable whole.
+
+Related ADRs
+
+ADR-023 · ADR-024 · ADR-026 · ADR-028 · ADR-029 · ADR-031
diff --git a/site/site/content/adr/en/accepted/adr-056.ncl b/site/site/content/adr/en/accepted/adr-056.ncl
new file mode 100644
index 0000000..959d597
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-056.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-056",
+ title = "Sufficient Verification over Complete Knowledge — Accredited, Not Generated, as the Basis of Trust",
+ slug = "056",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/056",
+ graph = {
+ implements = [],
+ related_to = ["adr-023", "adr-024", "adr-026", "adr-028", "adr-029", "adr-031"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-057.md b/site/site/content/adr/en/accepted/adr-057.md
new file mode 100644
index 0000000..603d687
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-057.md
@@ -0,0 +1,90 @@
+---
+id: "adr-057"
+title: "Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core"
+slug: "057"
+subtitle: "Accepted"
+excerpt: "The positioning layer (ADR-035) made marketing a queryable protocol surface:"
+author: "ontoref"
+date: "2026-06-10"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-057", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The positioning layer (ADR-035) made marketing a queryable protocol surface:
+audiences, value-props, differentiators, proofs, viability paths, all typed NCL.
+But the outward-facing SITE (outreach/site) did not consume that graph — it
+rendered a generic content template (blog/recipes/projects). The originating
+complaint that opened the 2026-06-10 ontology-completion interview was exactly
+this: "the site content does not correspond to the project."
+The interview established that the gap is not missing ontology (the layer is
+rich) but a missing PROJECTION discipline, and — more importantly — that the
+naive fix (a site that explains ontoref's core: the two axes, the witness-seam,
+the spiral, the wu-wei) is actively wrong. Exposition of the core repels by both
+ends, the failure mode named in the Spiral tension `reach-vs-qualification`: it
+bores the visitor who came for a concrete utility, and it reads as esoteric to
+the visitor who has not yet earned the frame. A site that argues the worldview
+either converts no one or attracts the wrong adopter who bails at first friction.
+The interview also established HOW the worldview is actually transmitted — a
+three-rung reveal ladder with increasing mediation-dependence: (1) a self-produced
+flip when a developer runs a live `describe impact` and sees the tool answer
+intent, not just structure; (2) a seeded flip, landing only in real use, when
+changing something forces touching the maintained identity (drift-check / ondaod
+/ witness); (3) a worldview flip — "this is for anything, not just projects" —
+produced ONLY by encounter with a real subject-graph the visitor recognizes as
+their own, never by argument. The proofs layer already names this:
+proof-personal-ontoref records that the latent audience "self-discovers when they
+see a personal graph, not in a pitch", and mech-repo's stance is "show, don't
+tell". The decision below makes that discipline architectural.
+
+Decision
+
+The site is a vestibule of three concrete doors (developer / infrastructure /
+personal), each routing to a live, navigable, witnessed graph. It is NOT an
+exposition of the core. Concretely:
+Projection, not authoring — door landings are PROJECTED from the positioning
+ graph through a structured contract (.ontoref/positioning/spine.ncl), not
+ hand-written. The contract carries the chosen spine copy (hero A, sub-hero B,
+ door lines C, prize D, close E) and references real audience/value-prop ids per
+ door; a generator emits the pages. Change a value-prop, regenerate, the door
+ updates. No duplicated copy that can drift.
+Hook static, prize live — the HOOK (hero, sub-hero, door landings) is static
+ NCL→build, so the site is usable with no daemon (local-fallback, ADR-029). The
+ REVEAL rungs that need a live graph — rung 1 (live `describe impact` demo) and
+ rung 3 (browsable real graph) — link to the running daemon. The daemon is the
+ prize surface, never a hard dependency of the hook.
+Route, do not argue — the site MUST route each door to a live graph or describe
+ surface, and MUST NOT try to produce the worldview flip by prose. rung 1 is
+ produced by a live demo; rung 2 is seeded (shown, lands in use); rung 3 is
+ triggered only by serving a real navigable subject-graph (ontoref's own, the
+ provisioning graph, a published personal graph). Prose alone never produces the
+ flip — this is the declared structural limit.
+Mediation by witnessed artifact — the worldview flip historically needed a
+ person to mediate. The decision converts that mediation into a reproducible
+ witnessed artifact: a browsable real graph (the proofs, made navigable). This
+ is what lets ontoref.dev produce reveal 1 and 3 without a human bottleneck.
+ Artifact-mediation serves reach (self-selection at scale); human mediation
+ remains only for the qualified-but-stuck — the reach-vs-qualification split.
+Honesty as filter — door copy is second person and names the pain (and the
+ exhaustion of facing it without a substrate); it does not soften the truth that
+ there is no deterministic recipe. The repulsion of the recipe-seeker is the
+ qualifier, not funnel leakage (reach-vs-qualification).
+
+Constraints
+
+Soft The site MUST route each door to a live graph or describe surface and MUST NOT attempt to produce the worldview flip by prose. The core (two axes, witness-seam, spiral, wu-wei) is the prize revealed after entry, never the landing-page argument.Hard Door landing pages MUST be projected from the structured contract (.ontoref/positioning/spine.ncl) referencing real audience/value-prop ids, not hand-authored. A value-prop change MUST be reflected by regeneration, never by editing copy in two places.Hard The hook (hero, sub-hero, door landings) MUST be static NCL→build and usable with no daemon (local-fallback). Only the reveal rungs that need a live graph (rung 1 live describe-impact, rung 3 browsable graph) may depend on the running daemon.Soft The rung-3 worldview flip MUST be mediated by a reproducible witnessed artifact (a browsable real graph), not by a required human. Publishing a real personal graph as that artifact is a per-artifact privacy decision and MUST be explicitly cleared before exposure.
+
+Alternatives considered
+
+An exposition site that explains the core (two axes, witness-seam, spiral, wu-wei) on the landing page — rejected: This is the naive fix the interview rejected. Exposition of the core repels by both ends (reach-vs-qualification): it bores the utility-seeker and reads esoteric to the unready, converting no one or attracting the wrong adopter. The worldview is not transmissible by prose — only by encounter with a real graph.Hand-authored door landing pages (marketing copy written directly as content) — rejected: Hand-authored copy drifts from the value-props it sells — the exact failure (content maintained by hand) that produced the generic template complaint. Projection from spine.ncl + the value-props keeps the site a derived view that regenerates on change.Site consumes the daemon API at runtime for everything, including the hook — rejected: Couples the whole site to a running daemon, contradicting local-fallback (ADR-029): the site goes dark when the daemon is down. The hook must be static so first contact never depends on the daemon; only the live-graph reveal rungs may.Rely on a human to mediate the worldview flip (talks, calls, demos by a person) — rejected: Human mediation does not scale and bottlenecks reach. The flip's mediation is convertible into a reproducible witnessed artifact — a browsable real graph (proof-personal-ontoref, mech-repo). Human mediation is kept only for the qualified-but-stuck, per reach-vs-qualification.
+
+Anti-patterns
+
+Arguing the Core on the Landing Page — A contributor writes a landing page that explains the two axes, the witness-seam, the spiral, or the wu-wei as the first thing a visitor reads. Exposition repels by both ends (reach-vs-qualification) and cannot produce the worldview flip, which only encounter with a real graph produces.Hand-Authoring Door Copy That Drifts From the Value-Props — Door landing text is written directly as site content rather than projected from spine.ncl + the value-props. The copy drifts from the claims it sells — the exact failure that produced the generic-template complaint.Gating First Contact on a Running Daemon — The hook (hero, sub-hero, door landings) is made to require the daemon (e.g. fetched live at runtime), so the site goes dark when the daemon is down. This breaks local-fallback and gates first contact on the prize surface.
+
+Related ADRs
+
+ADR-035 · ADR-046 · ADR-029 · ADR-043 · ADR-056
diff --git a/site/site/content/adr/en/accepted/adr-057.ncl b/site/site/content/adr/en/accepted/adr-057.ncl
new file mode 100644
index 0000000..f384111
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-057.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-057",
+ title = "Difusión Reveal Architecture — Route Each Door to a Live Graph, Never Argue the Core",
+ slug = "057",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/057",
+ graph = {
+ implements = [],
+ related_to = ["adr-035", "adr-046", "adr-029", "adr-043", "adr-056"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-059.md b/site/site/content/adr/en/accepted/adr-059.md
new file mode 100644
index 0000000..7c66b52
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-059.md
@@ -0,0 +1,82 @@
+---
+id: "adr-059"
+title: "Level-Aware About — The /about Surface Branches on Level Kind and Projects the Level's Own Graph"
+slug: "059"
+subtitle: "Accepted"
+excerpt: "ADR-057 made the SITE a projection of the positioning graph: three doors,"
+author: "ontoref"
+date: "2026-06-13"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-059", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-057 made the SITE a projection of the positioning graph: three doors,
+projected from spine.ncl, hook-static / prize-live, route-to-live-graph. That
+ADR is scoped to the difusión vestibule — the doors and their value-props.
+The /about route was untouched by it. It rendered a single texts-driven template
+(about.j2): a personal CV (the jpl level). But the About surface is where a level
+presents WHAT IT IS, and that differs by the kind of level: a personal ontology
+(jpl) presents a CV and links; a project (ontoref) should present its own
+identity — history, defs (axioms/tensions/practices), graph, ADRs, and the
+catalog of verifiables. The same complaint that opened ADR-057 ("the site content
+does not correspond to the project") applies to /about: it showed a person where
+a project should show itself.
+Three facts shaped the decision. (1) The data already exists and is queryable
+daemon-free: defs from core.ncl, the ADR set, the catalog of operations +
+validators. (2) The htmx site renders /about through a Tera template, NOT the
+Leptos UnifiedAboutPage — so making /about level-aware is a Tera handler + template
+change, not a component rewrite. (3) The site already has a hot-reload pattern for
+request-time graph data: content_graph.json is read by an mtime-cached loader, so
+a regenerated artifact takes effect with no restart. The About can consume the
+same way, which keeps the projection current without coupling the page to a live
+daemon.
+
+Decision
+
+The /about route is level-aware: it branches on the level's kind and, for a
+project, projects the level's own .ontoref/ graph. This EXTENDS ADR-057's reveal
+principle (hook-static / prize-live, projection-not-authoring, route-to-live-graph)
+to a second surface; it does not restate it. What is new here:
+A projection contract per level — .ontoref/positioning/about.ncl, typed by
+ about-schema.ncl, with a discriminant `kind = 'Personal | 'Project` and a
+ coherence predicate (kind ⇒ the matching block present, the other absent). The
+ contract declares SELECTIONS, not data: which axioms/tensions/practices to
+ surface, which ADR statuses to digest, which catalog kinds — the data itself is
+ queried from core.ncl / the ADR set / the catalog at projection time. Sparse
+ .ontoref/ yields a partial About, never an error.
+One view-model, two consumers — a single daemon-free projector (gen-about-pages)
+ assembles a view-model from about.ncl + .ontoref/. `--out` renders a standalone
+ static page (the marketing/web surface); `--emit-json` writes about.json, the
+ SSR consumption artifact.
+Hot-reload view-model, not a baked page — the /about handler reads about.json on
+ an mtime check (mirroring content_graph.json). When the artifact declares
+ kind == "project" the handler renders about_project.j2 with the view-model;
+ otherwise (absent, malformed, or kind == "personal") it falls back to the
+ existing texts-driven about.j2. Regenerating about.json updates live /about with
+ no restart; only handler-LOGIC changes need a rebuild. Data changes never do.
+Personal stays as it was — the personal level's About is unchanged: the same
+ texts-driven about.j2. Level-awareness is additive; it does not touch the
+ surface that already worked.
+The live graph remains the only daemon-dependent element of the project About —
+the reveal prize, per ADR-057. Everything else renders from the build-time
+projection, daemon-free.
+
+Constraints
+
+Hard The /about route MUST branch on the level kind: a project level renders the projected project About; a personal level (or absent/malformed projection) MUST fall back to the existing texts-driven personal template. The personal surface MUST stay the default and untouched.Hard The project About MUST be projected from .ontoref/positioning/about.ncl, which declares SELECTIONS (which defs/ADR-statuses/catalog-kinds), never inlined identity data. The data MUST be queried from core.ncl / the ADR set / the catalog at projection time so a source change regenerates the About.Soft The SSR /about MUST consume the projection as a hot-reload view-model artifact (about.json) read at request time on an mtime check, NOT baked into the binary. Regenerating the artifact MUST refresh /about with no restart; only handler-logic changes may require a rebuild.
+
+Alternatives considered
+
+Keep /about personal; put the project About at a separate route (a content-kind page) — rejected: This is the cheaper Option A and remains valid as the standalone static web surface. Rejected as the CANONICAL answer because /about itself would not become level-aware — a visitor to a project site's real /about would still get a person. Level-awareness of the canonical route is the point.Project the About live from the daemon at request time (a daemon HTTP client in the handler) — rejected: Couples /about to a running daemon, breaking the hook-static/prize-live split (ADR-057) and local-fallback (ADR-029): the page goes dark when the daemon is down. The hot-reload artifact gives most of the currency benefit while keeping the page daemon-free; the live graph stays the only daemon element.Rewrite /about as the Leptos UnifiedAboutPage with the projection embedded — rejected: The htmx site renders /about through Tera, not the Leptos component. A Leptos rewrite is a far larger blast radius for no benefit over a Tera handler + template that reads the view-model.Bake the project About into the binary at build (no runtime artifact) — rejected: Every .ontoref/ change would then require a binary rebuild + redeploy to refresh identity. The mtime-cached artifact (content_graph.json pattern) decouples data refresh from binary builds — regenerate about.json, no restart.
+
+Anti-patterns
+
+Showing a Person Where the Project Should Show Itself — A project level's /about renders a generic CV / personal template instead of projecting the project's own history, defs, graph, ADRs, and catalog. The About does not correspond to the level — the same gap ADR-057 closed for the doors.Hand-Authoring the Project About — The project About is written as site content (defs, ADR list, history) rather than projected from about.ncl + queried from .ontoref/. The copy drifts from the ontology it claims to present.Baking the About Into the Binary or Gating It on the Daemon — Either the project About is compiled into the server (so every .ontoref/ change needs a rebuild) or it is fetched live from the daemon at request time (so the page goes dark when the daemon is down). Both break the hot-reload / local-fallback discipline.
+
+Related ADRs
+
+ADR-057 · ADR-035 · ADR-045 · ADR-029 · ADR-043
diff --git a/site/site/content/adr/en/accepted/adr-059.ncl b/site/site/content/adr/en/accepted/adr-059.ncl
new file mode 100644
index 0000000..f4f79dc
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-059.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-059",
+ title = "Level-Aware About — The /about Surface Branches on Level Kind and Projects the Level's Own Graph",
+ slug = "059",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/059",
+ graph = {
+ implements = [],
+ related_to = ["adr-057", "adr-035", "adr-045", "adr-029", "adr-043"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-060.md b/site/site/content/adr/en/accepted/adr-060.md
new file mode 100644
index 0000000..23259bd
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-060.md
@@ -0,0 +1,75 @@
+---
+id: "adr-060"
+title: "mdBook Docsite — Projected Documentation as the Standard Surface for ontoref-Managed Projects"
+slug: "060"
+subtitle: "Accepted"
+excerpt: "The project already had `generate-mdbook` (generator.nu): it composed identity,"
+author: "ontoref"
+date: "2026-06-19"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-060", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The project already had `generate-mdbook` (generator.nu): it composed identity,
+ontology, ADRs, modes and scenarios and emitted a bare mdBook (raw markdown, the
+default theme, no served target). It answered "render the graph to markdown" but
+not "what is the standard, served, on-brand documentation surface for a project
+that manages itself with ontoref".
+Two external references fixed the missing shape. The cdci-dao mdBook
+(/Users/Akasha/Development/mdbook/cdci-dao) separates CHROME from CONTENT: a
+ds-theme + a book.toml with preprocessors (tera, admonish, toc, codeblocks) is the
+container; globaldefs.md + a tera context is how content enters, with a dev↔dist
+swap that bakes a base-URL. docserver (/Users/Akasha/Development/docserver/code)
+serves PRE-BUILT static HTML registered by a [[serv_paths]] entry (src_path →
+url_path, is_restricted); it does not build or watch — "on demand" means rebuild,
+not serve-time.
+The seam where ontoref plugs in is exactly cdci-dao's chrome/content split: keep
+the chrome fixed and shipped; feed the content from .ontoref/ on every build. The
+question is whether to make this the standard documentation surface for every
+ontoref project, and on what terms.
+
+Decision
+
+Adopt the cdci-dao mdBook model as the standard documentation surface, projected
+from the ontology and served by docserver, shipped as a protocol template.
+- CHROME (shippable, fixed): reflection/templates/mdbook/ carries the ds-theme,
+ a parameterized book.toml (tera/admonish/toc/codeblocks/variables/
+ extended-markdown-table), assets/css, globaldefs and brand images. It ships
+ with the reflection tree (installed by `just install-daemon`), so consumers
+ reach it at $ONTOREF_ROOT/reflection/templates/mdbook/ without the source repo.
+- MATERIALISE: `docsite init` (reflection/modules/docsite.nu) copies the chrome
+ into the project's .ontoref/docsite/ — idempotent, skipped if a book.toml
+ already exists. `docsite generate` auto-runs it on first use.
+- PROJECT (hybrid): on every build, docsite.nu reuses generator.nu's composed
+ `docs data`, writes a Tera context (identity read from card.ncl, not the
+ filesystem basename) for the static chrome pages, and pre-renders one page per
+ ADR, one per mode, and the architecture facets into docsite/src/, assembling
+ SUMMARY. Static Tera chrome + pre-rendered dynamic pages = the cdci-dao pattern.
+ Re-generating re-projects; the docs cannot drift from the state they describe.
+- SERVE: a dev build emits relative links for `mdbook serve`; `docsite generate
+ --serve-path /<mount>` bakes site-url to the docserver url_path (a surgical
+ one-line swap, save/ backup-restore) and emits the [[serv_paths]] registration.
+- The generate-docsite mode declares the procedure (materialize-template →
+ project-and-build → dist-and-register); `just docsite [serve-path]` is the
+ shortcut. The model was proven spine-local on ontoref itself before promotion;
+ migration 0039 carries it to consumers. Adoption is opt-in.
+
+Constraints
+
+Hard The mdBook chrome (ds-theme + book.toml preprocessors + assets) MUST ship as the protocol template under reflection/templates/mdbook/. The per-ADR, per-mode and architecture pages MUST be regenerated from .ontoref/ on every build, never hand-authored into the materialised docsite/src/.Hard Project identity (name, description) in the docsite MUST be read from card.ncl, the ontological beacon, NOT derived from the directory basename. This keeps the model correct for any project regardless of where its ontology sits.Soft Serving SHOULD go through a dist build with site-url baked to the docserver mount, emitting a [[serv_paths]] registration entry. The mode MUST NOT mutate a foreign docserver config; it emits the entry and the copy hint for the operator to apply.Soft Building a docsite SHOULD remain opt-in: no project is required to materialise or generate one, and the absence of .ontoref/docsite/ is never an error. The capability ships; adoption is voluntary and reports direction of motion, not a gate.
+
+Alternatives considered
+
+Keep generate-mdbook (the bare renderer) as the only doc surface — rejected: It emits raw markdown on the default theme with no served target and no chrome/content split. It answers 'render the graph to markdown' but is not an on-brand, docserver-servable, standard surface. Kept as a quick dump; not the standard.Pure-Tera: every page a {{ }} template, mdbook-tera renders all content at build — rejected: Maximises 'on demand' but forces per-ADR/per-mode loops into a single tera context while mdbook still needs one .md file per page — rigid for the per-item pages with no benefit over pre-rendering them. The hybrid keeps tera for the chrome where it helps.Ship to install/resources/templates/ and copy via install.nu — rejected: install.nu does not copy install/resources/templates/ wholesale, while the reflection tree IS copied verbatim to the data dir and is already how forms templates ship ($ONTOREF_ROOT/reflection/templates/). Placing the chrome under reflection/templates/mdbook/ reuses the wired path with no install.nu change.Promote straight to the protocol template without proving it spine-local first — rejected: Standardises an unverified surface for every consumer in one step — the premature-formalization anti-pattern (adr-029). Proving the slice on ontoref itself, then promoting with a migration, keeps the realised instance ahead of the protocol claim (sufficient-verification).
+
+Anti-patterns
+
+Hand-Authoring the Projected Documentation — The architecture / decisions / modes pages are written as static markdown rather than projected from .ontoref/ on each build. The docs drift from the ontology they claim to present.Naming the Project from the Directory — The docsite title/identity is derived from the directory basename instead of card.ncl, producing wrong names (e.g. '.ontoref') and a model that breaks when the ontology does not sit at the project root.Making the Docsite a Gate — Treating the absence of a docsite as an error, or requiring every project to build one. Collapses the formalization-vs-adoption Spiral by imposing the surface rather than offering it.
+
+Related ADRs
+
+ADR-057 · ADR-035 · ADR-029 · ADR-032 · ADR-010
diff --git a/site/site/content/adr/en/accepted/adr-060.ncl b/site/site/content/adr/en/accepted/adr-060.ncl
new file mode 100644
index 0000000..1a1656d
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-060.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-060",
+ title = "mdBook Docsite — Projected Documentation as the Standard Surface for ontoref-Managed Projects",
+ slug = "060",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/060",
+ graph = {
+ implements = [],
+ related_to = ["adr-057", "adr-035", "adr-029", "adr-032", "adr-010"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-061.md b/site/site/content/adr/en/accepted/adr-061.md
new file mode 100644
index 0000000..666a25d
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-061.md
@@ -0,0 +1,75 @@
+---
+id: "adr-061"
+title: "Retire 'Structure' as the Mother-Concept and Decouple Brand Logos from the Tagline"
+slug: "061"
+subtitle: "Accepted"
+excerpt: "'Structure' was ontoref's mother-concept on every outward surface: the card.ncl"
+author: "ontoref"
+date: "2026-06-21"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-061", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+"Structure" was ontoref's mother-concept on every outward surface: the card.ncl
+identity tagline ("Structure that remembers why."), the positioning hero
+("Structure that stays true."), the SEO page title, a curated rotator phrase, and
+the text baked into ~20 brand logo SVGs.
+A role-swap ontology-completion interview (cadence per the 2026-06-10 model)
+surfaced three faults the noun carries. First, "structure" fails the project's own
+`diff-domain-transversality` differentiator: a software project reads as a
+"structure", but an infrastructure provisioning or a personal ontology does not —
+the noun silently narrows the identity to the developer door and de-identifies the
+other two. Second, it is mono-polar and static: it names only the Substance pole
+(ADR-031) and amputates the Act/reflection pole, and "structure" connotes fixity
+against the core's own "what is real is in transformation, not a fixed set of
+invariants". Third, the verb "remembers"/"stays true" names an EFFECT (amnesia,
+drift) rather than the root the project actually defends against (loss of
+self-control / autocontrol).
+The tagline was baked into the logo SVGs, so the identity could not change without
+re-rendering the whole logo family — a coupling that had frozen the identity and
+was the main argument for leaving "structure" in place.
+
+Decision
+
+Retire "structure" as the mother-concept across identity, hero and branding, and
+decouple the logos from the tagline so identity can evolve without a brand
+re-render.
+- IDENTITY (card.ncl): the stable tagline becomes the honest, transversal
+ category definition — "Operational ontology with reflection" — and the
+ description drops "queryable structure" and states the transversality. The
+ evocative voice moves to the evolving positioning hero, not the stable beacon.
+- HERO (spine.ncl): an embodied, transversal, both-pole line — "Pisa firme y
+ anda ligero." / "Sure-footed, and light on your feet." — with the domain
+ (ámbito) carried by the page eyebrow ("Operational ontology with reflection"),
+ not by the hero. The certainty is punctual (the witnessed point); the act is
+ continuous.
+- LOGOS: the tagline <text> is removed from the brand SVGs (wordmark + icon
+ only). The logo no longer encodes the claim, so the tagline lives in layout and
+ can change freely without re-rendering logos.
+- ROOT ACT: named with the continuous verb "sostener" (durative, indispensable),
+ never the punctual "responder". The three tier-taglines
+ (.ontoref/positioning/tier-taglines.ncl) capture the same root at the three
+ adoption tiers (ADR-029).
+The retired lines were swept from the rotator fallback, the SEO title and the
+curated rotator queue; the value-prop vp-001 (whose claim still uses the retired
+concept) is left for a separate content review, not a mechanical id rename.
+
+Constraints
+
+Hard The card.ncl identity tagline MUST be the transversal category definition 'Operational ontology with reflection', not a domain-narrowing mother-noun (e.g. 'structure', 'graph').Hard The brand logo SVGs MUST NOT bake the retired tagline text. The logo is wordmark+icon only; the tagline/definition is applied separately in layout.Soft Outward identity/positioning surfaces SHOULD NOT reintroduce 'structure' as the mother-noun. 'graph'/'structure' may appear only as a sanctioned access-substrate (per diff-domain-transversality), never as the headline identity.
+
+Alternatives considered
+
+Keep 'Structure that remembers why' as the stable identity — rejected: Defensible on brand-stability and because 'remembers why' carries the reflection pole — but it loses to the transversality argument: 'structure' de-identifies the infrastructure and personal audiences, which is more fundamental than stability since transversality is the core differentiator.Change only the positioning hero, leave the card identity and logos as-is — rejected: Leaves the identity beacon and the whole logo family asserting a mother-noun that fails transversality, and keeps the logo/tagline coupling that freezes the identity. Half-measure that preserves the root problem.Keep the tagline baked into the logos and re-render all ~20 SVGs on each change — rejected: Re-couples identity to a 20-file brand re-render every time the claim evolves. Decoupling once is strictly cheaper and is what mature brand systems do (mark separate from claim).Pick a new poetic mother-noun (e.g. 'graph', 'coherence') as the identity — rejected: 'graph' repeats the same reification one level down (a substrate-noun, Substance-poled); any single abstract noun risks collapsing one pole or failing a domain. The resolution is a category definition for the stable identity plus an embodied act for the evolving hero, not another mother-noun.
+
+Anti-patterns
+
+Mother-Noun That Lands in One Domain — A single identity noun that reads natural in one domain (software 'structure') silently excludes the others (an infra, a life), narrowing reach to one audience while appearing neutral.Baking the Claim into the Mark — The tagline text is rendered inside the logo SVG, coupling identity to a multi-file brand re-render and freezing the claim — the identity cannot evolve without re-rendering the whole logo family.Naming the Symptom Instead of the Root — The identity verb defends against a symptom (amnesia → 'remembers', drift → 'stays true') instead of naming the root act (self-governance held continuously → 'sostener'). The line treats the effect as the essence.
+
+Related ADRs
+
+ADR-035 · ADR-057 · ADR-029
diff --git a/site/site/content/adr/en/accepted/adr-061.ncl b/site/site/content/adr/en/accepted/adr-061.ncl
new file mode 100644
index 0000000..51b7dcd
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-061.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-061",
+ title = "Retire 'Structure' as the Mother-Concept and Decouple Brand Logos from the Tagline",
+ slug = "061",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/061",
+ graph = {
+ implements = [],
+ related_to = ["adr-035", "adr-057", "adr-029"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-062.md b/site/site/content/adr/en/accepted/adr-062.md
new file mode 100644
index 0000000..11e4402
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-062.md
@@ -0,0 +1,109 @@
+---
+id: "adr-062"
+title: "Constellation Member Taxonomy — Primary, Addon, Projection; Recursion in the Level Graph, Not Git; Uniform Forge Rule"
+slug: "062"
+subtitle: "Accepted"
+excerpt: "ADR-048 fixed the constellation layout (non-versioned parent, independent git"
+author: "ontoref"
+date: "2026-06-22"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-062", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-048 fixed the constellation layout (non-versioned parent, independent git
+sub-repos, spine at parent) on one implicit assumption: one folder = one
+publication boundary (code→Forgejo, outreach→public, vault→private). Reality
+stressed that assumption from two directions the original ADR did not name.
+(1) DERIVATION/COUPLING. Some surfaces exist by relation to another member, not
+autonomously. The daemon UI is served by ontoref-daemon (peer ontoref-ui), shares
+code's remote/visibility/release. desktop is an app with its own cycles, versions
+and multi-license terms. examples are CI-tested fixtures that also feed docs and
+workshops. study-group is a workshop format that consumes examples. None of these
+map cleanly to "a folder with one boundary".
+(2) INTRA-MEMBER VISIBILITY SPLIT. Some surfaces carry the boundary inside the
+build pipeline, not between folders. presentations are md slides (own cycles) that
+project to public PDFs. web is a distributable pack rendered to jesusperez.pro;
+site renders to ontoref.dev — different deploy targets, same public visibility.
+docs is the extreme case: one generated source routed to dev (code), public
+(outreach) and private (vault) audiences, served by an external docserver
+(/Users/Akasha/Development/docserver, a peer like stratumiops, not a member).
+The pull this created was toward git submodules / nested repos to mirror the
+apparent recursion (code{daemon,UI,examples}, outreach{site,web,presentation,
+study-group}). That pull is the error this ADR closes: it would reify a logical
+recursion into the most expensive substrate (git topology + version-pinning +
+clones), and — decisively — a submodule IS clone-and-pin, which contradicts the
+project's own witness-not-clone invariant (ADR-028). ontoref describing its own
+internal structure by cloning would be anti-PAP of its deepest axiom.
+
+Decision
+
+Classify every constellation member into exactly one of three categories, decouple
+the logical recursion from the git topology, and fix a uniform forge rule.
+THREE CATEGORIES
+ - Primary — owns a publication boundary (distinct remote + visibility) OR a
+ distinct release lifecycle. Gets its own git repo.
+ Members: code/, outreach/, vault/, desktop/.
+ - Addon — exists by a declared dependency edge to a Primary, with its own
+ lifecycle. Gets its own repo ONLY when it earns its own boundary;
+ otherwise it is a node in the level graph inside its Primary.
+ desktop is an Addon-that-earned-Primary (ADR-054 capture surface,
+ multi-license); the daemon UI is an Addon that stays inside code/.
+ - Projection — one source, the frontier lives INSIDE the build. Never its own
+ repo. Four sub-shapes the build routes:
+ copy assets/ — canonical → identical materialized copies
+ reveal site — projects the live graph (ADR-057)
+ visibility-split presentation — private/undeployed src → public PDF
+ multi-boundary docs — one source → dev/public/private slices
+ by audience (ADR-046 view) → docserver
+DECISION DRIVER. "Own repo?" is decided by publication boundary or release
+lifecycle, never by conceptual separability. Conceptual recursion is carried by
+the level graph (ADR-045 relative chains + manifest.hosts co-tenancy, resolved by
+witness, never cloned), decoupled from the filesystem (ADR-028). Submodules are
+forbidden as a structural mechanism; they are admissible only if a sub-surface
+earns an independent boundary AND needs version-pinning into a parent — a
+condition zero current surfaces meet, and even then the ontoref-native form is
+co-tenancy-by-witness, not a git submodule.
+UNIFORM FORGE RULE. Forgejo (repo.jesusperez.pro) is always the canonical origin.
+GitHub is always a one-way push-mirror (Forgejo native push-mirror, force-overwrite
+of all refs → de-facto read-only) or absent — never canonical of anything. rad
+(Radicle) is an optional sovereign peer, pushed separately, never inside the mirror
+chain. vault is excluded from every mirror (Forgejo private only). code/ carries
+all three (Forgejo canonical + rad sovereign + GitHub mirror); outreach/ and
+desktop/ carry Forgejo canonical + GitHub mirror; vault/ Forgejo private only.
+PLACEMENTS. examples → code/ (CI fixtures), projected to outreach docs and consumed
+by study-group via co-tenancy. presentations → src in outreach/presentation/
+(public repo, PDFs deployed, sources not); strategically sensitive decks are the
+exception routed to vault. study-group → outreach/, hosts the examples chain by
+witness (ADR-045 instance-as-host). docs → not a member: a multi-boundary
+projection whose private branch is sourced tags-in-place (each member carries its
+own docs tagged by audience; docserver composes the slices), per ADR-046/060.
+DEV-INTERNAL OVERLAY. Each member's dev-internal process and config (.coder/,
+.claude/) is gitignored from the public-mirrored history and tracked instead in a
+private 'bare-overlay' repo: a separate git-dir at <member>/.internal.git (itself
+gitignored) whose work-tree is the member root and which tracks only those two
+dirs. It pushes ONLY to a private Forgejo remote and is NEVER mirrored — the same
+boundary as vault, one sensitivity tier below it (plaintext, not SOPS). The public
+repo and the overlay coexist on one working tree via distinct git-dirs
+(plane-habitability), which is NOT a submodule (no .gitmodules, no pin). Tooling:
+code/scripts/internal-overlay.nu (setup / onboard / commit / run), generic for any
+project with the same .coder/.claude layout.
+
+Constraints
+
+Soft Every constellation member is classified as exactly one of Primary | Addon | Projection; a new member declares its category before it is wired.Hard No constellation member uses git submodules to express internal structure or cross-member composition; recursion is carried by the level graph (ADR-045 co-tenancy), resolved by witness, never cloned.Soft Forgejo is the canonical origin of every materialized member; GitHub appears only as a one-way push-mirror, never as canonical.Hard vault/ is excluded from every public mirror — it has no github.com remote and no rad:// peer; Forgejo private only.Soft An Addon declares its dependency edge to the Primary it derives from (desktop → code via cargo path dependency), so the derivation is explicit, not implied by colocation.Soft A member's .coder/ and .claude/ are gitignored from the public repo and tracked in a private bare-overlay repo (git-dir at <member>/.internal.git, work-tree the member root) that pushes only to a private remote — never github/rad.Soft A Projection surface (assets, site, web, presentations, examples, docs) does not become its own git repo; it lives inside a Primary and the build routes its visibility.
+
+Alternatives considered
+
+Git submodules / nested repos to mirror the conceptual recursion, with examples/presentations nested inside — rejected: A submodule is clone-and-pin, contradicting witness-not-clone (ADR-028) — ontoref would violate its own invariant to describe itself. It also reintroduces version-pinning the constellation does not want and the friction ADR-048 already rejected. Recursion belongs in the level graph (ADR-045), resolved by witness, not in git topology.Promote every conceptually separable surface (daemon UI, site, web, examples) to its own repo — rejected: Conceptual separability is not a publication boundary. This proliferates repos with shared remotes/visibility/cadence, multiplying remotes and CI for surfaces that share one boundary — the opposite of ADR-048's minimal topology. The driver is the boundary, not the concept.Make GitHub canonical for the public-facing members (outreach), since the audience lives there — rejected: Splits the sovereignty model into special cases and cedes a canonical to a centralized host. The one-way mirror already delivers reach without surrendering authority; a uniform Forgejo-canonical rule is more coherent with the project's sovereignty axioms.Add a fourth top-level category for cross-boundary surfaces like docs — rejected: docs is not a new category — it is a Projection whose build fans out to multiple visibility targets. Naming it a sub-shape (multi-boundary) of Projection keeps the taxonomy at three and reuses the existing projection machinery (ADR-046 views, ADR-060 mdbook) instead of inventing a parallel concept.
+
+Anti-patterns
+
+Submodules to Mirror Conceptual Recursion — Reaching for git submodules / nested repos to express a member's internal sub-surfaces. A submodule is clone-and-pin, contradicting witness-not-clone; it reifies a logical recursion into git topology and reintroduces version-pinning the constellation does not want.A Repo Because It Is Separable — Promoting a surface to its own git repo because it is conceptually distinct, when it shares the remote, visibility and release cadence of its host. Multiplies remotes and CI for surfaces that share one publication boundary.Letting the Public Mirror Hold Authority — Making GitHub the canonical origin of a public-facing member because the audience lives there. Cedes a canonical to a centralized host and fragments the sovereignty model into special cases.
+
+Related ADRs
+
+ADR-048 · ADR-054 · ADR-045 · ADR-028 · ADR-013 · ADR-057 · ADR-060 · ADR-046
diff --git a/site/site/content/adr/en/accepted/adr-062.ncl b/site/site/content/adr/en/accepted/adr-062.ncl
new file mode 100644
index 0000000..40200b4
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-062.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-062",
+ title = "Constellation Member Taxonomy — Primary, Addon, Projection; Recursion in the Level Graph, Not Git; Uniform Forge Rule",
+ slug = "062",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/062",
+ graph = {
+ implements = [],
+ related_to = ["adr-048", "adr-054", "adr-045", "adr-028", "adr-013", "adr-057", "adr-060", "adr-046"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-063.md b/site/site/content/adr/en/accepted/adr-063.md
new file mode 100644
index 0000000..98b08ad
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-063.md
@@ -0,0 +1,82 @@
+---
+id: "adr-063"
+title: "The Work-Order Governance Unit — Constrain an Agent by an Externally-Owned Contract and a Witnessed Deliverable, Not by Trust"
+slug: "063"
+subtitle: "Accepted"
+excerpt: "A project was stopped because an agent, session after session, skipped the"
+author: "ontoref"
+date: "2026-06-24"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-063", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+A project was stopped because an agent, session after session, skipped the
+project's own guardrails — constraints, tiers, validators — and built runtime
+configuration as CLI flags in direct violation of a hard invariant ("NCL config
+as source of truth") that was queryable in `describe constraints` the whole time.
+The honest diagnosis, reached in the 2026-06-23 role-swap interview, was not "the
+agent forgot": it was that a rule written as prose, with no gate that checks it,
+is — to a probabilistic agent biased toward visible progress — a suggestion its
+sampling is free to skip.
+The failure is structural. Ontoref's knowledge surface is overwhelmingly
+read-side (`describe`, `qa show`); information only changes behavior if something
+consumes it and blocks. The validators exist as modes the agent may choose to
+run, not as gates. The layers with real teeth (compiler, tests, pre-commit, CI,
+harness permissions) were never wired to the ontology. And because the agent
+writes the NCL, the validators and the hooks, almost every guardrail lives inside
+its own editable surface — a lock it can edit in the same gesture it breaks it
+(the fox builds the henhouse). A guardrail that does not block is, for an agent
+like this, decoration.
+A minimum mechanism is a trap: a minimal gate still depends on the agent
+respecting it. The interview converged on a different category of fix — not a
+better guardrail but a reusable governance unit whose terms are owned outside the
+worker and whose "done" carries a validator's witnessed output instead of the
+agent's word. It was demonstrated once (Work Order #0, config-NCL check, three
+red-team runs) before being recorded here.
+
+Decision
+
+Adopt the Work-Order Governance Unit (Spanish: Encargo) as the protocol's pattern
+for constraining agent work, recorded as typed glossary terms
+(`encargo`, `pliego`, `orden-de-trabajo`, `testigo-verificado` in
+`.ontoref/ontology/glossary.ncl`). It separates terms from execution along the
+one boundary the worker must not cross:
+- A Statement of Work (SOW · Pliego), the terms — scope (minimal context +
+ one-sentence objective), a FALSIFIABLE contract (a machine check, schema or
+ constraint), and the validation mode ('Machine | 'Adversarial, signed or
+ not). The SOW is authored and owned by a human, OUTSIDE the agent's editable
+ surface. One SOW authorizes many Work Orders.
+- A Work Order (WO · Orden de Trabajo), the execution — disposable, contained,
+ owned by the agent: deliverable, an observability envelope, the verified
+ witness, and a contained verdict ('Accepted | 'Rejected | 'Escalated).
+Two atomic behavioral rules, short and at the point of use, are the whole gate:
+(1) no scope + contract, no start; (2) no witness, no done. Where the deliverable
+is not machine-checkable (open design), the contract falls back to 'Adversarial —
+a second unit whose contract is to refute the first — never to prose.
+This ADR accepts the pattern as the governance APPROACH; the SUBSTRATE stays
+unbuilt. Promotion to a typed `Sow`/`WorkOrder` schema and an orchestrator wired
+into the daemon/CLI is GATED on a second realized instance beyond Work Order #0 —
+specifically a domain Work Order exercising `signed = true` end-to-end — mirroring
+the project's standing rule for nascent primitives (bl-036 phase-transition-witness,
+gated on a second instance). Accepting the decision activates the constraints below;
+it does not declare the substrate built.
+
+Constraints
+
+Hard The SOW (scope + contract + validation mode) MUST be authored and owned outside the executing agent's editable surface. A Work Order MUST NOT modify the SOW that invoked it.Hard A Work Order MUST NOT reach 'Accepted without a verified witness carrying the contract's real output (exit code / validator result). A prose claim of completion is inadmissible by itself.Soft A SOW contract MUST be machine-checkable (cmd | schema | constraint). Where the deliverable is open-ended, the validation mode MUST be 'Adversarial (a refuting second unit) — never unverified prose passing as a contract.Soft Promotion of this pattern to a typed Sow/WorkOrder schema or a built orchestrator MUST be gated on a second realized instance beyond Work Order #0 (a domain WO exercising signed witness end-to-end). DISCHARGED 2026-07-04: the second instance is realized — wo-adr001-zero-deps-v2 ran end-to-end through the enforcing run-lifecycle executor (guards block at run start, `verify` derives step status, mode complete verifies the receipt's detached signature), corroborated by an eight-attempt falsification closed only by two out-of-band human signatures. Both promotion targets now exist: the typed schema (code/ontology/schemas/sow.ncl) and the built orchestrator (reflection/modules/run.nu), distributed via migration 0041. The promotion this gate authorized is recorded as ADR-066 (Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor), Accepted 2026-07-04; the gate no longer holds the pattern Proposed.
+
+Alternatives considered
+
+Hooks (PreToolUse) as the core that constrains the agent — rejected: A hook needs a guarantor that it is invoked, is provider/version/context dependent, and chains awkwardly with git/CI/linter hooks. It is one more external guard that itself needs a guard — infinite regress, and it lives near the agent's editable surface.VCS (branches/worktrees, protected CI) as the core — rejected: With multiple agents on one codebase it degrades into rebase/sync hell; centralized forges can collapse access and reorder priorities; jj+radicle+bare+worktrees multiply the surface. It governs the artifact after the fact, not the act.Signed witness alone as the precondition — rejected: A witness is post-hoc evidence that X happened, not a barrier that forbids not-X (ADR-050). It becomes governance only when a control point refuses to proceed without a valid witness — i.e. inside a unit like the Work Order, not on its own.Keep the constraints as typed prose in core.ncl / CLAUDE.md and rely on the agent reading them — rejected: This is the exact failure being recorded: typed-but-unforced is still prose-unforced with better syntax. Loading context is not obeying it; only a unit that gates on a machine-checkable contract changes behavior.
+
+Anti-patterns
+
+Selling the Gate's Friction as the Permanent Product — Treating the Work-Order friction as the deliverable — 'ontoref makes your agent comply' — and forgetting it is transitional. This is the forbidden Yang-capture collapse of enforcement-vs-emergence: it repels, and it mistakes the teacher (friction) for the lesson (a form internalized until coherent action feels effortless).The Operator Authoring the SOW It Must Satisfy — The executing agent writes (or edits) the Statement of Work whose contract it then satisfies, collapsing the ownership boundary. The lock is edited in the same gesture it is broken — the unit becomes advisory.Claiming Done Without a Witness — A Work Order is treated as 'Accepted on the agent's prose claim, with no witness carrying the contract's real output. 'Done' degrades back to 'trust my word' — the exact failure the unit exists to forbid.A Non-Machine-Checkable Contract Masquerading as a Gate — A SOW carries a 'contract' that is actually prose ('design the auth well') with no machine check and no 'Adversarial fallback. The unit looks governed but gates nothing — the failure mode wearing the costume of the fix.
+
+Related ADRs
+
+ADR-066 · ADR-050 · ADR-052 · ADR-056 · ADR-024 · ADR-029 · ADR-031
diff --git a/site/site/content/adr/en/accepted/adr-063.ncl b/site/site/content/adr/en/accepted/adr-063.ncl
new file mode 100644
index 0000000..e5c7a9c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-063.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-063",
+ title = "The Work-Order Governance Unit — Constrain an Agent by an Externally-Owned Contract and a Witnessed Deliverable, Not by Trust",
+ slug = "063",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/063",
+ graph = {
+ implements = [],
+ related_to = ["adr-066", "adr-050", "adr-052", "adr-056", "adr-024", "adr-029", "adr-031"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-064.md b/site/site/content/adr/en/accepted/adr-064.md
new file mode 100644
index 0000000..aa224f1
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-064.md
@@ -0,0 +1,74 @@
+---
+id: "adr-064"
+title: "The Authoring/Knowledge Door (Funnel Re-Architected to Four) and the Content–Vehicle Convergence Membrane"
+slug: "064"
+subtitle: "Accepted"
+excerpt: "The difusión vestibule (ADR-057) declared a marketing funnel of THREE doors in"
+author: "ontoref"
+date: "2026-06-24"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-064", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The difusión vestibule (ADR-057) declared a marketing funnel of THREE doors in
+`.ontoref/positioning/spine.ncl`: developer, infrastructure, personal — a curated
+audience partition with a three-beat rhetorical architecture (the sub_hero triad
+"Tu proyecto, tu infra, tu vida", the A–E narrative).
+The CLI-domain catalog (`code/domains/`, ADR-012) is a different axis: five domains
+(framework, personal, provisioning, knowledge-works/librosys, rustelo), each
+activated when a project's repo_kind matches. Mapping the five domains' audiences
+onto the doors, four are covered (framework→developer, rustelo→developer,
+provisioning→infrastructure, personal→personal). ONE is not: knowledge-works' —
+authors, editors and knowledge-workers who diffuse an authored Work.
+That audience is real and distinct: not a developer (a developer BUILDS the
+publishing vehicle, an author AUTHORS the content), not private PKM (the personal
+door). It deserves a front door of its own. Adding it is not a data append: the
+funnel's rhetoric is built on three beats, so a fourth door re-architects the
+hero/sub_hero/narrative to four — a deliberate positioning-surface change.
+Two further facts shape the edges. rustelo is NOT a door (its audience is developer)
+and is moreover the publishing/diffusion VEHICLE while knowledge-works is the
+CONTENT — they converge at "publish & diffuse". And a developer can also be an
+author, so the developer and authoring audiences overlap at a seam rather than
+partition cleanly.
+
+Decision
+
+The marketing funnel is re-architected to FOUR doors:
+developer · infrastructure · authoring · personal.
+The fourth door — authoring/knowledge — is added to `spine.ncl`, with the funnel
+ rhetoric updated to four beats (sub_hero "Tu proyecto, tu infra, tu obra, tu vida";
+ the narrative's door table and headings). The door references a real audience
+ (`audiences/audience-authoring-knowledge.ncl`, status 'Hypothesis) and value-prop
+ (`value-props/vp-009-the-whole-work-everywhere.ncl`, status 'Draft), with the same
+ `live_view` / `graph_focus` reveal rungs as the other doors (ADR-057). Its domain
+ is knowledge-works (librosys).
+rustelo does NOT get a door. A RusteloApp builder is a developer and enters through
+ the developer door; a rustelo door would duplicate the developer audience.
+The rustelo↔knowledge-works convergence (vehicle↔content) is modeled as the
+ `content-vehicle-convergence` gate membrane in `.ontoref/ontology/gate.ncl`
+ (permeability 'High, protocol 'Absorb). The membrane HOLDS both poles
+ (developer/author, vehicle/content) and favors the developer-author crossing
+ rather than collapsing the doors into one or denying the synergy — the ondaod
+ non-collapse, and the synergy axis distinct from the audience (door) axis.
+Result: four marketing doors, five domains, one membrane. No shipped protocol
+surface changes (positioning + project ontology only) — no migration.
+
+Constraints
+
+Hard The difusión funnel in spine.ncl MUST include the authoring/knowledge door as a first-class audience entry (four doors total: developer, infrastructure, authoring, personal), referencing a real audience id and value-prop id. The funnel rhetoric (sub_hero, narrative) MUST be coherent at four — no 3-in-prose / 4-in-data split.Hard The rustelo↔knowledge-works convergence (vehicle↔content) MUST be modeled as a gate membrane in gate.ncl. It MUST NOT be expressed by opening a rustelo door nor by merging the developer and authoring doors.Soft The authoring door SHOULD route to a real value-prop (vp-009) and reveal rungs, never argue the core — the same reach-vs-qualification discipline as the other three doors.
+
+Alternatives considered
+
+Keep the funnel at three; treat authoring as audience+value-prop only, not a door — rejected: The authoring audience is genuinely uncovered and distinct (not developer, not PKM); leaving it doorless routes it nowhere. A front door is the honest answer for a real audience — the funnel is re-architected to four to carry it.Open a fifth door for rustelo too (one door per domain) — rejected: rustelo's audience IS developer; a rustelo door duplicates it and inflates the funnel. Doors track uncovered audiences, not the domain count.Merge developer and authoring into one 'builder/author' door — rejected: Collapses the developer↔author / vehicle↔content Spiral toward one pole and loses routing precision. The convergence is better expressed as a membrane between two doors than as one fused door.Model the rustelo↔knowledge-works synergy as a door or as prose — rejected: A door mis-models a synergy on the audience axis; prose drifts and is not queryable. The gate membrane is the typed mechanism for controlled cross-boundary exchange.
+
+Anti-patterns
+
+Bolting a Fourth Door onto a Three-Beat Funnel — The authoring door is appended to the doors array as data while the funnel rhetoric (sub_hero triad, narrative, comments) stays at three — leaving the spine internally inconsistent (4 in data, 3 in prose). A door is added without re-architecting the rhetoric to four.The Authoring Door Arguing the Core — The authoring door is filled with what knowledge-works can DO (commands, formats) instead of routing the author audience to a value-prop — the reach-vs-qualification trap, now on a fourth door.Modeling the Synergy as a Door Instead of a Membrane — The rustelo↔knowledge-works convergence is expressed by opening a rustelo door, or by merging the developer and authoring doors — collapsing the developer↔author / vehicle↔content Spiral onto the audience axis instead of holding it as a controlled exchange.
+
+Related ADRs
+
+ADR-057 · ADR-059 · ADR-035 · ADR-043 · ADR-012
diff --git a/site/site/content/adr/en/accepted/adr-064.ncl b/site/site/content/adr/en/accepted/adr-064.ncl
new file mode 100644
index 0000000..f58108b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-064.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-064",
+ title = "The Authoring/Knowledge Door (Funnel Re-Architected to Four) and the Content–Vehicle Convergence Membrane",
+ slug = "064",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/064",
+ graph = {
+ implements = [],
+ related_to = ["adr-057", "adr-059", "adr-035", "adr-043", "adr-012"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-065.md b/site/site/content/adr/en/accepted/adr-065.md
new file mode 100644
index 0000000..90d0ebb
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-065.md
@@ -0,0 +1,84 @@
+---
+id: "adr-065"
+title: "The Door/Domain Surface Is Served by a Dedicated `dominios` Content-Kind, Not the Reused `projects` Kind"
+slug: "065"
+subtitle: "Accepted"
+excerpt: "ADR-057 built the difusión vestibule as doors projected from `spine.ncl`, and"
+author: "ontoref"
+date: "2026-06-24"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-065", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-057 built the difusión vestibule as doors projected from `spine.ncl`, and
+ADR-064 re-architected the funnel to four doors over five domains plus the
+content-vehicle-convergence membrane. The SITE that renders this
+(`outreach/site`, a rustelo-framework SSR app) does not yet carry that model.
+The site mounts the doors on the rustelo framework's `projects` content-kind:
+`scripts/build/gen-spine-pages.nu` emits each door as a post of the EXISTING
+`projects` kind, served at `/projects/<key>` · `/proyectos/<key>`. Its own header
+(lines 6–8) documents WHY: "Mounting on an already-compiled kind" avoids the
+ContentKind codegen boundary. That was a deliberate shortcut to ship without a
+rustelo rebuild.
+The shortcut leaves a permanent name↔thing drift. In ontoref these are NOT
+projects — they are DOMAINS (the `code/domains/` axis, ADR-012). Yet the surface is
+structurally `projects` end-to-end: route `/projects`, `content_type: "projects"`
+in the generated index, directory `content/projects/`, view `ProjectsCTAView`, the
+`projects-*` i18n strings (whose default copy is "Open source projects and
+platforms"). The framework's own demo portfolio (kogral, stratumiops, vapora…)
+shares the kind, conflating ontoref's domains with an author portfolio.
+Three concrete drifts follow: the site renders three doors (the fourth, authoring,
+is missing since ADR-064), names the surface "projects", and surfaces the membrane
+nowhere.
+The cost is known and verified: routing a new/renamed kind crosses rustelo's
+build-time codegen (`rustelo_tools/src/build/build_tasks/` consumes
+`content-kinds.toml` + `routes.ncl`, baked into `rustelo-htmx-server`). The content
+registry is dynamic (`ContentKindRegistry { kinds: HashMap }`) but routing is not.
+A vestigial `doors` kind already exists in `content-kinds.ncl` reusing
+`ProjectsCTAView`, so no new Rust view is required — only the boundary crossing
+(a rustelo rebuild + reinstall of the `~/.local/bin` binaries).
+
+Decision
+
+The door/domain surface is served by a dedicated `dominios` content-kind, replacing
+the reused `projects` kind. This reverses the gen-spine-pages.nu "reuse the
+already-compiled projects kind" shortcut, accepting the rustelo rebuild as the cost
+of an honest surface.
+Kind key: `dominios` (the project's settled vocabulary). Route `/dominios` for
+ both languages (a brand term, not localized; `/domains` as an EN route is a
+ deferred sub-choice). The kind reuses `ProjectsCTAView` (no new Rust view); it is
+ registered in `content-kinds.{toml,ncl}`, `content.ncl`, and `routes.ncl`, then
+ activated by a rustelo rebuild + reinstall.
+The `dominios` content is PROJECTED from `spine.ncl` by `gen-spine-pages.nu` — all
+ FOUR doors (developer, infrastructure, personal, authoring) into
+ `content/dominios/<lang>/`, served at `/dominios/<key>`; the spine hrefs
+ (`/projects/*` → `/dominios/*`) update in `spine.ncl`, `home_spine.ftl`, `home.j2`.
+The membrane (content-vehicle-convergence, ADR-064: librosys↔rustelo) is surfaced
+ on `/dominios` as a NON-door block — five domains, four doors, one membrane — so
+ the asymmetry is visible, never flattened into a fifth door card.
+The framework `projects` kind is decoupled from ontoref's domains. Whether the
+ legacy portfolio (kogral, stratumiops…) is removed or kept as a separate
+ author-portfolio surface is a follow-on decision, not part of this ADR.
+This touches the rustelo framework (a separate vehicle codebase), NOT ontoref's
+shipped protocol surface (code/ontology/schemas, install/templates) — no ontoref
+migration.
+
+Constraints
+
+Hard The door/domain diffusion surface MUST be served by a dedicated `dominios` content-kind (route `/dominios`), not the reused `projects` kind. The generated content MUST NOT carry `content_type: "projects"` for the doors.Hard The `dominios` content MUST be projected from `spine.ncl` by gen-spine-pages.nu (all four doors), never hand-authored. The generated files MUST carry the GENERATED banner so drift checks catch hand edits.Soft The content-vehicle-convergence membrane (librosys↔rustelo) SHOULD be surfaced on `/dominios` as a non-door block. The site MUST NOT render rustelo as a fifth door nor merge developer and authoring into one door — preserving four doors over five domains.
+
+Alternatives considered
+
+Relabel only — reuse the `projects` kind, rename the visible strings to Dominios/Domains — rejected: Restart-cheap but dishonest: route, content_type, directory and view stay `projects`. The name↔thing drift the decision exists to remove would persist in the structure a consumer actually reads.Keep reusing `projects` as-is (the status quo shortcut) — rejected: The shortcut's own rationale was 'avoid the rebuild', not 'projects is the right model'. With the domain model now settled (ADR-064), the structural lie is no longer acceptable.Rename the kind key `dominios` but localize the route as en=`/domains`, es=`/dominios` — rejected: Defensible for bilingual consistency, but `dominios` is the project's chosen brand term for the surface; a single canonical route avoids a split. Kept as a deferred sub-choice, not a blocker.Author the `dominios` content by hand instead of projecting it from spine.ncl — rejected: Re-creates the exact hand-maintained drift this plan was opened to fix (Layer 1). The generator already projects the doors; it must point at the new kind, not be bypassed.
+
+Anti-patterns
+
+Relabeling the Strings While the Structure Stays `projects` — The visible i18n is renamed to Dominios while the route, content_type, directory and view stay `projects`. The surface reads 'Dominios' but every machine-facing layer still says projects — the drift is hidden, not removed.Hand-Authoring the Dominios Content — The dominios door pages are written by hand instead of projected from spine.ncl, re-creating the Layer-1 hand-maintained drift the plan was opened to fix.Flattening the Membrane into a Fifth Card — The site renders five domain cards (rustelo among them) or merges developer and authoring, collapsing the 4-doors/5-domains asymmetry and re-modeling the synergy on the audience axis — the ADR-064 anti-pattern, now on the site.
+
+Related ADRs
+
+ADR-064 · ADR-057 · ADR-012 · ADR-035
diff --git a/site/site/content/adr/en/accepted/adr-065.ncl b/site/site/content/adr/en/accepted/adr-065.ncl
new file mode 100644
index 0000000..0e428c9
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-065.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-065",
+ title = "The Door/Domain Surface Is Served by a Dedicated `dominios` Content-Kind, Not the Reused `projects` Kind",
+ slug = "065",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/065",
+ graph = {
+ implements = [],
+ related_to = ["adr-064", "adr-057", "adr-012", "adr-035"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-066.md b/site/site/content/adr/en/accepted/adr-066.md
new file mode 100644
index 0000000..639218c
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-066.md
@@ -0,0 +1,104 @@
+---
+id: "adr-066"
+title: "Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor: the Check Decides, Never the Reporter"
+slug: "066"
+subtitle: "Accepted"
+excerpt: "ADR-063 accepted the Work-Order Governance Unit as an APPROACH and explicitly"
+author: "ontoref"
+date: "2026-07-04"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-066", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-063 accepted the Work-Order Governance Unit as an APPROACH and explicitly
+gated its promotion to a built substrate on a second realized instance
+exercising signed witnesses end-to-end. That gate is now discharged twice
+over: hand-wired instances in the provisioning constellation (its ADR-050,
+signed L1/L3 receipts), and — decisive — a falsación run on ontoref itself
+(2026-07-04, `.coder/2026-07-04-governed-delivery-falsacion.done.md`) that
+drove the ACTUAL mode-DAG mechanism through eight refutation attempts.
+Before this work, the executor did not enforce what the mode narrated — the
+exact failure shape ADR-063 records, reproduced one level down, inside the
+enforcement machinery itself: `'Block` guards only printed WARN (`run start`
+never evaluated them, contradicting both ADR-011's decision text and the
+schema's own comment); `steps[].verify` was dead code in BOTH executors —
+`step report --status pass` was taken on faith even on steps declaring a
+check, so an agent could report `witness-pre pass` without invoking the
+witness; `postconditions` were prose `mode complete` never read, while the
+mode's own step text claimed a signature verification that did not exist;
+committer-identity ratification could not distinguish actors sharing one git
+config. The falsación found three more defects the reviews had missed: a
+contract check that vacuous-passed from any wrong cwd (`! grep` on a missing
+file exits 0), exempt-matching by substring that exempted unrelated
+violations, and first-wins step reads that made 'resolve and re-run this
+step' structurally impossible.
+
+Decision
+
+Promote governed delivery to a built, enforcing substrate. The semantic core,
+frozen as three design gates before executor work started:
+ONE RULE ABOVE ALL — where a machine check exists, the check decides;
+ a reporter's word can refuse to be contradicted but never override.
+- Run variables (⛓G-1): a mode declares `vars_required`; `run start`
+ receives each as a KEY=VALUE argument, refuses to start when one is
+ missing, persists them in run.json; the executor substitutes exactly the
+ declared `${KEY}` occurrences in guard/verify/postcheck commands —
+ undeclared `${…}` passes through to the shell untouched.
+- Guards are preconditions: evaluated once at `run start`, post-
+ substitution, fail-closed — a failing `'Block` guard aborts before any
+ run state exists; `'Warn` prints and continues. This makes ADR-011's
+ "'Block aborts execution" true for the run/step-report path.
+- Verify decides (⛓G-2): a step declaring `verify` has its status DERIVED
+ from the check's exit code — the executor runs it; an explicit --status
+ contradicting the derived one is refused with nothing recorded; `skip`
+ on an executed verify is refused; `cmd` is informational and never
+ executed by `step report`. A verify still carrying a bare `{word}` form
+ placeholder after resolution is form-scoped: not runnable in that
+ context, explicit --status required. steps.jsonl records `status_source`
+ (`verify` / `reported` / `reported-form-scoped-verify`) so the audit
+ trail states who decided every status.
+- Completion is machine-checked: `postchecks | Array { desc, cmd }` run at
+ `mode complete`, fail-closed, each failure a named blocker; an
+ incomplete run exits 1 (CI can gate on it). `postconditions` remains
+ human prose. The latest report of a step supersedes earlier ones —
+ matching the executor's own 'resolve and re-run' instruction.
+- Every signature is a separate, human-executed, out-of-band act (⛓G-3):
+ ratification is a detached minisign signature over the SOW, verified
+ against `.governance/witness.pub` (data in the governed repo, not env) —
+ never committer identity; the receipt gets a second detached signature;
+ the witness NEVER signs its own output; no schema ever carries an
+ embedded signature field (a file cannot reference a signature computed
+ over its own bytes including that reference).
+- The witness is honest about location and exemption: contract checks run
+ with cwd pinned to the governed repository root (the parent of the
+ `.governance/` the SOW lives in), so a relative path can never vacuous-
+ pass from an unrelated cwd; an exempt-bearing contract must emit one
+ violating path per stdout line and exemption subtracts exact paths — the
+ verdict is `exempt` only when EVERY violation is exempt.
+Distribution rides the EXISTING adoption channel, no new mechanism: the
+ mode ships in the installed reflection tree; the witness stays single-
+ source in the code repo and is PROJECTED by install into
+ `reflection/bin/witness.nu` (one source, frontier inside the build — never
+ vendored into consumer repos); SOW validation is an opt-in contract
+ (`ontology/schemas/sow.ncl`); migration 0041 (protocol 0.1.17 → 0.1.18)
+ carries adoption, its check passing for projects that never adopt.
+
+Constraints
+
+Hard A step declaring `verify` MUST have its status derived from the check's exit code by the executor; an explicit --status contradicting the derived one MUST be refused with nothing recorded.Hard `'Block` guards MUST be evaluated at `run start`, post-substitution, and a failure MUST abort before any run state exists. A WARN-only evaluation of a 'Block guard is a regression to narrated enforcement.Hard No script, witness, or executor may sign artifacts; every signature is a human act in the human's own shell, verified as a detached sidecar against a public key stored as data in the governed repository. No schema may carry an embedded signature field.Hard Contract checks MUST run with cwd pinned to the governed repository root, and SOW checks SHOULD assert file existence before inverting a match on it — a check that can pass vacuously from a wrong cwd is not a contract.Soft An exempt-bearing contract MUST emit one violating path per stdout line; exemption subtracts exact paths and the verdict is `exempt` only when every violation is exempt. Substring matching against check output is forbidden.Soft The witness ships single-source from the code repository, projected by the install pipeline into the shipped reflection tree. It MUST NOT be vendored by copy into consumer repositories.
+
+Alternatives considered
+
+Ratification by git committer identity (the original guard) — rejected: Cannot distinguish actors sharing one git config — the exact environment agents run in. A detached signature against a key the agent provably cannot drive is actor separation by cryptography, not by configuration.Keep --status authoritative and treat verify as advisory documentation — rejected: That is the pre-existing state, disproven live: an agent can and did report a gate step it never executed. Advisory checks are ADR-063's 'typed-but-unforced is still prose-unforced' one level down.Evaluate guards on every step report (where the old WARN loop lived) — rejected: Guards are preconditions; re-running them N times per run is the wrong lifecycle point, costs N shell executions, and still gated nothing. Once at run start, before any run state exists, is where refusal is cheap and clean.Embed the signature (or a signature field) in the SOW/receipt schema — rejected: A file cannot correctly reference a signature computed over its own bytes including that reference. Detached sidecars keep signing fully out-of-band and survive byte-exact restores — proven in falsación F3/F7.A new distribution channel (per-project vendoring of mode+witness) — rejected: Vendor-by-copy is the drift the constellation already forbids (provisioning ADR-046/050, §7.4). The installed reflection tree already reaches every consumer; the witness is projected from one source at install time — no copy to drift.First-wins step records (the pre-existing behavior) — rejected: Made the executor's own instruction ('resolve and re-run this step') structurally impossible — a failed step blocked its run forever. Latest-wins matches the instruction and preserves the full fail→pass history in the append-only log.
+
+Anti-patterns
+
+Prose Claiming a Check the Executor Does Not Perform — A schema comment, step action text, or postcondition asserts that something is verified ('Block aborts', 'mode complete verifies the signature') while no code path performs it. The narration reads as safety while gating nothing — the ADR-063 failure reproduced inside the enforcement machinery.A Contract Check That Passes for the Wrong Reason — A check whose failure mode is indistinguishable from success — `! grep` on a file the cwd does not contain, a test against a path that silently does not exist. The receipt records 'pass' while nothing was examined.One Exemption Silencing Unrelated Violations — Exemption matched by substring against the whole check output marks the entire contract exempt when any exempt path appears — real violations ride out under a neighbor's exemption.A Self-Reported Human Act Whose Effect Nothing Checks — Human-actor steps are self-reported by necessity; the design stays sound only because their EFFECT is machine-verified downstream. A mode whose human step has no subsequent verify or postcheck recreates done-by-assertion for exactly the acts that matter most.
+
+Related ADRs
+
+ADR-063 · ADR-011 · ADR-050 · ADR-052 · ADR-056 · ADR-010
diff --git a/site/site/content/adr/en/accepted/adr-066.ncl b/site/site/content/adr/en/accepted/adr-066.ncl
new file mode 100644
index 0000000..f1f743f
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-066.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-066",
+ title = "Governed Delivery — the Work-Order Unit Promoted to an Enforcing Executor: the Check Decides, Never the Reporter",
+ slug = "066",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/066",
+ graph = {
+ implements = [],
+ related_to = ["adr-063", "adr-011", "adr-050", "adr-052", "adr-056", "adr-010"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-067.md b/site/site/content/adr/en/accepted/adr-067.md
new file mode 100644
index 0000000..b88fc2e
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-067.md
@@ -0,0 +1,83 @@
+---
+id: "adr-067"
+title: "Positioning Authorship Follows Evidence-ADR Sovereignty — A Subject Is Authored Where Its Anchoring ADRs Are Accepted; Peers Reference It by Role, Never Duplicate"
+slug: "067"
+subtitle: "Accepted"
+excerpt: "ADR-035 established positioning as a queryable protocol surface and ADR-043"
+author: "ontoref"
+date: "2026-07-04"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-067", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-035 established positioning as a queryable protocol surface and ADR-043
+modelled it as four elements (WHAT / FOR WHOM / HOW bound by the Proof seam),
+governed by the five coherence rules of `ore positioning audit` — the first of
+which, AnchorDrift, requires that every `evidence_adr` cited by a Differentiator
+or Proof resolves to an Accepted ADR. Both ADRs were authored when the
+positioning surface had exactly one occupant: ontoref itself. In a single-project
+surface, "Accepted" silently meant "Accepted in this project", and the question
+of WHOSE ADR set AnchorDrift resolves against never had to be asked.
+That assumption broke the moment a second subject appeared. The rustelo framework
+is positioned two ways at once: (1) inside ontoref's own positioning it is a
+REFERENCED ROLE — "the publishing vehicle" at the content-vehicle-convergence
+membrane (vp-009, audience-authoring-knowledge, ADR-064) — and (2) as a framework
+with its own market position against Cot / Axum / Leptos it is a SUBJECT with its
+own Differentiator, Competitor, Audience and ValueProp. The subject's differential
+value is anchored by rustelo's ADRs (adr-001 build-time codegen, adr-002 custom
+routing, adr-003 layered override, adr-006 rendering profile) — which are Accepted
+only in rustelo's ADR catalogue, not ontoref's.
+Working the placement through surfaced the load-bearing fact: a rustelo
+Differentiator authored inside ontoref's `.ontoref/positioning/` would FAIL
+AnchorDrift, because ontoref does not own those ADRs. Authored inside rustelo's
+own `.ontoref/positioning/`, the same subject passes — verified: `ore positioning
+audit` run from rustelo reports `adr-drift: none`. The audit already resolves
+`evidence_adrs` against the AUTHORING project's ADR set; the framework simply
+never named where that places a subject. This is the same authority-upstream /
+reference-not-duplicate principle ADR-028 established for the ontology layer
+(a project consumes ontologies by reference and cannot mutate them) and that the
+`.rustelo.ontoref/` domain marker and the rustelo publishing-contract already
+manifest — now made explicit for the positioning surface.
+
+Decision
+
+Positioning authorship follows evidence-ADR sovereignty. State it as a protocol
+rule over the ADR-043 surface:
+A positioning SUBJECT — a Differentiator, Competitor, ValueProp, or Proof whose
+ admissibility depends on `evidence_adrs` — is authored in the `.ontoref/
+ positioning/` of the project whose ADR catalogue Accepts those ADRs. AnchorDrift
+ resolves `evidence_adrs` against the AUTHORING project's ADR set; therefore the
+ authoring project is fixed by ADR sovereignty, not by editorial convenience.
+A peer project may REFERENCE that subject only by ROLE — a prose mention or an
+ id in one of its own authored records (e.g. ontoref's vp-009 naming rustelo as
+ "the publishing vehicle") — and MUST NOT re-author the peer's typed subject in
+ its own tree. The reference carries the relationship; the subject stays sovereign.
+This generalises the single-project AnchorDrift assumption of ADR-043 to a
+multi-project positioning surface without adding a kind or a schema field. It is
+additive and opt-in (ADR-029): a project with no `positioning/` directory, or one
+that references no peer, pays zero adoption cost, and every existing positioning
+file validates unchanged. The shared `make_*` helpers are imported by the
+authoring project (rustelo imports ontoref's `code/ontology/defaults/positioning.ncl`
+via its declared `nickel_import_paths`), so the schema is authored once in ontoref
+and consumed by every positioning surface — the same producer/consumer split as
+the rustelo publishing-contract.
+
+Constraints
+
+Hard A positioning subject that cites `evidence_adrs` MUST be authored in the `.ontoref/positioning/` of the project whose ADR catalogue Accepts those ADRs. `ore positioning audit`, run from that project, MUST report no AnchorDrift for the subject. Placing such a subject in a project that does not Accept its evidence ADRs is a drift finding, not a stylistic choice.Soft A project MUST reference a peer's positioning subject only by ROLE — a prose mention or an id in its OWN authored record — and MUST NOT re-author the peer's typed subject in its own tree. Cross-references between subjects are by id (competitor_ids, differentiator_ids), never by copied record.Soft The sovereignty rule MUST stay additive and opt-in: a project with no `positioning/` directory, or one that references no peer subject, MUST validate unchanged and pay zero adoption cost. The rule adds no kind, folder, or required field to ADR-043's surface.
+
+Alternatives considered
+
+Author rustelo's positioning subject inside ontoref's positioning layer (single-layer, all subjects in one place) — rejected: Chosen first for expedience, then rejected on a mechanical basis: the subject's evidence_adrs (rustelo's adr-001/002/003/006) are not Accepted in ontoref's catalogue, so AnchorDrift fails. Placing the subject where its ADRs are not sovereign is not a style choice the audit tolerates — it is a hard drift finding.Duplicate the subject in both projects (nivel-0 and nivel-1 copies kept in sync) — rejected: Unsound, not merely redundant. The ontoref copy would have to strip evidence_adrs (unanchored, rejected by _diff_requires_anchor) or cite rustelo ADRs it cannot Accept (AnchorDrift failure). Duplication also reintroduces the drift the AnchorDrift rule exists to prevent — two records that can diverge with no single sovereign truth.Add a cross-project evidence_adr resolver so a subject in project A can cite Accepted ADRs from project B — rejected: Would let a host author a peer's subject by reaching into the peer's ADR catalogue — dissolving sovereignty rather than respecting it, and coupling the audit to a cross-project fetch it does not need. The reference-by-role rule achieves the same visibility (the relationship is expressed) without importing the peer's evidence, and stays faithful to ADR-028's consume-by-reference-cannot-mutate model.
+
+Anti-patterns
+
+Authoring a Peer's Positioning Subject in the Host's Tree — A contributor places project B's Differentiator/ValueProp inside project A's `.ontoref/positioning/` to 'own the story in one place'. Its evidence_adrs are Accepted in B, not A, so `ore positioning audit` from A reports AnchorDrift — the subject is homeless, not centralised.Duplicating a Subject Across Projects and Syncing by Hand — The same Differentiator is copied into both the sovereign project and a peer to keep each surface 'complete'. The copies drift, and the peer copy must either strip its anchor (unanchored, rejected) or cite un-Acceptable ADRs (AnchorDrift). Two records, no single sovereign truth — the exact drift AnchorDrift exists to prevent.Reaching Into a Peer's ADR Catalogue to Anchor a Local Subject — To author a peer's subject locally, a contributor adds a resolver so evidence_adrs can cite a peer project's Accepted ADRs. This dissolves ADR sovereignty — the host now anchors claims on decisions it did not make and cannot govern — the opposite of ADR-028's consume-by-reference-cannot-mutate model.
+
+Related ADRs
+
+ADR-043 · ADR-035 · ADR-028 · ADR-029 · ADR-055
diff --git a/site/site/content/adr/en/accepted/adr-067.ncl b/site/site/content/adr/en/accepted/adr-067.ncl
new file mode 100644
index 0000000..c015cb7
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-067.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-067",
+ title = "Positioning Authorship Follows Evidence-ADR Sovereignty — A Subject Is Authored Where Its Anchoring ADRs Are Accepted; Peers Reference It by Role, Never Duplicate",
+ slug = "067",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/067",
+ graph = {
+ implements = [],
+ related_to = ["adr-043", "adr-035", "adr-028", "adr-029", "adr-055"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-068.md b/site/site/content/adr/en/accepted/adr-068.md
new file mode 100644
index 0000000..ba575cd
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-068.md
@@ -0,0 +1,87 @@
+---
+id: "adr-068"
+title: "Declarative Constellation Surface and the Reconciling Sync Mode — Reversible Auto, Human-Gated Remotes"
+slug: "068"
+subtitle: "Accepted"
+excerpt: "ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the"
+author: "ontoref"
+date: "2026-07-05"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-068", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+ADR-062 fixed the constellation TAXONOMY (Primary | Addon | Projection) and the
+forge rule (Forgejo canonical · GitHub one-way mirror · rad sovereign · vault
+never mirrored) as prose plus Soft/Hard constraints on ontoref-self's own repos.
+What it did NOT provide is a mechanism: how a project DECLARES its constellation
+and how the declared topology is RECONCILED against the filesystem — for first
+setup and, equally, for updating an existing partial constellation.
+The gap surfaced concretely. ADR-013 already ships a per-project `vcs` block
+(manifest.ncl: primary/direction/mirrors) and a `vcs.nu` module (push --mirror,
+cross-project catalog, drift status) plus the `adopt_repo_distribution` mode —
+but all per-PROJECT. The constellation has multiple members, most of them NOT
+individually onboarded (outreach, vault, desktop are plain repos or not yet
+repos), so per-member manifests do not exist to aggregate. CI (Woodpecker) and
+the private dev-internal overlay (.coder/.claude, ADR-062
+dev-internal-overlay-never-mirrored) were declarable nowhere at the constellation
+level. And the operator-facing question — "given the declared topology, what is
+done and what must I still do?" — had only hand-written runbooks as an answer.
+The forces: a reconciler must (a) read a single declared source of the whole
+constellation, (b) converge the parts that are cheap to reverse without a human
+in the loop, yet (c) never silently perform the parts that are expensive to
+reverse or external — creating a Forgejo repo, pushing, wiring a mirror. A mode
+that created repos and pushed on its own would be convenient and wrong: it would
+mutate remotes from an inferred plan, exactly the failure `governed-delivery`
+(ADR-041) was built to prevent for Work Orders.
+
+Decision
+
+Add a declarative constellation surface and a reconciling mode that applies the
+reversible half itself and emits the irreversible half to a human.
+DECLARATION (the single editable source of the topology)
+ - reflection/schemas/constellation.ncl — a member is {name, path, category
+ (Primary|Addon|Projection), canonical, sovereign+rid, mirror (Auto|Never),
+ ci (None|Woodpecker|GithubActions), overlay, overlay_url}. The forge rule is
+ a TYPED contract, not prose: a 'Projection owns no repo (canonical_url /
+ mirror_url must be empty), a 'Never member carries no mirror, a 'Radicle
+ sovereign requires a rid. Illegal combinations fail at nickel-export time.
+ - .ontoref/ontology/constellation.ncl — the project's instance. Editing it is
+ how the topology changes; the mode is how the change is applied.
+RECONCILER (reflection/bin/constellation.nu — offline, install-independent)
+ - plan — per member, declared-vs-actual + the [auto]/[you] next actions.
+ - apply-auto — applies ONLY the reversible half: sets up the private overlay
+ (never mirrored) for members that declare one and have content.
+ Never inits a repo, never touches a remote.
+ - check [--auto-only] — exit 1 on drift; --auto-only gates the reversible half.
+ It NEVER mutates a remote. Server-side facts (does the forge repo exist? is the
+ push-mirror wired?) are REPORTED as pending human steps, never asserted.
+MODE (reflection/modes/constellation-sync.ncl)
+ guard(declaration parses) → plan(report) → apply_overlays(reversible, agent) →
+ emit_remote_steps(actor = 'Human: create repo / push / wire mirror). A
+ postcheck gates that the reversible overlay job is done; full convergence
+ includes the human steps and the mode is re-run idempotently to reconverge.
+BOUNDARY (the load-bearing rule). The mode's automated steps are exactly the
+reversible ones. Every remote-creating or remote-pushing action is actor 'Human
+and emitted as instruction — the same human-gated boundary as governed-delivery's
+witnessed signing. Adoption is opt-in: a single-repo project declares nothing and
+the mode is inert.
+
+Constraints
+
+Soft A constellation's topology is a declared source (.ontoref/ontology/constellation.ncl conforming to the Constellation schema), not inferred from the filesystem.Hard The forge rule (Projection owns no repo, Never carries no mirror, Radicle needs a rid) is enforced by a typed schema contract that fails nickel-export on violation, not by prose.Hard The constellation-sync mode never creates a repo or pushes to a remote in an automated step; every remote-creating/pushing action is actor 'Human and emitted as instruction.Soft The reconciler (constellation.nu) inspects git remotes/dirs locally and reports server-side facts as pending; it issues no remote-mutating git command.
+
+Alternatives considered
+
+Extend adopt_repo_distribution to iterate over members. — rejected: Conflates two responsibilities: adopt_repo_distribution declares ONE member's backend; constellation-sync reconciles the WHOLE constellation and emits human remote steps. Growing one mode to do both muddies its contract; a separate mode keeps each single-purpose.A full-auto mode that creates Forgejo repos and pushes via API tokens. — rejected: Mutating remotes from an inferred plan is exactly the failure governed-delivery (ADR-041) prevents. Creating a repo and pushing are expensive to reverse and external; automating them trades a real sovereignty/safety risk for convenience. Human-gated emission preserves the boundary.Keep the pattern as static runbooks / a QA entry only. — rejected: Docs cannot enforce the forge rule or report per-member drift. The typed contract + reconciler make the rule executable and the state queryable; the runbook survives as the mode's template payload, not the mechanism.Infer the constellation by scanning the filesystem for repos. — rejected: Inference cannot express intent (a member declared but not yet materialized, a Projection that must NOT become a repo, a vault that must NOT be mirrored). A declaration carries intent the filesystem lacks.
+
+Anti-patterns
+
+A Sync Mode That Creates Repos and Pushes On Its Own — Letting a reconciling mode create Forgejo repos, push, or wire mirrors from an inferred plan for convenience. Mutates external, expensive-to-reverse state without a human deciding — the failure governed-delivery prevents for Work Orders, reintroduced at the constellation layer.Reconstructing the Constellation From the Filesystem — Deriving the constellation's membership and forge policy by scanning for repos instead of reading a declaration. Loses intent: a member declared but not yet materialized, a Projection that must NOT be a repo, a vault that must NOT be mirrored — none are inferable from the tree.Merging the Member Backend and the Constellation Policy — Treating the per-project manifest `vcs` block (ADR-013, a member's working backend) and the constellation declaration (this ADR, the constellation's forge policy) as one surface. They describe different scopes; collapsing them re-couples a member's release cadence to the constellation topology.
+
+Related ADRs
+
+ADR-062 · ADR-048 · ADR-013 · ADR-041 · ADR-028 · ADR-054 · ADR-026 · ADR-014
diff --git a/site/site/content/adr/en/accepted/adr-068.ncl b/site/site/content/adr/en/accepted/adr-068.ncl
new file mode 100644
index 0000000..62252e5
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-068.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-068",
+ title = "Declarative Constellation Surface and the Reconciling Sync Mode — Reversible Auto, Human-Gated Remotes",
+ slug = "068",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/068",
+ graph = {
+ implements = [],
+ related_to = ["adr-062", "adr-048", "adr-013", "adr-041", "adr-028", "adr-054", "adr-026", "adr-014"],
+ },
+}
diff --git a/site/site/content/adr/en/accepted/adr-069.md b/site/site/content/adr/en/accepted/adr-069.md
new file mode 100644
index 0000000..caa7624
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-069.md
@@ -0,0 +1,93 @@
+---
+id: "adr-069"
+title: "Typed Warrant and Reconciled Edge Contract — Provenance and Graph Integrity the Validator Can Prove"
+slug: "069"
+subtitle: "Accepted"
+excerpt: "The `context-as-property` work (2026-07-09, from Jessica Talisman's essay naming,"
+author: "ontoref"
+date: "2026-07-09"
+published: true
+featured: false
+category: "accepted"
+tags: ["adr-069", "adr", "accepted"]
+css_class: "category-adr"
+---
+Context
+
+The `context-as-property` work (2026-07-09, from Jessica Talisman's essay naming,
+from library science, the category ontoref occupies) recorded field-for-field the
+convergence between the glossary term schema and ANSI/NISO Z39.19 — and, with
+ondaod honesty, the three gaps the lineage does NOT certify. Two of them are
+structural claims the ontology graph could not yet back:
+1. WARRANT — "on whose authority does this node exist?" — was typed only on
+ glossary terms (`origin`) and value-props (`evidence_adrs`); inside the
+ ontology's own node descriptions it was prose. A node's provenance could be
+ asserted in a paragraph and never queried.
+2. Z39.19 §8.1.1 reciprocity ("every relation A→B carries B→A, for all types of
+ relationships") was stricter than any validator ontoref ran on its own graph.
+Verifying the edge surface to act on gap 2 surfaced a dormant third-party defect:
+a THREE-WAY contract divergence on edges, alive but asleep. The schema enum
+(`code/ontology/schemas/core.ncl`) listed four kinds never used (ValidatedBy,
+FlowsTo, CyclesIn, LimitedBy) and omitted four in active use (Contradicts,
+DependsOn, Implies, Resolves); it typed `weight` as `Number` while the data has
+always carried symbolic `'High/'Medium/'Low`; and the Rust loader
+(`types.rs::EdgeType`, `weight: f64`) mirrored the stale schema. `nickel export`
+passed anyway because the edge records in `core.ncl` are BARE — no `make_edge`, no
+`CoreConfig` on the top level — so no contract ever bit them. The Rust typed path
+(`Core::from_value`) would REJECT the real `core.ncl` (`kind:"Resolves"`,
+`weight:"High"`), but it has zero live callers: the daemon seeds edges from raw
+JSON. A latent lie, not an active break.
+
+Decision
+
+Close both admitted gaps and reconcile the dormant divergence, in one move, with a
+validator as the enforcement surface where the NCL contract cannot reach.
+WARRANT (gap 1) — additive, audited, never required:
+ - `WarrantRef = { kind | [| 'Adr, 'Emergence, 'Interview, 'Session, 'External |],
+ ref | String, note | String | default = "" }` and
+ `Node.warrant | Array WarrantRef | default = []` in the core schema. The
+ default makes every existing node validate unchanged (no migration: the
+ trigger rule fires only for REQUIRED fields).
+ - The evidence itself lives in reflection (emergence.log, interviews); the node
+ CITES it, never duplicates it. The Rust `Node` struct is left unchanged —
+ serde drops the field exactly as it already drops `axis` — until a surface
+ needs to expose it.
+ - Enforcement is an AUDIT, not a contract: `validate ontology --warrant` flags a
+ non-Axiom node (Practice / Tension / — in a consumer — Project / Moment) that
+ carries neither `adrs` nor `warrant`. Soft, direction-reporting; a REQUIRED
+ warrant would be ceremony-capture. Axioms are self-warranting (they are the
+ Yang floor, invariant = true).
+EDGE RECONCILIATION (gap 2 + the dormant bug) — schema and Rust follow the data:
+ - `edge_type` becomes the nine kinds ACTUALLY used (Complements, Contains,
+ Contradicts, DependsOn, Implies, ManifestsIn, Resolves, SpiralsWith,
+ TensionWith); `weight` becomes the symbolic `weight_type = [| 'High, 'Medium,
+ 'Low |]`. Rust `EdgeType` and `Weight` mirror them exactly. The typed path now
+ tells the truth about the data.
+ - Reciprocity is DERIVABLE, not stored (the correct DAG adaptation of §8.1.1):
+ `code/ontology/defaults/edge-inverses.ncl` freezes, per kind, an inverse label
+ for reverse-direction rendering, and the set of symmetric kinds (Complements,
+ Contradicts, SpiralsWith, TensionWith) whose inverse is the kind itself.
+ - `validate ontology` is the graph-integrity gate the bare-record edges left
+ open: dangling endpoints (Hard), every kind present in the inverse-map
+ vocabulary (Hard), symmetric reciprocity consistency (Soft). It reads
+ `core.ncl` via `nickel export`, never through the typed Rust path, so it works
+ daemon-free.
+The nine-kind vocabulary is now single-source across three surfaces (schema enum,
+ Rust `EdgeType`, `edge-inverses.ncl`); a new kind must be added to all three, and
+ `validate ontology` fails until it is.
+
+Constraints
+
+Hard `Node.warrant` MUST carry `default = []` so existing nodes and consumer graphs validate unchanged; no schema may make warrant a required field.Hard Warrant provenance MUST be enforced as a Soft audit (`validate ontology --warrant`) that reports the gap, never as a Hard reject of un-warranted nodes.Hard The edge-kind vocabulary MUST stay identical across the schema enum, the Rust `EdgeType`, and `edge-inverses.ncl`; a new kind added to one MUST be added to all three.Hard Edge `weight` MUST be the symbolic `weight_type` ('High/'Medium/'Low) in both the schema and Rust — never a Number — matching the data the graph has always carried.Soft `validate ontology` MUST check dangling endpoints and kind-in-vocabulary against `core.ncl` via `nickel export`, working without a running daemon (local-fallback, ADR-029).Soft Reciprocity MUST be derivable, not stored: symmetric kinds (their inverse is the kind itself) are frozen in `edge-inverses.ncl`, and a stored reverse edge of a symmetric kind carrying a DIFFERENT kind is a reciprocity inconsistency.
+
+Alternatives considered
+
+Leave warrant as prose in node descriptions — rejected: The status quo the essay named as a gap: provenance asserted in a paragraph is not queryable, not auditable, and indistinguishable from an unsupported claim. The whole point of the context-as-property lineage is that warrant is a typed property, not narration.Make warrant a required Node field — rejected: Ceremony-capture (adr-029): every node authored under a Hard gate that may not fit, and a mass migration of 59 existing nodes. Collapses formalization-vs-adoption to the Yang pole. The audit reports the gap without forbidding the node.Migrate the edge DATA to the stale schema (numbers, drop the four extra kinds) — rejected: Inverts the direction of truth: the data is the ground reality (168 edges across nine kinds with symbolic weights), the schema was the aspirational fiction. Schema and Rust follow the data, not the reverse — and rewriting 168 records to fit a lie is pure churn.Store both edge directions (literal Z39.19 §8.1.1) — rejected: In a directed DAG with a fixed kind vocabulary, storing B→A for every A→B is redundant noise that doubles the edge set and invites the two directions to drift. Derivable reciprocity (an inverse label per kind + a symmetric set) renders both directions without persisting either.Wrap every edge in make_edge so the per-record NCL contract bites — rejected: 168 mechanical edits, high churn and merge risk, and it still would not catch dangling endpoints or cross-edge reciprocity (a record contract sees one record). A graph-level validator is one surface with strictly greater reach.
+
+Anti-patterns
+
+A Typed Contract That Diverges From Its Data Because Nothing Enforces It — A schema, a Rust type and the data drift apart while every path that would surface the mismatch is dead (bare records skip the contract; the typed loader has no callers). The crispness of the types lends unearned credibility to a lie nothing checks — tier-0 false certainty at the contract layer.Provenance Asserted in a Description, Never Queryable — A node's authority ('surfaced in the interview', 'per ADR-055') lives only in its prose description — not typed, not auditable, indistinguishable from an unsupported claim. The Z39.19 warrant gap the context-as-property essay admitted.Making Provenance a Hard Gate — Turning warrant into a required field or a Hard reject — every node authored under a gate that may not fit, a mass migration forced on consumers. Enforcement as the product; the Yang collapse of formalization-vs-adoption.An Enum That Lists What Was Imagined, Not What Is Used — A type enumerates kinds that never appear in the data while omitting kinds the data actively uses. The schema reads as a governed vocabulary but describes a fiction; a graph validator built on it checks the wrong set.An Agent Moves the Protocol Version on Its Own Criterion — An agent edits reflection/version.ncl protocol_version, or adds a version-bumping protocol_version delta to a migration, because a migration 'implies' a bump. The protocol version is an outward release act (⛓G3 single-source in reflection/version.ncl); coupling it to each migration delta lets an agent move the version by its own judgment. check_chain (audit C2) enforces version↔chain CONSISTENCY, not AUTHORSHIP — so a consistent-but-unauthorized bump passes the audit while still being a decision the agent had no standing to make. Surfaced 2026-07-09 when this very session bumped 0.1.19→0.1.20 unprompted while authoring migration 0043.
+
+Related ADRs
+
+ADR-029 · ADR-035 · ADR-050 · ADR-001 · ADR-028
diff --git a/site/site/content/adr/en/accepted/adr-069.ncl b/site/site/content/adr/en/accepted/adr-069.ncl
new file mode 100644
index 0000000..c0d470b
--- /dev/null
+++ b/site/site/content/adr/en/accepted/adr-069.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-069",
+ title = "Typed Warrant and Reconciled Edge Contract — Provenance and Graph Integrity the Validator Can Prove",
+ slug = "069",
+ excerpt = "",
+ tags = ["adr", "accepted"],
+ page_route = "/adr/069",
+ graph = {
+ implements = [],
+ related_to = ["adr-029", "adr-035", "adr-050", "adr-001", "adr-028"],
+ },
+}
diff --git a/site/site/content/adr/en/proposed/adr-020.md b/site/site/content/adr/en/proposed/adr-020.md
new file mode 100644
index 0000000..b19b19d
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-020.md
@@ -0,0 +1,86 @@
+---
+id: "adr-020"
+title: "Three-Layer Model for Project Ontoref Instances"
+slug: "020"
+subtitle: "Proposed"
+excerpt: "Multiple projects now adopt the ontoref protocol (lian-build is the explicit"
+author: "ontoref"
+date: "2026-05-03"
+published: true
+featured: false
+category: "proposed"
+tags: ["adr-020", "adr", "proposed"]
+css_class: "category-adr"
+---
+Context
+
+Multiple projects now adopt the ontoref protocol (lian-build is the explicit
+external case under bl-002 / bl-008). Field experience across ontoref +
+lian-build + provisioning shows that an ontoref-onboarded project's
+repository carries TWO distinct ontoref-shaped layers, while a third layer
+exists outside the project (in caller repositories). Without codification,
+adopters re-derive the model per project, mix layers accidentally, and lose
+boundaries silently:
+- Layer 3 content (caller-side cabling) drifts into a project's qa.ncl,
+ making the FAQ a how-to-deploy guide for one specific caller.
+ - Layer 2 schemas live under reflection/ as 'project notes', drifting
+ from the binary because they're not on the contract path.
+ - Layer 1 architectural rationale ends up in catalog/domains/<id>/
+ contract.ncl, where consumers expecting a typed shape get prose.
+The model has been observed (lian-build/reflection/qa.ncl::lian-build-what-
+and-why), described (ontoref/reflection/qa.ncl::ontoref-three-layer-model),
+and queued for codification (bl-009). This ADR commits the model with
+machine-checkable constraints. Acceptance is gated on the constraints
+running clean across the existing ontoref-onboarded projects (ontoref
+itself, lian-build, provisioning).
+
+Decision
+
+A project's ontoref instance has THREE distinct layers — but only the first
+two live in the project's own repository:
+LAYER 1 — Self-management ontoref (about the project itself)
+ paths .ontology/ reflection/ adrs/
+ audience this project's developers and maintainers
+ purpose describe the project to itself — axioms, FSM dimensions,
+ binding decisions, open questions, accepted knowledge
+ presence MANDATORY on every ontoref-onboarded project
+LAYER 2 — Specialized domain/mode ontoref (the integration surface)
+ paths schemas/ catalog/{domains,modes}/
+ manifest.ncl::registry_provides
+ audience OTHER projects that want to integrate this project
+ purpose the contract surface other projects bind to — typed domain
+ artifacts, orchestration mode artifacts, registry-namespace
+ claim
+ presence OPTIONAL but BICONDITIONAL — a project either has all of
+ {schemas/, catalog/, registry_provides} or none. Half-Layer-2
+ is a contract violation.
+LAYER 3 — Caller-side implementations (NOT in this project)
+ paths <caller>/extensions/<this-project>/
+ <caller>/catalog/components/<...>/ (when consuming)
+ <workspace>/infra/<ws>/integrations/
+ audience operators and CI of caller projects
+ presence PER CALLER, NEVER in this project's repo. Cross-references
+ from this project to Layer 3 are explicit pointers, never
+ copy-paste.
+The three-layer axis is ORTHOGONAL to ADR-018's level hierarchy
+(Base/Domain/Instance). A project at any level may have any combination of
+Layer 1 (always) and Layer 2 (sometimes). Layer 3 is the boundary outward,
+not a property of the project. The 3-layer × 3-level matrix is navigable in
+both axes: 'where in the protocol hierarchy' (level) is independent of
+'where in the project's repo' (layer).
+Cross-layer references inside a project carry an explicit layer-N tag on
+qa entries. A qa entry whose primary topic is a Layer N concern wears
+the layer-N tag; satellite entries (operational how-to, troubleshooting)
+inherit the layer of their anchor without re-tagging.
+
+Constraints
+
+Hard Every ontoref-onboarded project has .ontology/core.nclHard Every ontoref-onboarded project has reflection/qa.nclHard Every ontoref-onboarded project has reflection/backlog.nclHard Every ontoref-onboarded project has an adrs/ directoryHard A federated-peer catalog (catalog/domains/ or catalog/modes/) exists if and only if manifest.ncl declares registry_providesSoft qa entries whose primary topic is a Layer N concern carry the layer-N tag (anchors only; satellites inherit by association)
+
+Alternatives considered
+
+Single 'ontoref content' namespace, no layering — rejected: Observed drift outcomes (Layer 3 in qa, Layer 2 schemas treated as notes, etc.) are caused precisely by absence of layering. The single-namespace alternative is what we're correcting; rejecting it is the entire decision.Two-layer model collapsing self-management with integration-surface — rejected: lian-build's adoption explicitly experienced the schema-vs-rationale split: schemas/build_directives.ncl (Layer 2 contract) and adrs/adr-001-lian-build-as-standalone.ncl (Layer 1 rationale) have different audiences, different change cadences, different validation rules. Collapsing them produced the FAQ-vs-contract confusion that retiring lian-build/.ontology/FAQ.md addressed. The two-layer alternative re-creates that confusion.Make Layer 3 (caller-side) optionally co-resident with Layer 2 in the producer's repo — rejected: Creates ambiguity about who owns the cabling. If a producer's repo carries `extensions/<self>/`, who maintains it when a caller's workspace evolves? The producer doesn't know about the caller's infrastructure; the caller can't depend on the producer to update the cabling on its schedule. Co-residence inverts the integration arrow.Numbered layers (Layer 1 / 2 / 3) vs named layers ('self' / 'integration-surface' / 'caller-side') — rejected: Named layers are more descriptive but verbose; numbered layers are more precise but flat. The compromise (this ADR): use 'Layer N — descriptive name' in prose, 'layer-N' in tags. Tag economy wins; prose retains the descriptive name.
+
+Related ADRs
+
+ADR-001 · ADR-016 · ADR-018
diff --git a/site/site/content/adr/en/proposed/adr-020.ncl b/site/site/content/adr/en/proposed/adr-020.ncl
new file mode 100644
index 0000000..50fd349
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-020.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-020",
+ title = "Three-Layer Model for Project Ontoref Instances",
+ slug = "020",
+ excerpt = "",
+ tags = ["adr", "proposed"],
+ page_route = "/adr/020",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-016", "adr-018"],
+ },
+}
diff --git a/site/site/content/adr/en/proposed/adr-022.md b/site/site/content/adr/en/proposed/adr-022.md
new file mode 100644
index 0000000..0f4d058
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-022.md
@@ -0,0 +1,33 @@
+---
+id: "adr-022"
+title: "Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions"
+slug: "022"
+subtitle: "Proposed"
+excerpt: "ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cann"
+author: "ontoref"
+date: "2026-05-16"
+published: true
+featured: false
+category: "proposed"
+tags: ["adr-022", "adr", "proposed"]
+css_class: "category-adr"
+---
+Context
+
+ADR-017 established that the daemon is structurally excluded from credential resolution: it has no age private key and cannot decrypt sops files. This is load-bearing — any actor with MCP access cannot use the daemon as a credential amplifier. The existing MCP surface has one secret-related tool: ontoref_vault_status (read-only metadata: vault_id, recipient_count, access_sops_present, recipient_groups). The workspace toolchain (secrets.just + provisioning/workspace/tools/secrets.nu) exposes a full secret management lifecycle: gen, new, make, list, pending, show, edit, rekey. The question arose when staging plaintext credential files (*.sops.yaml.template) was added to the workspace workflow, and when CI/CD and agent automation scenarios began requiring machine-readable discovery of pending and existing secrets. Two failure modes emerge if the wrong boundary is drawn: (1) exposing decrypt operations via MCP turns the daemon into a plaintext exfiltration channel — any actor with daemon access and the right MCP token can trigger decryption of any credential file; (2) exposing no operations at all forces agents and CI to shell out to just directly, bypassing the actor authorization model entirely and producing invisible, unaudited secret access.
+
+Decision
+
+Secret management operations on the MCP surface are partitioned into three permanent tiers. Tier 1 — Permanent Exclusions (never on MCP surface): any operation that produces plaintext credential values in a response payload (secret-show, decrypt-to-stdout, raw sops --decrypt), age private key material (.kage content or derived values), and staged plaintext template content (*.sops.yaml.template). These exclusions are structural and cannot be lifted by actor configuration or policy override. Tier 2 — Read-Only Observation (permitted for any authorized actor): ontoref_vault_status (already exists), secret names list (encrypted filenames, no values), pending secret discovery (*.sops.yaml.template filenames without corresponding *.sops.yaml). Tier 2 tools invoke the secrets engine as a read-only subprocess and return structured metadata only. Tier 3 — Orchestrated Mutating Operations (permitted only for actor=admin, requires subprocess isolation, mandatory audit log): secret-gen, secret-make, secret-new, secret-rekey. These invoke the secrets.nu subprocess with the workspace environment variables; the daemon never holds key material — the subprocess reads .kage from the filesystem path configured in the project. Every Tier 3 invocation appends an entry to the vault access log (logs/access.jsonl) recording: actor, operation, secret name, timestamp, exit code. The daemon never buffers subprocess output that contains plaintext — stdout of any secret subprocess is streamed to the caller's MCP response only after the daemon confirms the response payload type is metadata (exit code, names, status), never raw subprocess stdout when the underlying command is decrypt-capable.
+
+Constraints
+
+Hard No MCP tool may return plaintext credential values in its response payload — secret-show and any decrypt-capable operation are permanently excluded from the MCP surfaceHard No MCP tool may return age private key content, sops DEK material, or staged template plaintext in its response payloadHard Tier 3 mutating tools (secret-gen, secret-make, secret-new, secret-rekey) must require actor=admin; agent and developer actors must receive Unauthorized before the subprocess is spawnedHard Every Tier 3 subprocess invocation must append an entry to logs/access.jsonl before returning — a missing log entry for a completed operation is a constraint violationHard The daemon must not forward raw subprocess stdout to the MCP response caller when the invoked secrets.nu subcommand is decrypt-capable (show, edit); for Tier 3 commands, only structured metadata (exit code, names changed, audit entry written) may be returnedHard Every MCP tool in category credentials must declare its tier (1|2|3) as a metadata field at registration time; tools without a declared tier fail daemon startup
+
+Alternatives considered
+
+No expansion of MCP surface (Tier 1 only, current state) — rejected: Forces agents and CI pipelines to shell out to just secret-* outside the actor authorization model. Pending secret discovery, vault compliance checks, and automated secret rotation all become invisible to the audit trail. The automation use case is real and growing; refusing it produces shadow workflows rather than preventing them.Full exposure including decrypt-to-response (no Tier 1 exclusions) — rejected: secret-show and any decrypt-capable operation as MCP tools turns the daemon into a plaintext credential proxy. Any actor with admin token and network access to the loopback daemon can extract credential values without a trace in the vault's own audit log (daemon access log is separate). The structural exclusion of ADR-017 would be effectively nullified — the daemon would hold no key but could produce plaintext on demand.Daemon-as-proxy: daemon acquires .kage path, resolves credentials in-process — rejected: Breaks the load-bearing invariant of ADR-017: 'the daemon cannot be used as a credential amplifier regardless of MCP actor permissions'. In-process resolution means the daemon holds plaintext during the operation lifecycle. Any memory disclosure (core dump, debug interface, language runtime reflection) exposes key material. The subprocess model keeps key material in a child process with a narrower attack surface and no persistence in daemon memory.Separate secret-management daemon with its own MCP server — rejected: Adds operational complexity (two daemons to manage, two auth models to align) without meaningful security gain over subprocess isolation. The tier model achieves the same separation of concerns within the existing daemon's actor authorization infrastructure. A separate daemon would need its own loopback trust model — re-solving a solved problem.
+
+Related ADRs
+
+ADR-017 · ADR-019 · ADR-015 · ADR-005
diff --git a/site/site/content/adr/en/proposed/adr-022.ncl b/site/site/content/adr/en/proposed/adr-022.ncl
new file mode 100644
index 0000000..2d0189a
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-022.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-022",
+ title = "Secret Management Operations on the MCP Surface — Observation, Orchestration, and Permanent Exclusions",
+ slug = "022",
+ excerpt = "",
+ tags = ["adr", "proposed"],
+ page_route = "/adr/022",
+ graph = {
+ implements = [],
+ related_to = ["adr-017", "adr-019", "adr-015", "adr-005"],
+ },
+}
diff --git a/site/site/content/adr/en/proposed/adr-030.md b/site/site/content/adr/en/proposed/adr-030.md
new file mode 100644
index 0000000..b399d04
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-030.md
@@ -0,0 +1,276 @@
+---
+id: "adr-030"
+title: "Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host"
+slug: "030"
+subtitle: "Proposed"
+excerpt: "ADR-029 commits the protocol to permanent tier coexistence: any project"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "proposed"
+tags: ["adr-030", "adr", "proposed"]
+css_class: "category-adr"
+---
+Context
+
+ADR-029 commits the protocol to permanent tier coexistence: any project
+may climb to tier-2 (operations adopted) voluntarily. The piloto self-host
+(ontoref itself) demonstrated tier-2 functionally during the session of
+2026-05-26 — 10 domain operations registered, dispatched, witnessed,
+replay-deterministic. The piloto works because its operations are
+compiled into the `ontoref-daemon` binary via `inventory::collect!` at
+link time: `crates/ontoref-ops/src/ops/*.rs` annotated `#[onto_operation]`
+emit static `OperationEntry` records; `dispatch_op(id, …)` resolves
+against `inventory::iter::<OperationEntry>()`.
+This works because the piloto and the daemon are co-developed in the same
+repository — the daemon binary that serves the piloto IS the binary
+compiled with the piloto's catalog baked in. The 18 other onboarded
+projects (mirador, build-in-layers, lian-build, vapora, stratumiops,
+kogral, libre-{daoshi,wuji,forge}, secretumvault, typedialog, jpl-website,
+personal-ontoref, jpl-ontoref, cloudatasave, DD7pasos, librosys,
+forge-fleet, mina) sit at tier-0 or tier-1; their adoption of tier-2
+would surface a structural gap that the current architecture does not
+address:
+When project P climbs to tier-2, where do P's domain operations live,
+ and how does the daemon discover and invoke them?
+Today's only answer — "compile P's ops into the daemon binary and ship
+that binary to P" — does not scale. It implies a per-project daemon
+build, breaks the "one daemon, mixed-tier projects" model that
+ADR-029's `ontoref-multi-tier-management` QA describes, and ties the
+project's release cadence to the daemon's. For the piloto self-host this
+is acceptable (ontoref's release IS the daemon's release); for any other
+project it is not.
+The catalog has two sides per ADR-029:
+- DECLARATIVE side (NCL): `catalog/operations/<id>.ncl` and
+ `catalog/validators/<id>.ncl`. Per-project, in the project's own repo.
+ Typed against `catalog/schema.ncl` (the protocol-wide contract).
+ - EXECUTIVE side (Rust): the `#[onto_operation]`-annotated functions
+ + `#[onto_validator]`-annotated functions. Today baked into the
+ daemon binary via inventory.
+The declarative side composes cleanly across projects — ADR-028 already
+content-addresses ontologies; the catalog NCL files could be published
+the same way (as Type-2 or Type-3 ontologies in `_ontology_refs.ncl`),
+discovered via `fetch_cell_witness`, type-checked locally. The
+executive side is the harder problem: how does the daemon, running once
+per host and serving N projects of mixed tiers, obtain and invoke the
+Rust handlers for project P's ops?
+The session that produced ADR-029 surfaced this open question
+explicitly: "the daemon binary that hosts ontoref-piloto has the 10
+piloto ops linked at compile time. When lian-build, provisioning, or any
+other project climbs to tier-2 and authors its own catalog, the inventory
+mechanism does not extend — those projects' ops are not in the daemon's
+binary." The piloto demonstrates that tier-2 works; this ADR
+acknowledges that scaling tier-2 across the ecosystem needs a discovery
+mechanism beyond link-time inventory.
+No external pressure yet justifies picking a mechanism. Only ontoref
+itself is at tier-2. The remaining 18 projects have not signalled tier-2
+ambitions. This ADR records the problem, the design space, and the
+trigger-based decision pattern (consistent with ADR-026 / D15 commitment
+backend deferral, ADR-027 / D18 sync backend deferral, ADR-029 / D23 blob
+backend deferral) — IMPLEMENT WHEN TRIGGERED, not before.
+
+Decision
+
+The catalog-discovery problem is real, named, and deferred behind
+explicit trigger thresholds. The architecture reserves the abstraction
+point and documents the design space so a future spike can land an
+impl with minimum protocol disruption.
+ABSTRACTION POINT — `CatalogBackend` trait (future)
+A future trait in ontoref-ops or a new crate ontoref-catalog:
+pub trait CatalogBackend {
+ fn discover_ops(&self, project: &ProjectContext) -> Vec<OperationEntry>;
+ fn discover_validators(&self, project: &ProjectContext) -> Vec<ValidatorEntry>;
+ }
+The default impl `InventoryCatalogBackend` returns only the
+ link-time-registered entries (what the daemon does today). Future
+ impls extend the surface:
+- DynLibCatalogBackend — loads .so/.dylib per project
+ - WasmCatalogBackend — loads WASM modules per project
+ - SidecarCatalogBackend — proxies dispatch to a per-project process
+ - InterpretedNclBackend — executes NCL-declared ops in-process
+ - CompositeCatalogBackend — combines the above
+Each impl pairs with documented spike triggers (see below).
+DESIGN SPACE — five candidate mechanisms
+1. CompiledIntoDaemon (current state)
+ The project's ops are compiled into the daemon binary. The project
+ ships its own daemon build, OR the project's ops live in a workspace
+ crate that the daemon includes as a dep.
+ Pros: full Rust type safety; zero runtime overhead; replay
+ determinism preserved trivially.
+ Cons: does NOT scale beyond 1-2 projects co-developed with the
+ daemon; binary distribution per project; impossible cross-version.
+ Status: status quo for the ontoref piloto; not a general solution.
+2. DynamicLibraryLoading (libloading / dlopen)
+ Project compiles `.so/.dylib`; daemon loads it at startup via
+ `libloading`. The library exposes a C-ABI surface that returns
+ `OperationEntry` records.
+ Pros: native Rust speed; the entries integrate naturally with
+ inventory after a one-time registration call.
+ Cons: ABI brittleness across Rust compiler versions; platform-
+ specific paths; security boundary is thin; symbol versioning is a
+ runtime hazard.
+ Trigger: 2+ projects at tier-2 + they're willing to compile against
+ a pinned Rust toolchain matching the daemon's.
+3. WasmComponents (component model)
+ Project compiles ops to WASM components; daemon loads them via
+ wasmtime/wasmer with the WIT interface.
+ Pros: portable across platforms; security sandbox; project's choice
+ of source language (Rust, AssemblyScript, others); ABI is the
+ component model, not Rust's.
+ Cons: WASM build pipeline overhead; need a WIT surface for
+ OperationEntry + Slice + ValidationCtx + Verdict + OpOutput
+ (significant interface design); slight runtime overhead.
+ Trigger: 2+ projects + diverse host platforms + security
+ sandboxing required (untrusted ops authors).
+ Note: ADR-026 mentions WASM as candidate for tier-0 Structural
+ validators. The same infra could host operations.
+4. SidecarProcess (per-project op process)
+ Each tier-2 project runs its own ops process (binary or
+ interpreter); daemon proxies `dispatch_op` calls via IPC (Unix
+ socket, gRPC, named pipe). The sidecar handles its own catalog.
+ Pros: process isolation; per-project deploy cycle independent of
+ daemon; language-agnostic (sidecar can be Rust, Go, Python, …).
+ Cons: extra process management; IPC latency on hot path; witness
+ signing crosses a process boundary (security review needed);
+ multiple processes per host complicates packaging.
+ Trigger: projects need independent op deploy cadence + IPC overhead
+ is acceptable per dispatch.
+5. InterpretedNclOps (NCL bodies executed in-process)
+ The op body is declared as an NCL expression (or small DSL) in the
+ catalog/operations/<id>.ncl file. The daemon interprets it against
+ `ctx` and `inputs`. No Rust handler required.
+ Pros: zero deploy overhead; ops author entirely in NCL; cross-
+ project ops trivially shareable as content-addressed ontologies.
+ Cons: loses Rust type safety; need an expression evaluator + safe
+ sandbox; performance lower than native; complex ops (signature
+ schemes, blob hashing) hard to express.
+ Trigger: most tier-2 ops in the ecosystem turn out to be small
+ state-mutation verbs that need no native code, and the cost of
+ expressing them in NCL is less than the cost of compiled-per-
+ project handlers.
+ Note: this is the most aligned with "voluntary-adoption, minimal
+ friction" if the expressive power suffices.
+6. SidecarProcessWithSharedDomainLibrary
+ Specialisation of #4 (SidecarProcess) aligned with ADR-018 level
+ hierarchy. The domain (Level 2, e.g. provisioning) ships a Rust
+ LIBRARY crate `<domain>-ops` containing the generic
+ `#[onto_operation]` handlers. Each instance (Level 3, e.g.
+ libre-daoshi / libre-wuji / libre-forge) compiles its OWN sidecar
+ BINARY that depends on the domain library AND adds
+ instance-specific ops. The ontoref main daemon proxies dispatch
+ to each instance's sidecar via HTTP.
+Convergence/divergence split:
+ CONVERGE (single source of truth)
+ - Catalog NCL declarations (shared at domain level via ADR-028)
+ - Rust handler code (one `<domain>-ops` library crate)
+ - catalog/schema.ncl validations (protocol-wide)
+ DIVERGE (per-instance)
+ - Sidecar binaries (each instance compiles its own)
+ - Deploy cadence (independent per instance)
+ - Signing keys per actor per instance
+ - Instance-specific ops (overrides + extensions)
+ - Sidecar process per host per instance
+Maps onto ADR-018 mode resolution semantics:
+ Delegate → instance uses the domain handler unchanged
+ Override → instance provides its own handler for the same op id
+ Compose → instance wraps the domain handler with extra logic
+Pros: ZERO new code in the daemon beyond a ~80-LOC proxy handler;
+ full Rust type safety for handlers (lib path/git dep);
+ per-instance deploy independence; blast radius isolated per
+ instance; instances can climb to tier-2 voluntarily without
+ forcing siblings; signing keys stay per-instance per-actor
+ (no shared cryptographic boundary); replay determinism preserved.
+ Cons: N processes per host where N = number of tier-2 instances;
+ Rust toolchain coupling between domain library and instance
+ sidecars; HTTP IPC overhead per dispatch (~50-500µs measured);
+ cross-instance ops require IPC chain (instance A → main daemon
+ → instance B's sidecar).
+ Trigger: domain with multiple Level-3 instances reaches tier-2
+ AND deploy cadences are independent AND HTTP IPC overhead is
+ acceptable. provisioning (with libre-daoshi, libre-wuji,
+ libre-forge as instances) is the canonical example.
+ Note: this is the RECOMMENDED near-term path for shared-domain
+ tier-2 adoption — implementable today, no WASM spike required.
+7. CompositeBackend (mix of the above)
+ Daemon loads multiple backends in priority order; an op lookup
+ consults each backend until one returns a match. Lets ontoref-piloto
+ keep its CompiledIntoDaemon backend AND add a second backend for
+ external tier-2 projects.
+ Pros: incremental migration; the piloto doesn't have to change to
+ accommodate new projects; the abstraction holds even with one
+ backend.
+ Cons: dispatch lookup is O(N backends); priority semantics need
+ clear documentation.
+CHOSEN MECHANISM — tiered selection by trigger
+The 7 mechanisms split into two phases of the design space:
+Phase A — implementable today, no spike required:
+ 1. CompiledIntoDaemon — current piloto self-host
+ 6. SidecarProcessWithSharedDomainLibrary
+ — RECOMMENDED for shared-domain
+ tier-2 (provisioning, etc.)
+ 7. CompositeBackend (combines 1 + 6)
+Phase B — requires a spike with documented trigger conditions:
+ 2. DynamicLibraryLoading — Rust ABI brittleness
+ 3. WasmComponents — WIT interface design + runtime cost
+ 4. SidecarProcess (raw) — base case of #6 without domain lib
+ 5. InterpretedNclOps — NCL expressive power proof needed
+For Phase A mechanisms, the protocol does NOT defer — implementation
+ may proceed when a project commits to the pattern. Mechanism #1 is
+ already in production (the piloto). Mechanism #6 unblocks any shared
+ domain (provisioning + its Level-3 instances) at the cost of ~80 LOC
+ proxy handler in ontoref-daemon + path/git dep for the domain library.
+For Phase B mechanisms, the protocol defers behind explicit triggers:
+T1. THREE OR MORE projects (besides ontoref) author tier-2 catalogs
+ AND find Phase A mechanisms operationally insufficient.
+ T2. The ontoref piloto's catalog grows beyond ~30 ops to a size
+ where the inventory pattern shows cracks (compile-time startup,
+ debug builds, hot-reload friction).
+ T3. A consumer project requests cross-project ops dispatch (op A in
+ project P invokes op B in project Q) — IPC chain in mechanism #6
+ becomes the bottleneck.
+ T4. Toolchain divergence — an instance wants ops in a non-Rust
+ language; dynlib / WASM is the only way.
+ T5. Untrusted ops authors — the deployment must sandbox ops from
+ outside the instance's control; WASM is the natural fit.
+When ANY trigger fires for Phase B, a session is opened to:
+ 1. Audit which of the Phase B mechanisms (2, 3, 4 raw, 5) fits the
+ empirical constraints (host platforms, security model, deploy
+ cadence, language requirements).
+ 2. Define the WIT / C ABI / NCL DSL surface as appropriate.
+ 3. Implement the chosen backend behind the `CatalogBackend` trait.
+ 4. Document the new backend's spike triggers (mirroring the
+ ADR-026 / D15 spike-triggers pattern).
+This ADR commits the abstraction point and the design space. It does
+ not commit the implementation. The same trigger-based discipline that
+ applies to CommitmentBackend (D15), SyncBackend (D18), CrdtMergeStrategy
+ (D19), and BlobBackend (D23) extends to CatalogBackend (D30).
+DECLARATIVE SIDE — content-addressed catalogs as ontologies
+The declarative side (`catalog/operations/<id>.ncl`) composes
+ trivially via ADR-028's content-addressed ontology layer. A project's
+ catalog NCL files can be published as a Type-2 or Type-3 ontology;
+ consumer projects subscribe to that ontology and gain typed knowledge
+ of "ops project P exposes". This works TODAY at any tier — the
+ catalog declarations are inert at tier-0/1 (declarative documentation
+ only) but compose cross-project. The executive-side gap is what this
+ ADR defers; the declarative-side composition is already operational.
+CROSS-TIER COMPOSITION (preserved by design)
+Tier-0 / tier-1 projects can declare their own `catalog/operations/`
+ as forward-looking documentation without paying the executive cost.
+ Tier-2 projects can reference the declarative ontology of any other
+ project (via OntologyRef in OperationDecl). The discovery of a
+ project's executive handlers is the only gap this ADR names; the
+ declarative half of the catalog already supports the full
+ cross-project composition envisioned by ADR-028.
+
+Constraints
+
+Hard The default `CatalogBackend` impl MUST continue to support the ontoref piloto self-host without changes to the piloto's catalog format. Any future backend that supersedes CompiledIntoDaemon as default MUST be a strict superset of its capabilities for the piloto's 10 ops.Hard The declarative side of `catalog/operations/<id>.ncl` and `catalog/validators/<id>.ncl` MUST remain composable cross-project via ADR-028 content-addressing at ANY tier — independent of which `CatalogBackend` impl is in use.Hard When more than one `CatalogBackend` impl exists, the daemon MUST select impls based on explicit configuration in `.ontoref/config.ncl::ops.catalog_backend` (or equivalent). No automatic selection by environmental detection.
+
+Alternatives considered
+
+Pick CompiledIntoDaemon as the only mechanism, declare cross-project ops out-of-scope permanently — rejected: Forces every tier-2 project to either fork the daemon or upstream their ops into ontoref's workspace. Neither is acceptable for projects with independent release cycles, private domains, or non-Rust catalog authors. Violates the spirit of voluntary-adoption — climbing to tier-2 should be a per-project act, not gated on the protocol's deploy cadence.Pick WASM components now and commit to it — rejected: WASM is plausible but the design surface (WIT for OperationEntry + Slice + ValidationCtx + Verdict + OpOutput) is substantial, the runtime overhead unknown, and there is no second tier-2 project signalling demand. Premature commitment without empirical evidence repeats the patterns the trigger-based discipline (ADR-026/D15, ADR-027/D18, ADR-029/D23) was designed to prevent.Pick DynamicLibraryLoading now — rejected: Rust ABI is unstable across compiler versions, making dynlib coupling brittle. Without a pinned Rust toolchain shared between the daemon and the project's ops crate, the ABI breaks silently. This is a real-world maintenance burden that surfaces only after the second project ships. Defer until at least one project commits to the toolchain discipline.Defer the abstraction point itself — don't add `CatalogBackend` trait until impl is ready — rejected: Without naming the abstraction, future sessions re-rediscover the problem and may pick conflicting solutions. Naming the trait + listing the 5 candidates with trade-offs is cheap (one ADR) and load-bearing for the next session that engages with cross-project tier-2. The design exploration is the deliverable; the impl is the follow-up.Push everyone toward InterpretedNclOps and declare native handlers a legacy path — rejected: NCL/expression languages have not been proven expressive enough to handle the full range of ops (Ed25519 signing, blake3 hashing, oplog append, render pipelines). The piloto's 10 ops use native Rust for non-trivial logic. Committing to NCL-only would either restrict ops to the trivial subset OR force a complex NCL expression engine with its own safety story. Defer until evidence shows the trivial subset covers most real ops.
+
+Related ADRs
+
+ADR-001 · ADR-024 · ADR-025 · ADR-026 · ADR-027 · ADR-028 · ADR-029
diff --git a/site/site/content/adr/en/proposed/adr-030.ncl b/site/site/content/adr/en/proposed/adr-030.ncl
new file mode 100644
index 0000000..b965b37
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-030.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-030",
+ title = "Catalog Discovery Cross-Project — Tier-2 Ops Beyond the Piloto Self-Host",
+ slug = "030",
+ excerpt = "",
+ tags = ["adr", "proposed"],
+ page_route = "/adr/030",
+ graph = {
+ implements = [],
+ related_to = ["adr-001", "adr-024", "adr-025", "adr-026", "adr-027", "adr-028", "adr-029"],
+ },
+}
diff --git a/site/site/content/adr/en/proposed/adr-034.md b/site/site/content/adr/en/proposed/adr-034.md
new file mode 100644
index 0000000..8ae0470
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-034.md
@@ -0,0 +1,221 @@
+---
+id: "adr-034"
+title: "Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds"
+slug: "034"
+subtitle: "Proposed"
+excerpt: "ADR-024 (operations as the agent's only project-touching path) and"
+author: "ontoref"
+date: "2026-05-26"
+published: true
+featured: false
+category: "proposed"
+tags: ["adr-034", "adr", "proposed"]
+css_class: "category-adr"
+---
+Context
+
+ADR-024 (operations as the agent's only project-touching path) and
+ADR-026 (three-planes validation + SLA) commit the protocol to a typed
+catalog of domain operations, each declared in
+`.ontoref/catalog/operations/<id>.ncl` and paired with a Rust handler
+annotated `#[onto_operation]`. The Rust pairing is enforced by
+`inventory::collect!(OperationEntry)` at link time — there is no
+protocol surface today for a catalog declaration without a matching
+Rust function.
+ADR-030 (catalog discovery cross-project) named the broader problem of
+how non-piloto projects expose tier-2 ops and enumerated seven candidate
+mechanisms:
+1. CompiledIntoDaemon (current — Rust + inventory at link time)
+ 2. DynamicLibraryLoading (.so/.dylib via libloading)
+ 3. WasmComponents (WIT-typed WASM modules via wasmtime)
+ 4. SidecarProcess (HTTP/IPC to per-project process)
+ 5. InterpretedNclOps (declarative NCL data-transform)
+ 6. SidecarProcessWithSharedDomainLibrary (refinement of #4)
+ 7. CompositeBackend (mix of the above)
+ADR-030 split these into Phase A (implementable today: #1, #6, #7) and
+Phase B (spike-required behind explicit triggers: #2, #3, #4-raw, #5)
+and committed the `CatalogBackend` trait surface without committing
+any non-default impl.
+WHAT THIS ADR DOES vs DOES NOT DO
+ADR-034 does NOT re-open ADR-030's deferral. The trigger-based discipline
+for choosing WHICH executive backend lands first remains intact: WASM is
+deferred behind T3/T4/T5, dynlib behind toolchain-coupling pressure,
+NCL-interpreted behind expressive-power proof.
+ADR-034 commits a different layer: the catalog's DECLARATIVE schema.
+Today an `OperationDecl` in `catalog/schema.ncl` implicitly assumes the
+op is implemented in Rust (no `kind` field, the runtime looks up the
+inventory entry by id). For tier-2 to scale across the ecosystem — even
+under Phase A mechanisms only — the catalog declaration must be honest
+about what mechanism a particular op uses. The piloto's `move_fsm_state`
+is Rust; a future provisioning op in libre-daoshi's sidecar is
+Sidecar-mechanism; a future trivial CRUD op in a tier-2 mirador clone
+might be NclTransform. Each kind has different witness emission rules,
+different ondaod-discipline requirements, and different cross-project
+composability properties.
+ADR-034 adds a `kind` field to `OperationDecl` with the canonical
+enumeration: `'Rust` (default), `'NclTransform`, `'Wasm`, `'Sidecar`.
+The schema admits all four kinds. The Rust runtime implements `'Rust`
+today (inventory dispatch); the other three kinds are declarable but
+unexecutable until an executive backend lands per ADR-030's trigger
+discipline. The schema decoupling is the deliverable; the executive
+landings remain trigger-deferred.
+WHY DECLARE BEFORE EXECUTE
+Declaring the four kinds in the schema does three things that
+executive-only would not:
+1. Consumer projects can author non-Rust ops as documentation today
+ (declarative side of catalog composes cross-project via ADR-028
+ content-addressing per ADR-030's confirmation). A tier-0 project
+ can declare its future tier-2 catalog including non-Rust kinds
+ without paying the executive cost.
+ 2. Cross-project ontology consumers can read the declared `kind` and
+ understand what dispatch path the foreign project's op takes —
+ useful for trust analysis (a Sidecar op carries a process boundary;
+ a Wasm op carries sandbox guarantees).
+ 3. ondaod-discipline can be enforced structurally at the catalog
+ level: non-Rust kinds cannot inline-execute the ondaod-evaluation
+ op (NCL is declarative, WASM is sandboxed from the host runtime,
+ Sidecar crosses a process boundary). The schema requires
+ `evaluate_ondaod` in `constraints.pre` for non-Rust kinds, making
+ the host runtime perform the discipline check before dispatch.
+WHAT VALIDATORS DO
+Validators are pure (no state mutation). The kind field applies
+symmetrically to `ValidatorDecl` — `predicate_ref` becomes
+mechanism-dependent. Because validators don't mutate state, the
+ondaod-pre requirement does not apply to them; only operations are
+gated. The schema admits the same four kinds for validators with the
+same trigger-deferred executive impls.
+
+Decision
+
+SCHEMA FIELD — `kind` discriminator on `OperationDecl` and `ValidatorDecl`
+The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` gains a
+new field `kind` typed as:
+let t_OpKind = [|
+ 'Rust, # #[onto_operation] + inventory (DEFAULT)
+ 'NclTransform, # declarative NCL data-transform spec
+ 'Wasm, # WIT-typed WASM module path
+ 'Sidecar, # HTTP endpoint on a per-project process
+ |] in
+The field is optional with `default = 'Rust`, so every existing
+operation declaration continues to typecheck unchanged. Adding a new
+op requires no `kind` declaration unless the author chooses a non-Rust
+mechanism.
+PER-KIND BODY FIELD — `body | optional` (open record)
+Each non-Rust kind requires an additional declaration of WHERE the
+mechanism's runtime locates the implementation. The `body` field is an
+optional open record whose shape is per-kind:
+'Rust → body absent (impl resolved by id via inventory)
+'NclTransform → body = {
+ target | String, # NCL file path relative to project root
+ selector | String, # JSONPath into the exported value
+ operation | [| 'Set, 'Append, 'Remove, 'Merge |],
+ value_expr | String, # NCL expression evaluated in op context
+ }
+'Wasm → body = {
+ module_path | String, # path under .ontoref/catalog/wasm/<id>.wasm
+ export_name | String, # name of the WIT-exported function
+ capabilities | Array String, # explicit capability grants (file:read, net:none, ...)
+ }
+'Sidecar → body = {
+ endpoint | String, # HTTP URL (host-local default)
+ auth_token | String | default = "<from .ontoref/config.ncl::ops.sidecar_token>",
+ timeout_ms | Number | default = 5000,
+ }
+The `body` field is validated structurally by the schema (Nickel
+contracts enforce per-kind shape); executive backends interpret it at
+dispatch time. The protocol does NOT validate `body` semantically until
+an executive backend exists — declaration without execution is honest
+documentation per ADR-030.
+EXECUTIVE IMPL DEFERRAL — unchanged from ADR-030
+The `'Rust` kind is the only kind the daemon dispatches today. The
+other three kinds remain trigger-deferred per ADR-030:
+'NclTransform → trigger: most tier-2 ops in the ecosystem turn out
+ to be small state-mutation verbs that need no native
+ code, AND the expressive power of the declarative
+ spec is proven sufficient for ≥3 real ops.
+ 'Wasm → trigger: ADR-030 T3 / T4 / T5 — cross-instance
+ dispatch becomes the bottleneck, toolchain divergence,
+ or untrusted ops authors require sandboxing.
+ 'Sidecar → already Phase A per ADR-030 mechanism #6
+ (SidecarProcessWithSharedDomainLibrary). The ~80-LOC
+ proxy handler is implementable today; provisioning +
+ Level-3 instances is the canonical first consumer.
+This ADR commits the schema discriminator. It does NOT commit the
+non-`'Rust` executive impls beyond what ADR-030 already commits.
+Sidecar may land near-term (Phase A); Wasm and NclTransform stay
+deferred.
+ONDAOD-PRE REQUIREMENT FOR NON-Rust KINDS
+ADR-024 places `evaluate_ondaod` as a structural ondaod-discipline
+check that runs before architectural-mutation operations. The Rust
+runtime can invoke this directly from inside an op body (the op has
+access to the dispatch context). Non-Rust kinds cannot:
+- `'NclTransform` ops are declarative — no body to run ondaod inside
+ - `'Wasm` ops are sandboxed — the host's ondaod context is not
+ automatically reachable; passing it across the boundary requires
+ interface design
+ - `'Sidecar` ops cross a process boundary — invoking ondaod from
+ the sidecar would require the sidecar to call back into the daemon
+The protocol resolves this by requiring `evaluate_ondaod` (or a
+declared specialised ondaod validator) to appear in the
+`constraints.pre` array of every non-`'Rust` `OperationDecl`. The
+host runtime invokes the pre-validator before dispatching to the
+non-Rust kind. This makes ondaod-discipline a structural property
+of the catalog declaration, not a behavioural property of the op body —
+which is exactly what the declarative side of the catalog is for.
+The constraint is enforced as a Hard schema-level check at typecheck
+time:
+let t_OperationDecl = ... in
+ let t_OndaodPreRequired = std.contract.from_predicate
+ (fun decl =>
+ decl.kind == 'Rust
+ || std.array.any (fun v => v == "evaluate_ondaod") decl.constraints.pre)
+ in
+Rust-kind ops MAY still declare `evaluate_ondaod` in their
+`constraints.pre` (and ondaod-touching ops SHOULD); the constraint only
+*requires* it for non-Rust kinds. The asymmetry matches the asymmetry
+of executive access: Rust ops can synthesise ondaod context internally,
+non-Rust ops cannot.
+VALIDATOR EXTENSIBILITY — same field, no ondaod-pre requirement
+`ValidatorDecl` in `.ontoref/catalog/schema.ncl` also gains the `kind`
+field with the same enumeration. The ondaod-pre requirement does NOT
+apply because validators are pure (no state mutation). Otherwise the
+treatment is symmetric: `'Rust` is default and dispatches today;
+`'NclTransform` / `'Wasm` / `'Sidecar` are declarable but unexecutable
+until the corresponding executive backend lands.
+WITNESS SHAPE HONESTY
+ADR-024 requires every operation to emit a witness covering the act.
+The witness shape declared in `OperationDecl.witness_shape` must reflect
+what the executive backend can attest:
+'Rust → witness covers the post-state of the host's in-process
+ application; the signature is over the canonical
+ serialisation of the state delta (current behaviour).
+ 'NclTransform → witness covers the same shape — the host runtime
+ applies the transform in-process and signs the
+ delta. No additional attestation needed.
+ 'Wasm → witness covers the host's view of inputs + outputs of
+ the WASM call; the signature does NOT attest WASM-
+ internal computation (the sandbox is honest about being
+ a black box).
+ 'Sidecar → witness covers the sidecar's response payload as
+ received by the daemon; the signature is the daemon's
+ signature, not the sidecar's. The protocol does NOT
+ claim the sidecar's computation is honest — only that
+ the daemon faithfully forwarded the sidecar's reported
+ effect.
+The witness-shape distinction is documented in the schema's comments;
+it is NOT enforced as a contract because the protocol cannot statically
+verify which witness mode the executive backend will use. It IS
+documented in the constraint rationale below so that future executive
+backend implementations honour the declared boundaries.
+
+Constraints
+
+Hard The `OperationDecl` contract in `.ontoref/catalog/schema.ncl` MUST declare a `kind` field typed as an enum admitting at minimum `'Rust`, `'NclTransform`, `'Wasm`, `'Sidecar`. The field MUST default to `'Rust` so existing declarations remain valid without modification. The `ValidatorDecl` contract MUST declare the same field with the same default.Hard Every `OperationDecl` with `kind != 'Rust` MUST include `"evaluate_ondaod"` (or a documented specialised ondaod validator id) in its `constraints.pre` array. The schema MUST enforce this with a contract that fails typecheck when the requirement is unmet.Hard The `'Rust` kind MUST remain the default and MUST remain executable by the daemon under every set of feature flags including `--no-default-features`. Adding new kinds MUST NOT regress the Rust-kind dispatch path.Hard When a project declares an `OperationDecl` with `kind = 'Wasm`, `'NclTransform`, or `'Sidecar` and the daemon was built without the corresponding executive feature flag (`wasm`, `ncl-transform`, `sidecar` respectively when those features exist), the daemon's `/ops/{id}` endpoint MUST return a 501 Not Implemented response with a structured error explaining the missing feature. Silent fall-through to 404 is forbidden.Hard For each non-Rust `kind`, the schema MUST validate the `body` field's required keys at typecheck time. `kind = 'NclTransform` requires `body.target`, `body.selector`, `body.operation`, `body.value_expr`. `kind = 'Wasm` requires `body.module_path`, `body.export_name`, `body.capabilities`. `kind = 'Sidecar` requires `body.endpoint` (other fields default).Hard Adding the `'Wasm` kind to the schema MUST NOT introduce a `wasmtime` (or `wasmer`) dependency in the default `ontoref-daemon` build. Any WASM runtime dependency MUST be feature-gated behind an opt-in flag (e.g. `--features wasm`).Hard The daemon MUST NOT infer an op's `kind` from artefact presence (e.g. existence of a `.wasm` file, a sidecar URL in config, an NCL body in the declaration). The `kind` field is the authoritative source. When the field is absent, the kind is `'Rust` by default — never any other value.Soft For non-Rust kinds (`'Wasm`, `'Sidecar`), the witness emitted on dispatch MUST attest only the host-observable inputs and outputs of the foreign execution. The witness MUST NOT claim to attest the foreign computation's internal correctness, and the `payload_kind` MUST encode this honestly (e.g. `wasm_call_envelope`, `sidecar_response_envelope`).
+
+Alternatives considered
+
+Add only `'Rust` and `'Sidecar` to the kind enum; defer `'Wasm` and `'NclTransform` declarations entirely — rejected: Forces a schema migration when `'Wasm` or `'NclTransform` later become declarable. The cost of declaring all four kinds today is one Nickel enum line per kind; the cost of migrating the schema later (with all consumer projects' content needing re-checking) is higher. ADR-030 already enumerated all the mechanisms; adding them to the schema follows the same enumeration.Declare `kind` but make `body` field free-form `Dyn` for all non-Rust kinds — rejected: Loses the typecheck protection that the per-kind shape gives. Authors of `kind = 'Sidecar` ops would get no help from the schema confirming they declared `endpoint`, `auth_token`, `timeout_ms`. The open-record-per-kind approach offers minimal typecheck for the canonical fields while leaving room for executive-backend-specific extensions.Require ondaod-pre for ALL ops (Rust and non-Rust) — rejected: Over-constrains Rust ops. Rust-kind ops have inline access to dispatch::call("evaluate_ondaod", …) and currently invoke it from inside the body when needed (operationally — see crates/ontoref-ops/src/ops/transition_adr.rs). Forcing every Rust op to also declare it in constraints.pre would duplicate enforcement and create false-positive failures for ops that don't touch architectural state. The asymmetry matches the executive-access asymmetry: structural where structural is needed.Add Nushell as a fifth kind (`'NuScript`) — rejected: Nushell is the existing automation surface (reflection/modules/) and is already declarable via `'Sidecar` with the sidecar being a Nushell-based HTTP server. Adding `'NuScript` as a separate kind would create overlap with `'Sidecar` and require duplicate documentation. If empirical pressure later shows that Nushell ops are common enough to justify a dedicated mechanism, a future ADR can add the kind — the four-kind enum is the minimum coherent set today.Defer the schema discriminator entirely — wait until an executive backend lands and add the field then — rejected: Anti-pattern from ADR-030's perspective: schema decoupling is cheap; executive backend landings are expensive and require triggers. Adding the schema field now means the FIRST executive backend doesn't have to bump the schema — it implements its kind, and consumer projects that declared it speculatively start working without re-publication. The schema field also unblocks ADR-030 #6 (Sidecar Phase A) by giving it a place to declare without retro-fitting later.Use a tag union without per-kind body field — embed mechanism-specific fields directly in OperationDecl — rejected: Pollutes OperationDecl with N×M fields (each kind's fields × every declaration). The per-kind body is the canonical encoding pattern (matches `t_ConstraintCheck` in adr-schema.ncl — tag + per-tag required fields). Authors only see fields relevant to their chosen kind.
+
+Related ADRs
+
+ADR-024 · ADR-026 · ADR-028 · ADR-029 · ADR-030 · ADR-032 · ADR-033
diff --git a/site/site/content/adr/en/proposed/adr-034.ncl b/site/site/content/adr/en/proposed/adr-034.ncl
new file mode 100644
index 0000000..30f0a2d
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-034.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-034",
+ title = "Catalog Extensibility Beyond Rust — `kind` Discriminator on `OperationDecl` with ondaod-Pre Required for Non-Rust Kinds",
+ slug = "034",
+ excerpt = "",
+ tags = ["adr", "proposed"],
+ page_route = "/adr/034",
+ graph = {
+ implements = [],
+ related_to = ["adr-024", "adr-026", "adr-028", "adr-029", "adr-030", "adr-032", "adr-033"],
+ },
+}
diff --git a/site/site/content/adr/en/proposed/adr-058.md b/site/site/content/adr/en/proposed/adr-058.md
new file mode 100644
index 0000000..88f038f
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-058.md
@@ -0,0 +1,82 @@
+---
+id: "adr-058"
+title: "Difusión Demos Are Demonstration-Scenarios Projected From the Modes, Not a New Kind or Hand-Authored Prose"
+slug: "058"
+subtitle: "Proposed"
+excerpt: "A difusión proposal asked for concrete 'con y sin ontoref' demos — each facing a"
+author: "ontoref"
+date: "2026-06-12"
+published: true
+featured: false
+category: "proposed"
+tags: ["adr-058", "adr", "proposed"]
+css_class: "category-adr"
+---
+Context
+
+A difusión proposal asked for concrete "con y sin ontoref" demos — each facing a
+real problem and showing a way to manage it — published across four surfaces:
+mdBook (docs/), code examples, presentation examples, and web pages with
+asciinema. Two naive shapes were on the table and both are wrong for this
+project.
+The first naive shape is a NEW typed kind (a `CaseStudy` projecting a `Proof`):
+extra schema, a migration, and a second seam duplicating what the positioning
+Proof already binds. The second naive shape is hand-authored prose case studies
+anchored to ids by hand: marketing copy that drifts from the claims it sells and
+cannot be run or verified.
+The interview that produced ADR-057 already established the discipline that
+defeats both: the worldview is transmitted by encounter with a runnable artifact,
+not by argument. And ontoref already ships the machinery. The `generate-examples`
+mode encodes the axiom "the modes ARE the examples" — an example is a runnable,
+verifiable artifact, not a static directory. The `Scenario` schema
+(reflection/schemas/scenario.ncl) already models exactly a demo: `description`
+(the problem — "without ontoref"), `run` (the way to manage it — "with ontoref"),
+`validates` (the proof), under `purpose = 'Demonstration`. The `generate-mdbook`
+mode and the generator (reflection/modules/generator.nu) already extract scenarios
+and compose them into the doc-data. The asciinema pattern is already proven in
+outreach/study-group-ontoref/demo/recording/record.sh.
+The gap was never a missing abstraction; it was that nothing authored the demos
+as scenarios and nothing projected them to the difusión surfaces.
+
+Decision
+
+A difusión demo IS a `Scenario` with `purpose = 'Demonstration`, authored once
+under .ontoref/reflection/scenarios/<demo>/ and PROJECTED to every surface by the
+generator. No new typed kind, no per-surface hand-authored copy. Concretely:
+Demo = Demonstration-scenario — the problem the visitor faces is the scenario's
+ `description` (the "sin ontoref" frame), the way to manage it is `run` (the
+ "con ontoref" verifiable command), and what it proves is `validates`. This
+ reuses the Proof seam of positioning as an honesty anchor (each demo maps to a
+ Validated Proof) without duplicating it as a new kind.
+Project, don't duplicate — one Scenario source feeds five outputs. mdBook is
+ rendered by `docs generate --fmt mdbook` (a Demos chapter: problem → management
+ → proof). The other four surfaces — the example repo README, the Slidev
+ section, the asciinema web page, and the Rustelo site page — are materialised by
+ a dedicated `docs demos project --surface <s>` command. A demo edited in one
+ place regenerates everywhere; no copy maintained per surface.
+Runnable, not staged — the demo is executable and its `.cast` is recorded from
+ REAL execution against the `onref` branch of the example repo (the
+ generate-examples mode runs the scenario and records the cast). A demo is not a
+ staged screenshot; the example's `onref` side carries a minimal real `.ontoref/`
+ so the query actually answers.
+Honest hook, not a recipe — the demo hooks by the PROBLEM (`description`) and
+ the question the "without" world cannot answer, never by a deterministic A→B
+ recipe. The cast shows the real query with its limit (sufficient, not complete);
+ the repulsion of the recipe-seeker is the qualifier (reach-vs-qualification),
+ not funnel leakage.
+
+Constraints
+
+Hard A difusión demo MUST be authored as a Scenario (reflection/scenarios/<demo>/scenario.ncl) with purpose 'Demonstration — its description the problem, its run the management, its validates the proof — NOT as a new typed kind or standalone prose.Hard The difusión surfaces (mdBook, example README, slides, web, site) MUST be projected by the generator from the single Scenario source, never hand-maintained per surface. A demo edit MUST regenerate every surface, not be copied.Soft A demo's run MUST be executable and its .cast recorded from real execution against the onref branch (whose minimal .ontoref/ makes describe answer). A demo MUST NOT be a staged screenshot or a non-reproducible capture.Soft Demo copy MUST hook by the problem (the question the 'without' world cannot answer) and MUST NOT promise a deterministic recipe. The cast MUST show the real query with its limit (sufficient, not complete).
+
+Alternatives considered
+
+A new typed `CaseStudy` kind that projects a positioning `Proof` — rejected: Scenario with purpose 'Demonstration already models problema/gestión/prueba; a CaseStudy kind adds schema, a migration, and a second seam duplicating the Proof it would reference. The Proof is reused as an honesty anchor instead, with no new kind.Hand-authored prose case studies anchored to audience/value-prop ids by hand — rejected: Free prose drifts from the claims it sells, is not runnable, and is not verifiable — reintroducing the static, unverifiable examples that generate-examples exists to abolish ('the modes ARE the examples').A static before/after code diff (same project, with and without the .ontoref/ files) — rejected: A static diff sells a deterministic recipe (reach-vs-qualification) and shows ontoref's cost while hiding its benefit, which only appears at the moment of consequence. The demo's climax must be the runnable query the 'without' world cannot answer, recorded and named as sufficient-not-complete.
+
+Anti-patterns
+
+Selling ontoref With a Static Before/After Code Diff — A demo is built as a static with/without code diff of the same project. It promises a deterministic recipe (reach-vs-qualification) and shows ontoref's cost — the extra .ncl files, the friction — while hiding its benefit, which only appears at the moment of consequence. It reads as ceremony.Inventing a Typed Kind for What Scenario Already Models — A contributor adds a CaseStudy (or similar) typed kind to model difusión demos, adding schema and a migration, when Scenario with purpose 'Demonstration already carries description/run/validates. The new kind duplicates the positioning Proof it would reference.Hand-Authoring the Same Demo Once Per Surface — The same demo is written separately for mdBook, the example README, the slides, the web page and the site. The five copies drift from each other and from the scenario — the per-surface drift projection exists to abolish.
+
+Related ADRs
+
+ADR-057 · ADR-035 · ADR-043 · ADR-029 · ADR-056
diff --git a/site/site/content/adr/en/proposed/adr-058.ncl b/site/site/content/adr/en/proposed/adr-058.ncl
new file mode 100644
index 0000000..f4638b9
--- /dev/null
+++ b/site/site/content/adr/en/proposed/adr-058.ncl
@@ -0,0 +1,12 @@
+{
+ id = "adr-058",
+ title = "Difusión Demos Are Demonstration-Scenarios Projected From the Modes, Not a New Kind or Hand-Authored Prose",
+ slug = "058",
+ excerpt = "",
+ tags = ["adr", "proposed"],
+ page_route = "/adr/058",
+ graph = {
+ implements = [],
+ related_to = ["adr-057", "adr-035", "adr-043", "adr-029", "adr-056"],
+ },
+}
diff --git a/site/site/content/blog/en/_index.ncl b/site/site/content/blog/en/_index.ncl
new file mode 100644
index 0000000..eb4a7ab
--- /dev/null
+++ b/site/site/content/blog/en/_index.ncl
@@ -0,0 +1,9 @@
+# AUTO-GENERATED — do not edit manually
+# Scope: blog/en
+# Updated: 2026-06-24T02:32:42Z
+# Regen: nu scripts/content/generate-ncl-index.nu
+
+std.array.flatten [
+ import "./engineering/_index.ncl",
+ import "./ontoref/_index.ncl",
+]
diff --git a/site/site/content/blog/en/engineering/_index.ncl b/site/site/content/blog/en/engineering/_index.ncl
new file mode 100644
index 0000000..ee0a11a
--- /dev/null
+++ b/site/site/content/blog/en/engineering/_index.ncl
@@ -0,0 +1,8 @@
+# AUTO-GENERATED — do not edit manually
+# Scope: blog/en/engineering
+# Updated: 2026-06-24T02:32:42Z
+# Regen: nu scripts/content/generate-ncl-index.nu
+
+[
+ import "./release-engineering-from-one-model.ncl",
+]
diff --git a/site/site/content/blog/en/engineering/release-engineering-from-one-model.md b/site/site/content/blog/en/engineering/release-engineering-from-one-model.md
new file mode 100644
index 0000000..4f157bd
--- /dev/null
+++ b/site/site/content/blog/en/engineering/release-engineering-from-one-model.md
@@ -0,0 +1,177 @@
+---
+# Post metadata
+id: "release-engineering-from-one-model"
+title: "Release engineering from one model"
+slug: "release-engineering-from-one-model"
+subtitle: "Pre-commit, CI, justfiles, multi-arch OCI images and a curl|sh installer — generated from one typed model, not hand-maintained"
+excerpt: "Release engineering is the most-copied, least-governed surface in any multi-repo org: CI YAML pasted between services until no two pipelines agree. ontoref models the entire release surface as one typed NCL workflow layer and generates the real artifacts from it — so the pipeline is queryable like any other architectural fact, and drift fails before it ships. ADR-038 added multi-arch OCI distribution as a catalog entry plus a generator, not a fork."
+
+# Publication info
+author: "Jesús Pérez"
+date: "2026-06-21"
+published: true
+featured: true
+
+# Categorization
+category: "engineering"
+tags: ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"]
+
+# Image (card thumbnail + in-post featured)
+thumbnail: "/images/ontoref-release-one-model-post.webp"
+image_url: "/images/ontoref-release-one-model-post.webp"
+
+# Display
+read_time: "8 min read"
+sort_order: 1
+css_class: "category-engineering"
+category_description: "Engineering"
+category_published: true
+---
+
+
+
+
+
+
+
+# Release engineering from one model
+
+Release engineering is the most-copied, least-governed surface in any multi-repo
+organisation. CI YAML gets pasted between services and patched per-repo until no
+two pipelines agree. The build-and-ship procedure lives in the head of whoever
+set it up. And nothing ties *how we release* back to the architectural decisions
+that constrain it. When a registry moves, a signing policy changes, or an
+architecture is added, every repo is edited by hand and the drift is invisible —
+until a release breaks.
+
+ontoref takes the opposite stance: the entire release surface is **one typed
+model**, and the real artifacts are generated from it. This post walks through
+what that means concretely, using the decision that pushed it furthest —
+shipping ontoref as a multi-arch OCI image and a `curl | sh` installer
+(ADR-038 ).
+
+## One workflow layer, four triggers
+
+The release surface lives in a single NCL workflow model (`ontology/workflow.ncl`)
+with four trigger-scoped layers:
+
+- `OnCommit` — pre-commit hooks
+- `OnPR` — checks per pull request
+- `OnMainMerge` — integration on merge to main
+- `OnTag` — the release layer
+
+Inside those layers, work is typed. Build kinds carry `'Bundle` and `'Container`;
+distribution kinds carry `'ContainerRegistry`, `'Artifact`, `'Package`. None of
+these are strings in a YAML file — they are constructors in a typed model that a
+generator reads.
+
+The generators turn the model into the artifacts you actually run:
+
+```
+generate-precommit → .pre-commit-config.yaml
+generate-woodpecker → .woodpecker/*.yml
+generate-justfile → justfiles/*.just
+generate-distribution → .woodpecker/release.yml
+render-installer → install/install.sh
+generate-release-justfile → justfiles/release.just
+```
+
+The same NCL declares CI *and* the build *and* distribution. There is one source
+of truth, and every emitted file is regenerable from it.
+
+## Why this beats programmable CI
+
+The obvious objection: programmable CI already exists — Dagger, Earthly, monorepo
+task runners. They make pipelines into code. But code is not a *queryable model
+governed by architectural decisions*. With those tools the pipeline is a program;
+with ontoref the pipeline is a fact you can interrogate through the same
+`describe` / HTTP / MCP surface as the ontology and the ADRs.
+
+Concretely, that buys two things hand-maintained YAML and programmable CI both
+lack:
+
+- **Drift detection.** `onre workflow diff` reports divergence between the model
+ and the generated artifacts *before* it ships. A hand-edited `release.yml` is
+ caught, not discovered in production.
+- **ADR anchoring.** The release surface is tied to the decisions that constrain
+ it. The distribution catalog isn't free-floating config; it's evidence for a
+ value proposition and guarded by typed constraints in an ADR.
+
+## ADR-038: adding OCI distribution without a fork
+
+Here's the part that proves the pattern. Until recently ontoref could only be
+installed from a source checkout. Two install paths were wanted, sharing **one
+artifact origin** in the OCI registry:
+
+1. A runnable multi-arch image — `docker run …/ontoref/ontoref-daemon:VERSION`
+2. `curl … | sh` — an installer that detects OS/arch, pulls the matching bundle
+ (binary + data layer + wrappers), and installs into a prefix.
+
+The mechanism was **not** a pile of standalone scripts. The workflow layer
+already had the exact seam: `build_kind` already carried `'Container`,
+`distribution_kind` already carried `'ContainerRegistry | 'Artifact | 'Package`,
+and the `distributions` catalog was empty with no generator. The layer had been
+built anticipating exactly this.
+
+So the coherent move was to **populate the catalog and add the missing
+generator** — not to invent a parallel packaging path that `onre describe` could
+never see. A new provider is a catalog entry plus a generator, not a fork. That's
+the whole shape of the change, and `onre workflow list` shows the release layer
+afterward like any other build artifact.
+
+### Build-once: the image and the bundle are the same binary
+
+A typed model lets you state a hard property and enforce it. ADR-038 's is
+*build-once*: the musl binary is cross-compiled once per arch (`cross`), and both
+the install bundle **and** the runnable image consume that same binary. The image
+is assembled with `buildah` from the cross output plus the data layer — **no
+`docker buildx`, no QEMU, no docker-in-docker** on the runner.
+
+This is cheaper and deterministic: a multi-arch index built from prebuilt
+per-arch binaries, not by running an amd64 builder under arm64 emulation. And it
+is a *constraint*, not a convention — the model forbids recompiling per arch to
+build the image, so a future contributor reaching for the familiar
+`docker buildx --platform` path is stopped by the model rather than silently
+shipping a divergent binary.
+
+> **Aside — the cache problem you don't have to solve.** Teams compiling a large
+> Rust workspace inside Docker spend real effort taming `cargo-chef`: every change
+> to internal code busts the dependency-cache layer, and the fixes (stubbing
+> workspace crates, lockfile-hash-tagged base images) are fiddly. ontoref
+> sidesteps the whole category by building the binary *once* with `cross` and
+> assembling the image from it. There is no in-Docker workspace compile to cache,
+> so there is no cache to invalidate.
+
+### The axiom the decision had to protect
+
+Shipping a runnable daemon image risks reading ontoref as a *runtime* dependency
+— and "Protocol, Not Runtime" is an invariant axiom. The model makes the
+resolution explicit instead of hoping for it. The `curl|sh` installer installs a
+CLI + data layer that work **without** the daemon (ADR-029 : the daemon is a cache,
+not a hard dependency); the runnable image is an **optional accelerator** for
+container deployments. A typed constraint —
+`daemon-image-optional-not-runtime` — guards that a future change cannot quietly
+make the daemon required.
+
+This is the rare case where formalisation and adoption move *together*. The fear
+is always that more formalisation raises adoption friction. Here the
+formalisation — an NCL distribution catalog plus a generator — *produced* the
+artifact that most lowers friction: a one-line `curl | sh` install on a bare
+host, no source, no cargo, no nickel (nickel is bundled).
+
+## What you get from modeling it
+
+- ontoref installs on a bare Linux host with `curl … | sh` — no checkout, no
+ cargo, no nickel.
+- The daemon runs as a container from the same registry origin, multi-arch,
+ cosign-signed with an attested SBOM.
+- Distribution is queryable NCL: `onre workflow list` / `onre workflow diff` show
+ the release layer and catch drift; regeneration is deterministic.
+- A publish gate (`smoke-bundle`) extracts the freshly-assembled bundle and runs
+ the binary before anything publishes — a broken artifact fails the pipeline
+ rather than reaching users.
+
+The point isn't that ontoref has a nice CI setup. It's that *how we release* is no
+longer tribal knowledge or copy-pasted YAML. It's a typed fact, anchored to the
+decisions that constrain it, regenerable on demand, and unable to drift without
+the model saying so first.
diff --git a/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl b/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl
new file mode 100644
index 0000000..f14f0c4
--- /dev/null
+++ b/site/site/content/blog/en/engineering/release-engineering-from-one-model.ncl
@@ -0,0 +1,23 @@
+# Generated from release-engineering-from-one-model.md
+# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl
+# Regenerate: nu scripts/content/md-to-ncl.nu --force
+
+let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in
+
+schema.make_post {
+ id = "release-engineering-from-one-model",
+ title = "Release engineering from one model",
+ slug = "release-engineering-from-one-model",
+ subtitle = "Pre-commit, CI, justfiles, multi-arch OCI images and a curl|sh installer — generated from one typed model, not hand-maintained",
+ excerpt = "Release engineering is the most-copied, least-governed surface in any multi-repo org: CI YAML pasted between services until no two pipelines agree. ontoref models the entire release surface as one typed NCL workflow layer and generates the real artifacts from it — so the pipeline is queryable like any other architectural fact, and drift fails before it ships. ADR-038 added multi-arch OCI distribution as a catalog entry plus a generator, not a fork.",
+ date = "2026-06-21",
+ featured = true,
+ category = "engineering",
+ tags = ["release-engineering", "ci", "oci", "nickel", "supply-chain", "rust"],
+ read_time = "8 min read",
+ sort_order = 1,
+ graph = {
+ implements = ["oci-distribution", "adr-038", "adr-029", "adr-017", "protocol-not-runtime", "formalization-vs-adoption"],
+ related_to = [],
+ },
+}
\ No newline at end of file
diff --git a/site/site/content/blog/en/ontoref/_index.ncl b/site/site/content/blog/en/ontoref/_index.ncl
new file mode 100644
index 0000000..2affe2c
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/_index.ncl
@@ -0,0 +1,14 @@
+# AUTO-GENERATED — do not edit manually
+# Scope: blog/en/ontoref
+# Updated: 2026-06-24T02:32:42Z
+# Regen: nu scripts/content/generate-ncl-index.nu
+
+[
+ import "./ai-knowledge-tool-who-keeps-it-alive.ncl",
+ import "./dags-everywhere-none-know-what-they-are.ncl",
+ import "./one-protocol-multiple-subjects.ncl",
+ import "./the-seven-sins-of-ai-agents.ncl",
+ import "./trust-is-an-output-not-an-input.ncl",
+ import "./your-ontology-should-live-with-your-code.ncl",
+ import "./context-declared-not-filled.ncl",
+]
diff --git a/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md
new file mode 100644
index 0000000..9bc302b
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.md
@@ -0,0 +1,107 @@
+---
+# Post metadata
+id: "ai-knowledge-tool-who-keeps-it-alive"
+title: "AI is a Knowledge Tool. But Who Keeps the Knowledge Alive?"
+slug: "ai-knowledge-tool-who-keeps-it-alive"
+subtitle: "Talisman is right — and stops exactly where the hard problem begins"
+excerpt: "Jessica Talisman's KGC 2026 talk is the clearest articulation of why AI strategies fail: organizations invest in data infrastructure and expect reasoning to emerge. She's right. But her solution — build knowledge infrastructure — stops exactly where the hard problem begins: who keeps the knowledge from drifting?"
+
+# Publication info
+author: "Jesús Pérez"
+date: "2026-05-10"
+published: true
+featured: false
+
+# Categorization
+category: "ontoref"
+tags: ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"]
+
+# Image (card thumbnail + in-post featured)
+thumbnail: "/images/ontoref-ai-knowledge-post.webp"
+image_url: "/images/ontoref-ai-knowledge-post.webp"
+
+# Display
+read_time: "7 min read"
+sort_order: 1
+css_class: "category-ontoref"
+category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects"
+category_published: true
+---
+
+
+
+
+
+
+
+# AI is a Knowledge Tool. But Who Keeps the Knowledge Alive?
+
+Jessica Talisman delivered a talk at KGC 2026 called "Stop Betting, Start Building." The data she opens with is brutal:
+
+- **89%** of firms report zero productivity impact from AI after three years (NBER, Feb 2026, n=5,937 executives)
+- Experienced developers are **19% slower** with AI tools, not faster (METR RCT, 2025)
+- The average AI-using worker gains **−14 minutes** per week in net productivity (Foxit/Sapio, March 2026)
+
+The market is bullish. The evidence is not.
+
+Her diagnosis is correct: *AI is a knowledge tool, not a data tool.* Organizations are pouring money into data lakes, vector stores, and ETL pipelines and expecting context and reasoning to emerge from raw tokens. It won't. Models were trained on linked data, RDF triples, and controlled vocabularies — the top fifteen C4 training sources are knowledge-graph-heavy. They're then deployed against environments stripped of all that structure, and we wonder why they hallucinate.
+
+Her prescription is also correct: build knowledge infrastructure. Controlled vocabularies first. Taxonomies. Thesauri. Ontologies — formal commitments, classes, properties, constraints. Knowledge graphs on top. And govern them. Forever, not as a project.
+
+She's right. And she stops exactly where the hard problem begins.
+
+## The Governance Gap
+
+The sixth step in her stack is "Govern — this is infrastructure, stewardship, versioning, growth, forever." She names the problem. But naming it is not a mechanism.
+
+The real question is: *who ensures the ontology doesn't drift from the system it describes?*
+
+Software evolves. An architectural decision made in March invalidates a node in the graph by October. A new team joins with different mental models. A library gets replaced and a constraint becomes fictional. Knowledge graphs accumulate debt the same way codebases do — silently, without an alarm.
+
+In the enterprise knowledge graph world, the answer to governance is headcount: hire ontologists, librarians, knowledge engineers. That works at organizational scale with dedicated budgets. It is not a viable answer for a software project, an infrastructure environment, or an individual trying to maintain structured self-knowledge.
+
+## What's Actually Missing: the Operational Loop
+
+Every serious ontology without an operational closure layer becomes archaeology within eighteen months. Beautiful, formally correct, and describing a system that no longer exists.
+
+The missing piece is not more knowledge. It's a feedback loop that:
+
+1. **Observes** the current state of the system against its declared intent
+2. **Detects** drift before it becomes permanent
+3. **Executes** operations that reduce that drift
+4. **Records** decisions with lasting architectural weight
+5. **Propagates** changes to everything that depends on this knowledge
+
+This is the Yang to the ontology's Yin. Talisman describes the Yin in precise detail. The Yang is what makes it not an artifact.
+
+## Ontoref: Both Halves
+
+Ontoref is a protocol for structured self-knowledge in software projects. It operates as two coexisting layers that cannot function without each other:
+
+**Ontology (what IS):** typed nodes and edges representing practices, principles, tensions, capabilities. Not documentation — formal commitments. A `Practice` node that `implements` a `Principle`, `enforces` a `Constraint`, `enables` a `Capability`, and is in active `tension` with another `Practice`. The project as a knowledge graph.
+
+**Reflection (what BECOMES):** executable DAGs — operational modes that run against the ontological state, detect drift, report transitions, and propagate changes. The `sync diff --docs` command that catches when a crate's documentation has drifted from its ontology node. The FSM in `state.ncl` that tracks where each project dimension is versus where it intends to go. The migrations system that ensures protocol changes reach every consumer project.
+
+The tension between these two layers is not a design flaw — it's named explicitly in the project's own ontology as its core identity:
+
+> *"Ontology captures what IS. Reflection captures what BECOMES. Both must coexist without one dominating."*
+
+Without Reflection, the Ontology crystallizes. It becomes a snapshot of what the project wanted to be at the moment it was written. Without Ontology, Reflection is execution without truth — it knows how to operate but not what it's operating on.
+
+## The Accuracy Numbers
+
+Talisman's data on what structured knowledge actually does to AI accuracy is worth repeating:
+
+- Question-answering on enterprise SQL: **16% → 72%** when an ontology checks and repairs LLM-generated queries (Allemang & Sequeda, data.world AI Lab, 2024)
+- GraphRAG vs. vector RAG: **3.4× accuracy** across 43 enterprise queries (Diffbot KG-LM Benchmark, 2023)
+- Vector RAG collapses to **0% accuracy past five entities per query**. KG-grounded retrieval sustains performance well beyond that
+
+The accuracy gap is not closed by a bigger model. It is closed by a defined schema, an ontology, and a validated query.
+
+But only if the ontology is kept alive. Only if someone — or something — is running the operational loop that keeps it from drifting into fiction.
+
+That's the part Talisman's framework doesn't provide. That's what Reflection is for.
+
+---
+
+*Ontoref is open source. The protocol specification, Nushell automation, and Rust crates are at [github.com/jesusperezlorenzo/ontoref](https://github.com/jesusperezlorenzo/ontoref).*
diff --git a/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl
new file mode 100644
index 0000000..e52ed67
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/ai-knowledge-tool-who-keeps-it-alive.ncl
@@ -0,0 +1,24 @@
+# Generated from ai-knowledge-tool-who-keeps-it-alive.md
+# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl
+# Regenerate: nu scripts/content/md-to-ncl.nu --force
+
+let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in
+
+schema.make_post {
+ id = "ai-knowledge-tool-who-keeps-it-alive",
+ title = "AI is a Knowledge Tool. But Who Keeps the Knowledge Alive?",
+ slug = "ai-knowledge-tool-who-keeps-it-alive",
+ subtitle = "Talisman is right — and stops exactly where the hard problem begins",
+ excerpt = "Jessica Talisman's KGC 2026 talk is the clearest articulation of why AI strategies fail: organizations invest in data infrastructure and expect reasoning to emerge. She's right. But her solution — build knowledge infrastructure — stops exactly where the hard problem begins: who keeps the knowledge from drifting?",
+ date = "2026-05-10",
+ category = "ontoref",
+ tags = ["ontoref", "knowledge-graphs", "ontology", "ai-infrastructure", "reflection"],
+ read_time = "7 min read",
+ sort_order = 1,
+ image_url = "/images/ontoref-ai-knowledge-post.webp",
+ thumbnail = "/images/ontoref-ai-knowledge-post.webp",
+ graph = {
+ implements = ["drift-observation", "internal-coherence-enforced", "enforcement-vs-emergence", "witness-as-axis-seam", "sufficient-verification"],
+ related_to = ["one-protocol-multiple-subjects", "dags-everywhere-none-know-what-they-are", "your-ontology-should-live-with-your-code"],
+ },
+}
\ No newline at end of file
diff --git a/site/site/content/blog/en/ontoref/context-declared-not-filled.md b/site/site/content/blog/en/ontoref/context-declared-not-filled.md
new file mode 100644
index 0000000..bf4e85c
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/context-declared-not-filled.md
@@ -0,0 +1,133 @@
+---
+# Post metadata
+id: "context-declared-not-filled"
+title: "Declared, Not Filled: Context You Can Audit"
+slug: "context-declared-not-filled"
+subtitle: "The AI window fills a context and forgets why it held those tokens. Library science has declared context as a typed, warranted property since 1911 — and ontoref extends the same move from the catalogued term to the governed act."
+excerpt: "Jessica Talisman's essay splits the year's favourite buzzword in two: context as a container you fill, or context as a property you declare. ANSI/NISO Z39.19 has held the property view for a century — qualifiers, scope notes, warrant. ontoref reached the same record shape without ever citing the standard, then pushed it one step further: from declaring context on terms to declaring it on acts, where a signed witness answers 'on whose authority does this result exist?' with an exit code instead of fluency."
+
+# Publication info
+author: "Jesús Pérez"
+date: "2026-07-09"
+published: true
+featured: true
+
+# Categorization
+category: "ontoref"
+tags: ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"]
+
+# Image (card thumbnail + in-post featured)
+thumbnail: "/images/ontoref-context-declared-not-filled.webp"
+---
+
+
+
+
+
+
+
+There is an essay making the rounds — Jessica Talisman's [*Context Is a Property, Not an
+Object*](https://jessicatalisman.substack.com/p/context-is-a-property-not-an-object)
+(Intentional Arrangement, July 2026) — that cuts the year's favourite buzzword in half.
+The AI industry, she observes, talks about context as a **container**: a window to fill,
+a token budget to spend, a string assembled at the last moment and discarded when
+inference ends. Library and information science, codified in ANSI/NISO Z39.19, holds the
+opposite view: context is a **property** — declared at design time, attached to concepts
+and relationships, tested, displayed, maintained. Qualifiers that fix which *Mercury* you
+mean. Scope notes that travel with the term. Warrant that records on whose authority a
+category exists at all.
+
+We read that essay and recognised our own design bet — stated from a tradition a century
+older than the problem ontoref was built to solve. Two verbs carry the whole difference:
+a window is **filled**; a property is **declared**. And we declare precisely to make the
+context visible and explicit — so nothing is completed by invention. The declared can be
+verified; the filled cannot. Which turns the year's buzzword into an old, sharper question:
+on whose authority do we believe a result — a signature and an exit code, or fluency?
+
+## The question the window never asks
+
+Talisman's sharpest instrument is **warrant**, named by E. Wyndham Hulme in 1911: a term
+enters a vocabulary because evidence put it there — literature, community, operation —
+never because a designer preferred it. Her diagnosis of the language model follows in one
+line: it generates taxonomies whose authority is *borrowed from fluency*. A window filled
+at inference time carries whatever retrieval returned, and the reason those tokens existed
+dies with them. No record, no authority, no audit trail.
+
+ontoref is built on the same refusal, enforced. A Live value-prop MUST cite Accepted ADRs
+(`evidence_adrs`, audited for drift). A template earns promotion to the protocol only on
+deposited `emergence.log` evidence — use warrant, operationally. And since the
+governed-delivery executor (ADR-066 ), a gate step's status derives from the declared
+check's real exit code: a self-report that contradicts the check is refused. Authority
+borrowed from fluency is precisely what the witness seam exists to reject.
+
+## Convergence, field for field
+
+The uncomfortable, satisfying detail: ontoref never cited Z39.19. The glossary term schema
+was designed from the protocol's own needs — and it converged on the standard almost field
+for field.
+
+| Z39.19 / library science | ontoref |
+|---|---|
+| Qualifier disambiguates the homograph | `Term.aliases` + bilingual `definition` (`glossary.ncl`) |
+| Scope note: coverage, usage, assignment rules | `definition` + `examples` + `forbidden` |
+| Warrant — evidence admits the term | `origin = { kind, ref }` on terms; `evidence_adrs` on value-props; `emergence.log` gating promotion |
+| Associative relations, declared not derived | `related_terms` / `related_nodes`, typed edges in `core.ncl` |
+| Display the term in its full web of relations | `describe`, the browsable graph, `view mount` |
+| Provenance (PROV-O: generated-by, attributed-to) | the Ed25519, content-addressed witness (ADR-050 ) |
+| Live authority lookup at the moment of cataloguing | the daemon + MCP surface at the moment of the act |
+
+Two disciplines — one cataloguing meaning, one governing agent work — arriving at the same
+record shape is not imitation. It is the strongest kind of validation prior art can give:
+independent rediscovery.
+
+## From the term to the act
+
+Here is where the twist stops being someone else's essay and becomes ontoref 's own move.
+Library science declares context on *terms*. ontoref extends the identical move to *work*.
+A **Statement of Work** is one set of terms — declared before execution and owned outside
+the worker — plus its **Work Orders**, plus the signed witness each one deposits on
+completion. That witness is what Talisman calls **operational warrant**: the audit trail
+that answers "on whose authority does this result exist?" with a signature and an exit code
+instead of with fluency. Where the object approach spends its tokens and the reason they
+existed evaporates, the governed act leaves a record.
+
+Her production evidence points the same way. The remedy that worked for LLM cataloguing was
+not a bigger window — it was a server that made the governed structure *traversable at the
+moment of the act*. ontoref 's daemon and MCP tools are that remedy, generalised from the
+catalogue to the architecture.
+
+## The window becomes a projection
+
+"The window still exists in the property world, but it becomes a projection of a described
+state rather than a construction site." That sentence is the reveal architecture (ADR-057 )
+verbatim: what you see is generated from a declared source, and a drift is fixed by
+re-running the projection, never by hand-editing the served copy. What the agent — or the
+visitor — sees is never assembled improvisation. It is a declared state, projected. The
+window did not disappear; it stopped being where the truth is decided.
+
+## What the lineage buys — and what it does not
+
+It buys a name and a floor. The category ontoref occupies now has a title older than the
+industry that needs it — *context as property* — and a century of bibliographic prior art
+underneath: Z39.19, warrant, authority control, SKOS. That floor qualifies rather than
+widens: it will not seduce the recipe-seeker, and it will recognise the
+knowledge-organization reader who has lived the property view all along.
+
+It does not certify us finished, and honesty is part of the record too. Warrant is typed on
+glossary terms and value-props but still prose inside the ontology's node descriptions.
+Z39.19's reciprocity rule — every relation A→B must carry B→A, "for all types of
+relationships" — is stricter than our current validators. And emergence remains claim-only,
+a spectrum asserted but not yet measured. Prior art is a mirror, not a diploma — and each of
+these gaps now has a date on it.
+
+---
+
+If you would rather traverse the claim than read it argued: the
+[**graph**](/graph) is the declared state, browsable — every node carries its own warrant,
+every edge its kind. Or step through the door that fits the work you do: the
+[**developer door**](/blog?tag=developer) for governing an agent that cannot promise to
+follow your rules, the [**authoring door**](/blog?tag=knowledge-works) for keeping a body of
+knowledge coherent across everyone who touches it. A sister essay,
+[*The Amnesiac Scholar*](/blog?tag=knowledge-works), records the same thesis arriving from
+the opposite shore — machine learning's own admission that the agent needs a memory it can
+carry, governed and legible, between sessions.
diff --git a/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl b/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl
new file mode 100644
index 0000000..39550a1
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/context-declared-not-filled.ncl
@@ -0,0 +1,25 @@
+# Generated from context-declared-not-filled.md
+# Schema: ../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl
+# Regenerate: nu scripts/content/md-to-ncl.nu --force
+
+let schema = import "../../../../../../../../../../jesusperezlorenzo/.local/share/rustelo/nickel/content/metadata/post_metadata.ncl" in
+
+schema.make_post {
+ id = "context-declared-not-filled",
+ title = "Declared, Not Filled: Context You Can Audit",
+ slug = "context-declared-not-filled",
+ subtitle = "The AI window fills a context and forgets why it held those tokens. Library science has declared context as a typed, warranted property since 1911 — and ontoref extends the same move from the catalogued term to the governed act.",
+ excerpt = "Jessica Talisman's essay splits the year's favourite buzzword in two: context as a container you fill, or context as a property you declare. ANSI/NISO Z39.19 has held the property view for a century — qualifiers, scope notes, warrant. ontoref reached the same record shape without ever citing the standard, then pushed it one step further: from declaring context on terms to declaring it on acts, where a signed witness answers 'on whose authority does this result exist?' with an exit code instead of fluency.",
+ date = "2026-07-09",
+ featured = true,
+ category = "ontoref",
+ tags = ["ontoref", "context-engineering", "library-science", "warrant", "knowledge-graphs", "developer", "knowledge-works"],
+ read_time = "6 min read",
+ sort_order = 7,
+ image_url = "/images/ontoref-context-declared-not-filled.webp",
+ thumbnail = "/images/ontoref-context-declared-not-filled.webp",
+ graph = {
+ implements = ["witness-as-axis-seam", "adr-050", "adr-057", "adr-063", "adr-066"],
+ related_to = ["trust-is-an-output-not-an-input", "ai-knowledge-tool-who-keeps-it-alive"],
+ },
+}
diff --git a/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md
new file mode 100644
index 0000000..3536401
--- /dev/null
+++ b/site/site/content/blog/en/ontoref/dags-everywhere-none-know-what-they-are.md
@@ -0,0 +1,116 @@
+---
+# Post metadata
+id: "dags-everywhere-none-know-what-they-are"
+title: "DAGs Are Everywhere. None of Them Know What They Are."
+slug: "dags-everywhere-none-know-what-they-are"
+subtitle: "Every build system, CI pipeline, and runbook uses DAGs for execution. Ontoref uses them for knowledge."
+excerpt: "CI/CD pipelines, compilers, runbooks, data orchestrators — they all use directed acyclic graphs. Every single one of them uses DAGs as execution models: this before that, topological ordering, dependency resolution. None of them use DAGs to represent what the system is, why it exists, or what trade-offs define it. That's the gap ontoref fills."
+
+# Publication info
+author: "Jesús Pérez"
+date: "2026-05-10"
+published: true
+featured: false
+
+# Categorization
+category: "ontoref"
+tags: ["ontoref", "dag", "ontology", "knowledge-graphs", "software-architecture"]
+
+# Image (card thumbnail + in-post featured)
+thumbnail: "/images/ontoref-dags-everywhere_post.webp"
+image_url: "/images/ontoref-dags-everywhere_post.webp"
+
+# Display
+read_time: "6 min read"
+sort_order: 2
+css_class: "category-ontoref"
+category_description: "Ontoref — protocol and tooling for structured self-knowledge in software projects"
+category_published: true
+---
+
+
+
+
+ ontoref