code wiki / hub / nx_pipeline_banner.nx

nx_pipeline_banner.nx source

↩ module page · 242 lines · 11331 B

1// nx_pipeline_banner.nx -- HUB primitive; renders per-article pipeline- 2// stage banner emitting "what stage is this artifact at + what's missing 3// for next transition". 4// 5// Per V2.0 PIPELINE BOOTSTRAP PLAN P-4: every wiki article shows its 6// pipeline stage at the top. Operator visiting /wiki/<doc-name> sees 7// immediate signal: how mature is this content? 8// 9// COMPOSES (HUB primitives only): 10// wiki/nx_artifact_store (NxArtifactStore + per-artifact taxonomy + stage) 11// nx_html_escape (safe value emission) 12// 13// COMPOSED BY: 14// wiki/nx_wiki_doc_handler (every /wiki/<doc-name> page) 15// (future) obd/nx_obd_doc_handler 16// (future) sprinkler/nx_sprinkler_doc_handler 17// (future) third-party hosted site doc handlers 18// 19// V2.0 SCOPE: 20// - Lookup doc by URL in NxArtifactStore 21// - Sealed 5-color stage badge 22// - Per-stage criteria-met + criteria-missing list (operator self-audit) 23// - "(not in artifact store)" hint when lookup misses (informational) 24// 25// V3+ SCOPE (TODO): 26// - "Advance this artifact" admin button (operator one-click stage promote) 27// - Per-criterion hint links ("add Quadrant front-matter -> see Diátaxis") 28// - History timeline (when did stage last change?) 29// 30// Status: V2.0. 2026-05-27. 31 32import "nx_syscalls.nx" 33import "nx_html_escape.nx" 34import "wiki/nx_artifact_store.nx" 35 36// ===== Sealed verdict surface (codes 2980-2989) ================================================= 37const NX_PBAN_OK: i64 = 0 38const NX_PBAN_BAD_INPUT: i64 = 2980 39const NX_PBAN_OUTPUT_OVERFLOW: i64 = 2981 40 41// ===== Named constants (M7) ================================================= 42const NX_PBAN_MAX_DOC_PATH: i64 = 512 43 44// Sealed stage color CSS classes (per V1 page format §3.4 chrome). 45const NX_PBAN_CLASS_UNSTRUCTURED: *u8 = "stage-banner stage-unstructured" as *u8 46const NX_PBAN_CLASS_STRUCTURED: *u8 = "stage-banner stage-structured" as *u8 47const NX_PBAN_CLASS_MEANINGFUL: *u8 = "stage-banner stage-meaningful" as *u8 48const NX_PBAN_CLASS_ACTIONABLE: *u8 = "stage-banner stage-actionable" as *u8 49const NX_PBAN_CLASS_REPEATABLE: *u8 = "stage-banner stage-repeatable" as *u8 50const NX_PBAN_CLASS_UNKNOWN: *u8 = "stage-banner stage-unknown" as *u8 51 52// ===== Lookup stage color class for a stage value ================================================= 53 54func nx_pban_class_for_stage(stage: i64) -> *u8 { 55 if stage == NX_ART_STAGE_UNSTRUCTURED { return NX_PBAN_CLASS_UNSTRUCTURED } 56 if stage == NX_ART_STAGE_STRUCTURED { return NX_PBAN_CLASS_STRUCTURED } 57 if stage == NX_ART_STAGE_MEANINGFUL { return NX_PBAN_CLASS_MEANINGFUL } 58 if stage == NX_ART_STAGE_ACTIONABLE { return NX_PBAN_CLASS_ACTIONABLE } 59 if stage == NX_ART_STAGE_REPEATABLE { return NX_PBAN_CLASS_REPEATABLE } 60 return NX_PBAN_CLASS_UNKNOWN 61} 62 63// ===== Output writer (bounded; M5) ================================================= 64 65func nx_pban_put_raw(out: *u8, cap: i64, off: i64, src: *u8, n: i64) -> i64 { 66 if off < 0 { return 0 - NX_PBAN_BAD_INPUT } 67 if off + n > cap { return 0 - NX_PBAN_OUTPUT_OVERFLOW } 68 var i: i64 = 0 69 while i < n { out[off + i] = src[i]; i = i + 1 } 70 return off + n 71} 72 73func nx_pban_put_z(out: *u8, cap: i64, off: i64, s: *u8) -> i64 { 74 var n: i64 = 0 75 while s[n] != (0 as u8) { 76 if n >= cap { return 0 - NX_PBAN_OUTPUT_OVERFLOW } 77 n = n + 1 78 } 79 return nx_pban_put_raw(out, cap, off, s, n) 80} 81 82func nx_pban_put_escaped(out: *u8, cap: i64, off: i64, src: *u8, n: i64) -> i64 { 83 if n < 1 { return off } 84 if cap - off < n * 6 + 1 { return 0 - NX_PBAN_OUTPUT_OVERFLOW } 85 let w: i64 = html_escape((out as i64 + off) as *u8, cap - off, src, n) 86 if w < 0 { return 0 - NX_PBAN_OUTPUT_OVERFLOW } 87 return off + w 88} 89 90// ===== Per-stage criteria list (what's met / what's missing) ================================================= 91// 92// Per V2_0_PIPELINE_BOOTSTRAP_ARC_PLAN §2 transitions: 93// STRUCTURED needs: Quadrant + Topic Type + Status all present 94// MEANINGFUL needs: + Composes count > 0 95// ACTIONABLE needs: + Trust State > WRITTEN_UNTESTED + Competitive State != NONE 96// REPEATABLE needs: + Trust = PERSONALLY_MEASURED + Comp ∈ {MATCHES, BEATS} 97 98func nx_pban_render_criteria(out: *u8, cap: i64, off: i64, 99 store: *NxArtifactStore, rowid: i64) -> i64 { 100 var o: i64 = off 101 102 let q: i64 = store.quadrant[rowid] 103 let tt: i64 = store.topic_type[rowid] 104 let st: i64 = store.status[rowid] 105 let tr: i64 = store.trust_state[rowid] 106 let co: i64 = store.competitive_state[rowid] 107 let nc: i64 = store.composes_count[rowid] 108 109 o = nx_pban_put_z(out, cap, o, "<ul class=\"pipeline-criteria\">" as *u8); if o < 0 { return o } 110 111 // Quadrant 112 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 113 if q != NX_ART_QUAD_NONE { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 114 if q == NX_ART_QUAD_NONE { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 115 if o < 0 { return o } 116 o = nx_pban_put_z(out, cap, o, "Quadrant (Diátaxis)" as *u8); if o < 0 { return o } 117 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 118 119 // Topic Type 120 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 121 if tt != NX_ART_DITA_NONE { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 122 if tt == NX_ART_DITA_NONE { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 123 if o < 0 { return o } 124 o = nx_pban_put_z(out, cap, o, "Topic Type (DITA)" as *u8); if o < 0 { return o } 125 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 126 127 // Status 128 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 129 if st != NX_ART_STATUS_NONE { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 130 if st == NX_ART_STATUS_NONE { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 131 if o < 0 { return o } 132 o = nx_pban_put_z(out, cap, o, "Status" as *u8); if o < 0 { return o } 133 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 134 135 // Composes (for MEANINGFUL) 136 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 137 if nc > 0 { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 138 if nc == 0 { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 139 if o < 0 { return o } 140 o = nx_pban_put_z(out, cap, o, "Composes ([[X]] refs) for MEANINGFUL" as *u8); if o < 0 { return o } 141 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 142 143 // Trust State (for ACTIONABLE) 144 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 145 if tr > NX_ART_TRUST_WRITTEN_UNTESTED { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 146 if tr <= NX_ART_TRUST_WRITTEN_UNTESTED { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 147 if o < 0 { return o } 148 o = nx_pban_put_z(out, cap, o, "Trust State &gt; WRITTEN_UNTESTED for ACTIONABLE" as *u8); if o < 0 { return o } 149 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 150 151 // Competitive State (for ACTIONABLE) 152 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 153 if co != NX_ART_COMP_NONE { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 154 if co == NX_ART_COMP_NONE { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 155 if o < 0 { return o } 156 o = nx_pban_put_z(out, cap, o, "Competitive State for ACTIONABLE" as *u8); if o < 0 { return o } 157 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 158 159 // PERSONALLY_MEASURED + MATCHES|BEATS (for REPEATABLE) 160 o = nx_pban_put_z(out, cap, o, "<li>" as *u8); if o < 0 { return o } 161 var repro_ok: i64 = 0 162 if tr == NX_ART_TRUST_PERSONALLY_MEASURED { 163 if co >= NX_ART_COMP_MATCHES { repro_ok = 1 } 164 } 165 if repro_ok == 1 { o = nx_pban_put_z(out, cap, o, "<b>OK</b> " as *u8) } 166 if repro_ok == 0 { o = nx_pban_put_z(out, cap, o, "<b>MISSING</b> " as *u8) } 167 if o < 0 { return o } 168 o = nx_pban_put_z(out, cap, o, "PERSONALLY_MEASURED + Competitive State &gt;= MATCHES for REPEATABLE" as *u8); if o < 0 { return o } 169 o = nx_pban_put_z(out, cap, o, "</li>" as *u8); if o < 0 { return o } 170 171 o = nx_pban_put_z(out, cap, o, "</ul>\n" as *u8); if o < 0 { return o } 172 return o 173} 174 175// ===== Top-level: render banner for a doc ================================================= 176// 177// Caller supplies the canonical doc PATH (filesystem path, matching 178// what was passed to nx_pipeline_walker). If lookup fails, renders a 179// brief informational note instead of an error. 180// 181// The doc URL (e.g., "/wiki/nishi-search-charter") MAY differ from the 182// artifact path (e.g., "/mnt/c/Users/elder/nishi-silicon/NISHI_SEARCH_CHARTER.md"). 183// V2.0 banner takes the artifact PATH so caller controls the mapping; 184// V3+ adds URL-keyed lookup. 185 186func nx_pipeline_banner_render(out: *u8, cap: i64, off: i64, 187 store: *NxArtifactStore, 188 doc_path: *u8, doc_path_n: i64, 189 out_off: *i64) -> i64 { 190 if (out as i64) == 0 { return 0 - NX_PBAN_BAD_INPUT } 191 if (out_off as i64) == 0 { return 0 - NX_PBAN_BAD_INPUT } 192 if doc_path_n < 0 { return 0 - NX_PBAN_BAD_INPUT } 193 if doc_path_n > NX_PBAN_MAX_DOC_PATH { return 0 - NX_PBAN_BAD_INPUT } 194 195 var o: i64 = off 196 197 // Lookup 198 if (store as i64) == 0 { 199 out_off[0] = o 200 return NX_PBAN_OK // no store; render nothing (silent skip) 201 } 202 if store.valid != 1 { 203 out_off[0] = o 204 return NX_PBAN_OK 205 } 206 207 let rowid: i64 = nx_artifact_store_find_by_path(store, doc_path, doc_path_n) 208 if rowid < 0 { 209 // Not in store -- brief informational note 210 o = nx_pban_put_z(out, cap, o, 211 "<div class=\"stage-banner stage-unknown\"><small>This article is not yet in the substrate pipeline store. <a href=\"/wiki/pipeline\">See pipeline overview</a>.</small></div>\n" as *u8) 212 if o < 0 { return o } 213 out_off[0] = o 214 return NX_PBAN_OK 215 } 216 217 let stage: i64 = nx_artifact_store_stage_at(store, rowid) 218 if stage < 0 { 219 out_off[0] = o 220 return NX_PBAN_OK 221 } 222 223 // Banner container 224 o = nx_pban_put_z(out, cap, o, "<div class=\"" as *u8); if o < 0 { return o } 225 o = nx_pban_put_z(out, cap, o, nx_pban_class_for_stage(stage)); if o < 0 { return o } 226 o = nx_pban_put_z(out, cap, o, "\">\n" as *u8); if o < 0 { return o } 227 228 // Stage label 229 o = nx_pban_put_z(out, cap, o, "<p><b>Pipeline stage:</b> <code>" as *u8); if o < 0 { return o } 230 o = nx_pban_put_z(out, cap, o, nx_art_stage_name(stage)); if o < 0 { return o } 231 o = nx_pban_put_z(out, cap, o, "</code> &nbsp; <small><a href=\"/wiki/pipeline\">see pipeline overview</a></small></p>\n" as *u8); if o < 0 { return o } 232 233 // Criteria checklist 234 o = nx_pban_put_z(out, cap, o, 235 "<p><small><b>5-stage pipeline criteria</b> (per <a href=\"/wiki/v2-0-pipeline-bootstrap-arc-plan\">V2.0 PLAN</a>):</small></p>\n" as *u8); if o < 0 { return o } 236 o = nx_pban_render_criteria(out, cap, o, store, rowid); if o < 0 { return o } 237 238 o = nx_pban_put_z(out, cap, o, "</div>\n" as *u8); if o < 0 { return o } 239 240 out_off[0] = o 241 return NX_PBAN_OK 242}