nx_product_board.nx source
↩ module page · 264 lines · 15573 B
1// nx_product_board.nx -- THE PRODUCT CATALOG + ROADMAP, rendered from the `products-` information plane
2// into the ALREADY-GATED wiki tree (sites/nishifamily/wiki/products.html -> /wiki/products.html).
3//
4// WHY THIS EXISTS: nx_wiki_products_page claimed in its header AND on the rendered page that it was
5// "Sourced from knowledge/registry/product_catalog.tsv", but it contained NO file read at all -- all 10
6// families were hardcoded string literals, so every catalog edit was invisible to the page (debt
7// 1785625149). There was no reader to repoint; the reader never existed. This organ is the reader.
8//
9// A product is an OUTCOME, and products live in FAMILIES (games contains checkers and chess). Families
10// are NOT hardcoded here either: they are the DISTINCT values of the family column in first-seen order,
11// so a new family appears by adding ONE plane row -- the same property that makes nx_research_ocean
12// free to extend. Adding a product must cost a data row, never code (the gen-2 golden-path bar).
13//
14// GATING: this writes INTO /wiki, which is OPAQUE-gated by nx_sites_daemon_v2 (no X-Nishi-Session +
15// text/html -> login bootstrap; else proxy to the session-validating gateway, fail-closed). Product
16// strategy is therefore behind the wall BY PLACEMENT, using the wall that already exists rather than
17// standing up a second one. Research stays public in its own rivers.
18//
19// Reads with sts_load_honest so a stale q:n cannot silently truncate the catalog and present a partial
20// board as complete; declared/loaded/rendered are printed on the page itself.
21//
22// Plane row (8 col): id family product arc status governance evidence rung
23// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
24import "nx_store_seed_lib.nx"
25import "nx_seg_store.nx"
26import "nx_syscalls.nx"
27
28const PB_PLANE: *u8 = "knowledge/store/products-"
29const PB_OUT: *u8 = "sites/nishifamily/wiki/products.html"
30const PB_RCAP: i64 = 262144
31const PB_HCAP: i64 = 524288
32const PB_NL: i64 = 10
33const PB_TAB: i64 = 9
34const PB_MAXCOL: i64 = 16
35const PB_PAIR: i64 = 2
36const PB_SPB: i64 = 256
37const PB_MAXFAM: i64 = 32
38const PB_FAMB: i64 = 512
39const PB_FLAGB: i64 = 64
40const PB_C_FAM: i64 = 1
41const PB_C_PROD: i64 = 2
42const PB_C_ARC: i64 = 3
43const PB_C_STATUS: i64 = 4
44const PB_C_GOV: i64 = 5
45const PB_C_EV: i64 = 6
46const PB_C_RUNG: i64 = 7
47const PB_NCOL: i64 = 8
48const PB_MODE: i64 = 0x1a4
49const PB_ERRFD: i64 = 2
50const PB_EXIT_IO: i64 = 1
51const PB_EXIT_EMPTY: i64 = 3
52const PB_LT: i64 = 60
53const PB_GT: i64 = 62
54const PB_AMP: i64 = 38
55
56func pb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
57func pb_werr(s: *u8) -> i64 { sys_write(PB_ERRFD, s, pb_slen(s)); return 0 }
58func pb_slice_eqs(q: *u8, a: i64, b: i64, s: *u8) -> i64 {
59 let sn: i64 = pb_slen(s)
60 if b - a != sn { return 0 }
61 var i: i64 = 0
62 while i < sn { if q[a+i] != s[i] { return 0 } i = i + 1 }
63 return 1
64}
65func pb_slice_eq(q: *u8, a: i64, b: i64, r: *u8, c: i64, d: i64) -> i64 {
66 if b - a != d - c { return 0 }
67 var i: i64 = 0
68 while a + i < b { if q[a+i] != r[c+i] { return 0 } i = i + 1 }
69 return 1
70}
71func pb_cols(q: *u8, ls: i64, le: i64, sp: *i64) -> i64 {
72 var c: i64 = 0
73 var p: i64 = ls
74 while c < PB_MAXCOL {
75 var e: i64 = p
76 var s: i64 = 1
77 while s == 1 { if e >= le { s = 0 } else { if q[e] == (PB_TAB as u8) { s = 0 } else { e = e + 1 } } }
78 sp[c*PB_PAIR] = p
79 sp[c*PB_PAIR+1] = e
80 c = c + 1
81 if e >= le { return c }
82 p = e + 1
83 }
84 return c
85}
86func pb_cat_esc(d: *u8, o: i64, q: *u8, a: i64, b: i64) -> i64 {
87 var oo: i64 = o
88 var i: i64 = a
89 while i < b {
90 let c: i64 = q[i] as i64
91 if c == PB_LT { oo = ss_cat(d, oo, "<" as *u8) }
92 else { if c == PB_GT { oo = ss_cat(d, oo, ">" as *u8) }
93 else { if c == PB_AMP { oo = ss_cat(d, oo, "&" as *u8) }
94 else { d[oo] = q[i]; oo = oo + 1 } } }
95 i = i + 1
96 }
97 return oo
98}
99
100func main() -> i64 {
101 let buf: *u8 = sts_mm(PB_RCAP)
102 let flags: *i64 = sts_mm(PB_FLAGB) as *i64
103 let n: i64 = sts_load_honest(PB_PLANE, buf, PB_RCAP, flags)
104 if n <= 0 {
105 pb_werr("PRODUCTBOARD-RED plane EMPTY / unseeded: knowledge/store/products-\n" as *u8)
106 sys_exit(PB_EXIT_EMPTY)
107 return PB_EXIT_EMPTY
108 }
109 let h: *u8 = sts_mm(PB_HCAP)
110 let sp: *i64 = sts_mm(PB_SPB) as *i64
111 let sp2: *i64 = sts_mm(PB_SPB) as *i64
112 let fam: *i64 = sts_mm(PB_FAMB) as *i64
113 let now: i64 = sys_now_realtime_sec()
114
115 // pass 1: distinct families, first-seen order -- this is what makes a new family free
116 var nf: i64 = 0
117 var i: i64 = 0
118 while i < n {
119 var le: i64 = i
120 var s: i64 = 1
121 while s == 1 { if le >= n { s = 0 } else { if buf[le] == (PB_NL as u8) { s = 0 } else { le = le + 1 } } }
122 if le > i {
123 let nc: i64 = pb_cols(buf, i, le, sp)
124 if nc > PB_C_FAM {
125 let fa: i64 = sp[PB_C_FAM*PB_PAIR]
126 let fb: i64 = sp[PB_C_FAM*PB_PAIR+1]
127 var seen: i64 = 0
128 var k: i64 = 0
129 while k < nf { if pb_slice_eq(buf, fa, fb, buf, fam[k*PB_PAIR], fam[k*PB_PAIR+1]) == 1 { seen = 1 } k = k + 1 }
130 if seen == 0 { if nf < PB_MAXFAM { fam[nf*PB_PAIR] = fa; fam[nf*PB_PAIR+1] = fb; nf = nf + 1 } }
131 }
132 }
133 i = le + 1
134 }
135
136 var w: i64 = 0
137 w = ss_cat(h, w, "<!doctype html>\n<html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>\n<title>Product catalog and roadmap - Nishi</title>\n<style>\n" as *u8)
138 w = ss_cat(h, w, ":root{--bg:#fff;--sf:#f6f8fb;--br:#dfe4ec;--ink:#232936;--mut:#68748a;--acc:#1a5dc8;--acc2:#2e8555;--hd:#111726;--hov:#f0f4fa;--amb:#a8792e}\n" as *u8)
139 w = ss_cat(h, w, "@media(prefers-color-scheme:dark){:root{--bg:#0d1017;--sf:#131826;--br:#252d40;--ink:#dbe2ec;--mut:#8a94a8;--acc:#5aa7f0;--acc2:#86c793;--hd:#eef2f8;--hov:#141a28;--amb:#d0a24e}}\n" as *u8)
140 w = ss_cat(h, w, "*{box-sizing:border-box}body{font-family:system-ui,'Segoe UI',sans-serif;margin:0;background:var(--bg);color:var(--ink);padding:22px 30px 40px;line-height:1.55}\n" as *u8)
141 w = ss_cat(h, w, "main{max-width:1120px;margin:0 auto}a{color:var(--acc);text-decoration:none}a:hover{text-decoration:underline}\n" as *u8)
142 w = ss_cat(h, w, "h1{font-size:1.55em;color:var(--hd);margin:2px 0 6px;letter-spacing:-.01em}\n" as *u8)
143 w = ss_cat(h, w, "h2{font-size:1.05em;color:var(--hd);margin:28px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--br)}\n" as *u8)
144 w = ss_cat(h, w, "table{border-collapse:collapse;width:100%;margin:8px 0}td,th{padding:6px 10px;border-bottom:1px solid var(--br);text-align:left;vertical-align:top;font-size:.88em}th{color:var(--mut);font-size:.82em}tr:hover td{background:var(--hov)}\n" as *u8)
145 w = ss_cat(h, w, ".muted{color:var(--mut)}.n{text-align:right;font-variant-numeric:tabular-nums}\n" as *u8)
146 w = ss_cat(h, w, ".nar{background:var(--sf);border:1px solid var(--br);border-left:3px solid var(--acc2);border-radius:8px;padding:12px 16px;font-size:.92em;margin:12px 0}\n" as *u8)
147 w = ss_cat(h, w, ".st{font-weight:600;font-size:.8em;white-space:nowrap}.go{color:var(--acc2)}.wip{color:var(--amb)}.plan{color:var(--mut)}\n" as *u8)
148 w = ss_cat(h, w, "code{font-family:ui-monospace,Consolas,monospace;font-size:.85em}\nfooter{margin-top:36px;padding-top:10px;border-top:1px solid var(--br);font-size:.8em;color:var(--mut)}\n" as *u8)
149 w = ss_cat(h, w, "</style></head><body><main>\n<p class='muted'><a href='/wiki/index.html'>nishi wiki</a> / products · public front door: <a href='/product'>/product</a></p>\n<h1>Product catalog and roadmap</h1>\n" as *u8)
150 w = ss_cat(h, w, "<p class='muted'>A product is an <b>outcome</b>, and products live in <b>families</b>. Every product inherits its family's governance contract by construction. Generated by <code>nx_product_board</code> from the <code>products-</code> information plane at epoch=" as *u8)
151 w = ss_catn(h, w, now)
152 w = ss_cat(h, w, ".</p>\n" as *u8)
153 w = ss_cat(h, w, "<div class='nar'><b>This page is data, not prose.</b> Families and products are read from the plane at render time — adding a product costs ONE data row and no code. Its predecessor claimed to be sourced from a catalog file and in fact hardcoded every family as a string literal, so edits to the catalog were invisible here for as long as that was true (debt 1785625149). If a number on this page looks wrong, fix the plane row; the page cannot disagree with it.</div>\n" as *u8)
154 w = ss_cat(h, w, "<div class='nar'><b>Why this is behind the wall.</b> Research is public — see <a href='/wiki/research'>the research ocean</a>. What we are building, in what order, and why is not. This page sits inside the gated <code>/wiki</code> tree deliberately, reusing the access wall that already exists rather than standing up a second one.</div>\n" as *u8)
155
156 var f: i64 = 0
157 var total: i64 = 0
158 var nrung: i64 = 0
159 while f < nf {
160 let fa: i64 = fam[f*PB_PAIR]
161 let fb: i64 = fam[f*PB_PAIR+1]
162 w = ss_cat(h, w, "<h2>" as *u8)
163 w = pb_cat_esc(h, w, buf, fa, fb)
164 w = ss_cat(h, w, "</h2>\n<table><tr><th>Product</th><th>Arc</th><th class='st'>Status</th><th>Governance inherited</th><th>Evidence</th><th>Next rung — exit criterion</th></tr>\n" as *u8)
165 var rows: i64 = 0
166 var j: i64 = 0
167 while j < n {
168 var le2: i64 = j
169 var s2: i64 = 1
170 while s2 == 1 { if le2 >= n { s2 = 0 } else { if buf[le2] == (PB_NL as u8) { s2 = 0 } else { le2 = le2 + 1 } } }
171 if le2 > j {
172 let nc2: i64 = pb_cols(buf, j, le2, sp2)
173 if nc2 >= PB_NCOL {
174 if pb_slice_eq(buf, sp2[PB_C_FAM*PB_PAIR], sp2[PB_C_FAM*PB_PAIR+1], buf, fa, fb) == 1 {
175 let sa: i64 = sp2[PB_C_STATUS*PB_PAIR]
176 let sb: i64 = sp2[PB_C_STATUS*PB_PAIR+1]
177 w = ss_cat(h, w, "<tr><td><b>" as *u8)
178 w = pb_cat_esc(h, w, buf, sp2[PB_C_PROD*PB_PAIR], sp2[PB_C_PROD*PB_PAIR+1])
179 w = ss_cat(h, w, "</b></td><td class='muted'><code>" as *u8)
180 w = pb_cat_esc(h, w, buf, sp2[PB_C_ARC*PB_PAIR], sp2[PB_C_ARC*PB_PAIR+1])
181 w = ss_cat(h, w, "</code></td><td class='st " as *u8)
182 if pb_slice_eqs(buf, sa, sb, "ROADMAP" as *u8) == 1 { w = ss_cat(h, w, "plan" as *u8) } else {
183 if pb_slice_eqs(buf, sa, sb, "DONE" as *u8) == 1 { w = ss_cat(h, w, "go" as *u8) } else {
184 if pb_slice_eqs(buf, sa, sb, "LIVE" as *u8) == 1 { w = ss_cat(h, w, "go" as *u8) } else { w = ss_cat(h, w, "wip" as *u8) }
185 }
186 }
187 w = ss_cat(h, w, "'>" as *u8)
188 w = pb_cat_esc(h, w, buf, sa, sb)
189 w = ss_cat(h, w, "</td><td class='muted'>" as *u8)
190 w = pb_cat_esc(h, w, buf, sp2[PB_C_GOV*PB_PAIR], sp2[PB_C_GOV*PB_PAIR+1])
191 w = ss_cat(h, w, "</td><td class='muted'>" as *u8)
192 w = pb_cat_esc(h, w, buf, sp2[PB_C_EV*PB_PAIR], sp2[PB_C_EV*PB_PAIR+1])
193 w = ss_cat(h, w, "</td><td>" as *u8)
194 // a rung of "-" means NO measured next rung has been declared. Say so plainly rather
195 // than printing a dash that reads like a decision -- an empty roadmap cell is a gap.
196 if pb_slice_eqs(buf, sp2[PB_C_RUNG*PB_PAIR], sp2[PB_C_RUNG*PB_PAIR+1], "-" as *u8) == 1 {
197 w = ss_cat(h, w, "<span class='muted'>none declared</span>" as *u8)
198 } else {
199 w = pb_cat_esc(h, w, buf, sp2[PB_C_RUNG*PB_PAIR], sp2[PB_C_RUNG*PB_PAIR+1])
200 nrung = nrung + 1
201 }
202 w = ss_cat(h, w, "</td></tr>\n" as *u8)
203 rows = rows + 1
204 total = total + 1
205 }
206 }
207 }
208 j = le2 + 1
209 }
210 w = ss_cat(h, w, "</table>\n<p class='muted'>" as *u8)
211 w = ss_catn(h, w, rows)
212 w = ss_cat(h, w, " products in this family.</p>\n" as *u8)
213 f = f + 1
214 }
215
216 w = ss_cat(h, w, "<h2>Coverage of this board</h2>\n<table><tr><th>Measure</th><th class='n'>Count</th><th>Meaning</th></tr>\n" as *u8)
217 w = ss_cat(h, w, "<tr><td>Families</td><td class='n'>" as *u8)
218 w = ss_catn(h, w, nf)
219 w = ss_cat(h, w, "</td><td class='muted'>distinct family values in the plane</td></tr>\n<tr><td>Declared by the plane</td><td class='n'>" as *u8)
220 w = ss_catn(h, w, flags[0])
221 w = ss_cat(h, w, "</td><td class='muted'>the plane's own row count</td></tr>\n<tr><td>Loaded</td><td class='n'>" as *u8)
222 w = ss_catn(h, w, flags[1])
223 w = ss_cat(h, w, "</td><td class='muted'>rows actually read back</td></tr>\n<tr><td>Rendered</td><td class='n'>" as *u8)
224 w = ss_catn(h, w, total)
225 w = ss_cat(h, w, "</td><td class='muted'>rows on this page — must equal Loaded, else a row is malformed</td></tr>\n<tr><td>With a declared next rung</td><td class='n'>" as *u8)
226 w = ss_catn(h, w, nrung)
227 w = ss_cat(h, w, "</td><td class='muted'>the rest have NO falsifiable next step declared — that gap is the roadmap's real backlog</td></tr>\n<tr><td>Reachable beyond the count</td><td class='n'>" as *u8)
228 w = ss_catn(h, w, flags[2])
229 w = ss_cat(h, w, "</td><td class='muted'>records past the declared count; non-zero means reconcile</td></tr>\n</table>\n" as *u8)
230 if flags[1] != total { w = ss_cat(h, w, "<p class='nar'><b>WARNING:</b> loaded and rendered disagree — at least one plane row is malformed and is missing above. This board is INCOMPLETE.</p>\n" as *u8) }
231 if flags[2] > 0 { w = ss_cat(h, w, "<p class='nar'><b>WARNING:</b> records exist past the declared count; this board may be under-reporting.</p>\n" as *u8) }
232
233 w = ss_cat(h, w, "<footer>Generated sovereignly by <code>nx_product_board</code> from <code>knowledge/store/products-</code>. Add a product or a family by adding a plane row — this page needs no edit. Research: <a href='/wiki/research'>the ocean</a> · <a href='/product/research'>product research</a> · <a href='/code/research'>code research</a>.</footer>\n</main></body></html>\n" as *u8)
234
235 let fd: i64 = sys_openat_wr(PB_OUT, PB_MODE)
236 if fd < 0 {
237 pb_werr("PRODUCTBOARD-RED cannot open sites/nishifamily/wiki/products.html for write\n" as *u8)
238 sys_exit(PB_EXIT_IO)
239 return PB_EXIT_IO
240 }
241 sys_write(fd, h, w)
242 sys_close(fd)
243
244 let m: *u8 = sts_mm(512)
245 var o: i64 = ss_cat(m, 0, "PRODUCTBOARD-GREEN families=" as *u8)
246 o = ss_catn(m, o, nf)
247 o = ss_cat(m, o, " rendered=" as *u8)
248 o = ss_catn(m, o, total)
249 o = ss_cat(m, o, " declared=" as *u8)
250 o = ss_catn(m, o, flags[0])
251 o = ss_cat(m, o, " loaded=" as *u8)
252 o = ss_catn(m, o, flags[1])
253 o = ss_cat(m, o, " with_rung=" as *u8)
254 o = ss_catn(m, o, nrung)
255 o = ss_cat(m, o, " beyond=" as *u8)
256 o = ss_catn(m, o, flags[2])
257 o = ss_cat(m, o, " bytes=" as *u8)
258 o = ss_catn(m, o, w)
259 m[o] = PB_NL as u8
260 o = o + 1
261 sys_write(1, m, o)
262 sys_exit(0)
263 return 0
264}