nx_compare_growth_lib.nx source
↩ module page · 304 lines · 12948 B
1// Additive .plan capability-growth projection. Authored stages never become evidence verdicts.
2// Input is borrowed for model lifetime. No store writes, execution, publication or stage advancement.
3// growthpath|1|id|rung|entry|target|research_refs|learned_model|questions
4// growthexercise|1|id|path|deps|contract|tool_ref|workload_ref|resource_ref|acceptance_ref|failure_next
5// growthassessment|1|id|path|scope|stage|evidence_refs|unix|assessor_ref|supersedes
6// "-" denotes no list or optional link. References remain unresolved until their authoritative owners verify them.
7import "nx_measurement_binding_lib.nx"
8const CG_ROW_WORDS: i64=3
9const CG_MODEL_WORDS: i64=10
10const CG_PATH: i64=1
11const CG_EXERCISE: i64=2
12const CG_ASSESSMENT: i64=3
13const CG_E_INPUT: i64=0-1
14const CG_E_CAPACITY: i64=0-2
15const CG_E_SHAPE: i64=0-3
16const CG_E_ID: i64=0-4
17const CG_E_DUPLICATE: i64=0-5
18const CG_E_LINK: i64=0-6
19const CG_E_CYCLE: i64=0-7
20const CG_E_TIME: i64=0-8
21struct CgModel {
22 plan: *u8,
23 n: i64,
24 rows: *i64,
25 count: i64,
26 capacity: i64,
27 error: i64,
28 error_row: i64,
29 paths: i64,
30 exercises: i64,
31 assessments: i64
32}
33func cg_kind(b: *u8,p: i64,e: i64) -> i64 {
34 if bf_line_starts(b,p,e,"growthpath|" as *u8)==1 { return CG_PATH }
35 if bf_line_starts(b,p,e,"growthexercise|" as *u8)==1 { return CG_EXERCISE }
36 if bf_line_starts(b,p,e,"growthassessment|" as *u8)==1 { return CG_ASSESSMENT }
37 return 0
38}
39func cg_error(m: *CgModel,code: i64,row: i64) -> i64 {
40 if m.error==0 { m.error=code; m.error_row=row }
41 return code
42}
43func cg_field(m: *CgModel,row: i64,col: i64,off: *i64) -> i64 {
44 return bf_field(m.plan,m.rows[row*CG_ROW_WORDS+1],m.rows[row*CG_ROW_WORDS+2],col,off)
45}
46func cg_is(m: *CgModel,row: i64,col: i64,s: *u8) -> i64 {
47 var off: i64=0; let n: i64=cg_field(m,row,col,&off)
48 return pb_equal(m.plan,off,n,s,0,bf_slen(s))
49}
50func cg_same(m: *CgModel,a: i64,ac: i64,b: i64,bc: i64) -> i64 {
51 var ao: i64=0; var bo: i64=0
52 let an: i64=cg_field(m,a,ac,&ao); let bn: i64=cg_field(m,b,bc,&bo)
53 return pb_equal(m.plan,ao,an,m.plan,bo,bn)
54}
55func cg_ident(b: *u8,o: i64,n: i64) -> i64 {
56 if n<=0 { return 0 }
57 var i: i64=0
58 while i<n {
59 let c: i64=b[o+i] as i64
60 var ok: i64=0
61 if c>=65 { if c<=90 { ok=1 } }
62 if c>=97 { if c<=122 { ok=1 } }
63 if c>=48 { if c<=57 { ok=1 } }
64 if c==45 { ok=1 }; if c==95 { ok=1 }; if c==46 { ok=1 }
65 if ok==0 { return 0 }
66 i=i+1
67 }
68 if n==1 { if b[o]==(45 as u8) { return 0 } }
69 return 1
70}
71func cg_idfield(m: *CgModel,row: i64,col: i64) -> i64 {
72 var o: i64=0; let n: i64=cg_field(m,row,col,&o)
73 return cg_ident(m.plan,o,n)
74}
75func cg_decimal(m: *CgModel,row: i64,col: i64) -> i64 {
76 var o: i64=0; let n: i64=cg_field(m,row,col,&o)
77 if n<=0 { return CG_E_TIME }
78 var i: i64=0; var v: i64=0
79 while i<n {
80 let d: i64=(m.plan[o+i] as i64)-48
81 if d<0 { return CG_E_TIME }; if d>9 { return CG_E_TIME }
82 if v>(PB_I64_MAX-d)/10 { return CG_E_TIME }
83 v=v*10+d; i=i+1
84 }
85 if v<=0 { return CG_E_TIME }
86 return v
87}
88func cg_find(m: *CgModel,kind: i64,o: i64,n: i64) -> i64 {
89 var i: i64=0
90 while i<m.count {
91 if m.rows[i*CG_ROW_WORDS]==kind {
92 var io: i64=0; let ni: i64=cg_field(m,i,2,&io)
93 if pb_equal(m.plan,io,ni,m.plan,o,n)==1 { return i }
94 }
95 i=i+1
96 }
97 return 0-1
98}
99func cg_link(m: *CgModel,row: i64,col: i64,kind: i64) -> i64 {
100 var o: i64=0; let n: i64=cg_field(m,row,col,&o)
101 return cg_find(m,kind,o,n)
102}
103func cg_existing(m: *CgModel,row: i64,col: i64,tag: *u8) -> i64 {
104 var o: i64=0; let n: i64=cg_field(m,row,col,&o)
105 var p: i64=0; var count: i64=0
106 while p<m.n {
107 let raw: i64=bf_line_end(m.plan,m.n,p); let e: i64=pb_trim_cr(m.plan,p,raw)
108 if bf_line_starts(m.plan,p,e,tag)==1 {
109 var eo: i64=0; let en: i64=bf_field(m.plan,p,e,1,&eo)
110 if pb_equal(m.plan,o,n,m.plan,eo,en)==1 { count=count+1 }
111 }
112 p=raw+1
113 }
114 return count
115}
116// A dependency list is ordered authored data. Duplicate, missing and cross-path edges are refused.
117func cg_deps(m: *CgModel,row: i64,target: i64) -> i64 {
118 if cg_is(m,row,4,"-" as *u8)==1 { return 0 }
119 var o: i64=0; let n: i64=cg_field(m,row,4,&o)
120 var p: i64=o; let end: i64=o+n; var count: i64=0
121 while p<=end {
122 var e: i64=p
123 while e<end { if m.plan[e]==(44 as u8) { break }; e=e+1 }
124 if cg_ident(m.plan,p,e-p)==0 { return cg_error(m,CG_E_ID,row) }
125 let dep: i64=cg_find(m,CG_EXERCISE,p,e-p)
126 if dep<0 { return cg_error(m,CG_E_LINK,row) }
127 if dep==row { return cg_error(m,CG_E_CYCLE,row) }
128 if cg_same(m,row,3,dep,3)==0 { return cg_error(m,CG_E_LINK,row) }
129 var earlier: i64=o
130 while earlier<p {
131 var ee: i64=earlier
132 while ee<end { if m.plan[ee]==(44 as u8) { break }; ee=ee+1 }
133 if pb_equal(m.plan,p,e-p,m.plan,earlier,ee-earlier)==1 { return cg_error(m,CG_E_DUPLICATE,row) }
134 earlier=ee+1
135 }
136 if target<0 { count=count+1 } else { if dep==target { count=count+1 } }
137 if e==end { break }; p=e+1
138 }
139 return count
140}
141// Build indexed adjacency once; Kahn visits each vertex and edge once after ID resolution.
142// Four vertex arrays (head, indegree, queue, duplicate mark) share one owned workspace.
143func cg_graph_build(m: *CgModel,head: *i64,degree: *i64,marks: *i64,edges: *i64) -> i64 {
144 var row: i64=0; var used: i64=0
145 while row<m.count {
146 if m.rows[row*CG_ROW_WORDS]==CG_EXERCISE {
147 if cg_is(m,row,4,"-" as *u8)==0 {
148 var o: i64=0; let n: i64=cg_field(m,row,4,&o)
149 var p: i64=o; let end: i64=o+n
150 while p<=end {
151 var e: i64=p
152 while e<end { if m.plan[e]==(44 as u8) { break }; e=e+1 }
153 if cg_ident(m.plan,p,e-p)==0 { return cg_error(m,CG_E_ID,row) }
154 let dep: i64=cg_find(m,CG_EXERCISE,p,e-p)
155 if dep<0 { return cg_error(m,CG_E_LINK,row) }
156 if dep==row { return cg_error(m,CG_E_CYCLE,row) }
157 if cg_same(m,row,3,dep,3)==0 { return cg_error(m,CG_E_LINK,row) }
158 if marks[dep]==row+1 { return cg_error(m,CG_E_DUPLICATE,row) }
159 marks[dep]=row+1
160 edges[used*2]=row; edges[used*2+1]=head[dep]; head[dep]=used
161 degree[row]=degree[row]+1; used=used+1
162 if e==end { break }; p=e+1
163 }
164 }
165 }
166 row=row+1
167 }
168 return used
169}
170func cg_graph(m: *CgModel) -> i64 {
171 var edgecap: i64=0; var row: i64=0
172 while row<m.count {
173 if m.rows[row*CG_ROW_WORDS]==CG_EXERCISE {
174 if cg_is(m,row,4,"-" as *u8)==0 {
175 var o: i64=0; let n: i64=cg_field(m,row,4,&o)
176 edgecap=edgecap+1; var p: i64=o
177 while p<o+n { if m.plan[p]==(44 as u8) { edgecap=edgecap+1 }; p=p+1 }
178 }
179 }
180 row=row+1
181 }
182 // Four vertex arrays plus the edge pairs, each a native 64-bit word.
183 if m.count>=PB_I64_MAX/(4*8)-1 { return cg_error(m,CG_E_CAPACITY,0-1) }
184 let vertex_bytes: i64=(m.count+1)*4*8
185 if edgecap>(PB_I64_MAX-vertex_bytes)/(2*8) { return cg_error(m,CG_E_CAPACITY,0-1) }
186 let bytes: i64=vertex_bytes+edgecap*2*8
187 let workspace: *u8=sys_mmap(bytes)
188 if (workspace as i64)<=0 { return cg_error(m,CG_E_CAPACITY,0-1) }
189 let head: *i64=workspace as *i64
190 let degree: *i64=((workspace as i64)+(m.count+1)*8) as *i64
191 let queue: *i64=((workspace as i64)+(m.count+1)*2*8) as *i64
192 let marks: *i64=((workspace as i64)+(m.count+1)*3*8) as *i64
193 let edges: *i64=((workspace as i64)+vertex_bytes) as *i64
194 row=0; while row<m.count { head[row]=0-1; row=row+1 }
195 let built: i64=cg_graph_build(m,head,degree,marks,edges)
196 if built<0 { sys_munmap(workspace,bytes); return built }
197 var put: i64=0; var take: i64=0
198 row=0
199 while row<m.count {
200 if m.rows[row*CG_ROW_WORDS]==CG_EXERCISE { if degree[row]==0 { queue[put]=row; put=put+1 } }
201 row=row+1
202 }
203 while take<put {
204 let node: i64=queue[take]; take=take+1
205 var edge: i64=head[node]
206 while edge>=0 {
207 let target: i64=edges[edge*2]
208 degree[target]=degree[target]-1
209 if degree[target]==0 { queue[put]=target; put=put+1 }
210 edge=edges[edge*2+1]
211 }
212 }
213 sys_munmap(workspace,bytes)
214 if take!=m.exercises { return cg_error(m,CG_E_CYCLE,0-1) }
215 return 0
216}
217func cg_validate(m: *CgModel) -> i64 {
218 var i: i64=0
219 while i<m.count {
220 let kind: i64=m.rows[i*CG_ROW_WORDS]
221 if cg_is(m,i,1,"1" as *u8)==0 { return cg_error(m,CG_E_SHAPE,i) }
222 if cg_idfield(m,i,2)==0 { return cg_error(m,CG_E_ID,i) }
223 var j: i64=0
224 while j<i {
225 if m.rows[j*CG_ROW_WORDS]==kind { if cg_same(m,i,2,j,2)==1 { return cg_error(m,CG_E_DUPLICATE,i) } }
226 j=j+1
227 }
228 if kind==CG_PATH {
229 if cg_existing(m,i,3,LD_RUNG_TAG)!=1 { return cg_error(m,CG_E_LINK,i) }
230 if cg_existing(m,i,5,LD_TARGET_TAG)!=1 { return cg_error(m,CG_E_LINK,i) }
231 let entry: i64=cg_is(m,i,4,"proactive_research" as *u8)+cg_is(m,i,4,"observed_failure" as *u8)+cg_is(m,i,4,"operator_target" as *u8)
232 if entry!=1 { return cg_error(m,CG_E_SHAPE,i) }
233 } else {
234 if cg_link(m,i,3,CG_PATH)<0 { return cg_error(m,CG_E_LINK,i) }
235 if kind==CG_EXERCISE {
236 if cg_is(m,i,10,"@research" as *u8)+cg_is(m,i,10,"@learning" as *u8)==0 {
237 let next: i64=cg_link(m,i,10,CG_EXERCISE)
238 if next<0 { return cg_error(m,CG_E_LINK,i) }
239 if cg_same(m,i,3,next,3)==0 { return cg_error(m,CG_E_LINK,i) }
240 }
241 } else {
242 let stage: i64=cg_is(m,i,5,"research" as *u8)+cg_is(m,i,5,"learning" as *u8)+cg_is(m,i,5,"identified" as *u8)+cg_is(m,i,5,"practice" as *u8)+cg_is(m,i,5,"mastery" as *u8)+cg_is(m,i,5,"innovation" as *u8)
243 if stage!=1 { return cg_error(m,CG_E_SHAPE,i) }
244 let epoch: i64=cg_decimal(m,i,7)
245 if epoch<0 { return cg_error(m,CG_E_TIME,i) }
246 if cg_is(m,i,9,"-" as *u8)==0 {
247 let prev: i64=cg_link(m,i,9,CG_ASSESSMENT)
248 if prev<0 { return cg_error(m,CG_E_LINK,i) }
249 if cg_same(m,i,3,prev,3)==0 { return cg_error(m,CG_E_LINK,i) }
250 if cg_same(m,i,4,prev,4)==0 { return cg_error(m,CG_E_LINK,i) }
251 let prior: i64=cg_decimal(m,prev,7)
252 if prior<0 { return cg_error(m,CG_E_TIME,i) }
253 if prior>=epoch { return cg_error(m,CG_E_TIME,i) }
254 }
255 }
256 }
257 i=i+1
258 }
259 return cg_graph(m)
260}
261func cg_parse(b: *u8,n: i64) -> *CgModel {
262 let m: *CgModel=sys_mmap(CG_MODEL_WORDS*8) as *CgModel
263 if (m as i64)<=0 { return 0 as *CgModel }
264 m.plan=b; m.n=n; m.error_row=0-1
265 if n<0 { cg_error(m,CG_E_INPUT,0-1); return m }
266 if n==0 { return m }
267 if (b as i64)<=0 { cg_error(m,CG_E_INPUT,0-1); return m }
268 if pb_input_ok(b,n)==0 { cg_error(m,CG_E_INPUT,0-1); return m }
269 var p: i64=0
270 while p<n {
271 let e: i64=bf_line_end(b,n,p)
272 if cg_kind(b,p,e)>0 { m.capacity=m.capacity+1 }
273 p=e+1
274 }
275 if m.capacity==0 { return m }
276 if m.capacity>PB_I64_MAX/(CG_ROW_WORDS*8) { cg_error(m,CG_E_CAPACITY,0-1); return m }
277 m.rows=sys_mmap(m.capacity*CG_ROW_WORDS*8) as *i64
278 if (m.rows as i64)<=0 { cg_error(m,CG_E_CAPACITY,0-1); return m }
279 p=0
280 while p<n {
281 let raw: i64=bf_line_end(b,n,p); let e: i64=pb_trim_cr(b,p,raw); let kind: i64=cg_kind(b,p,e)
282 if kind>0 {
283 var scratch: i64=0
284 let fields: i64=pb_field_count(b,p,e,&scratch)
285 var expected: i64=9
286 if kind==CG_EXERCISE { expected=11 }; if kind==CG_ASSESSMENT { expected=10 }
287 if fields!=expected { cg_error(m,CG_E_SHAPE,m.count); return m }
288 var f: i64=1
289 while f<fields {
290 if bf_field(b,p,e,f,&scratch)<=0 { cg_error(m,CG_E_SHAPE,m.count); return m }; f=f+1
291 }
292 m.rows[m.count*CG_ROW_WORDS]=kind; m.rows[m.count*CG_ROW_WORDS+1]=p; m.rows[m.count*CG_ROW_WORDS+2]=e
293 m.count=m.count+1
294 if kind==CG_PATH { m.paths=m.paths+1 }; if kind==CG_EXERCISE { m.exercises=m.exercises+1 }; if kind==CG_ASSESSMENT { m.assessments=m.assessments+1 }
295 }
296 p=raw+1
297 }
298 cg_validate(m); return m
299}
300func cg_free(m: *CgModel) -> i64 {
301 if (m as i64)<=0 { return 0 }
302 if (m.rows as i64)>0 { sys_munmap(m.rows as *u8,m.capacity*CG_ROW_WORDS*8) }
303 sys_munmap(m as *u8,CG_MODEL_WORDS*8); return 0
304}