code wiki / (root) / nx_project.nx

nx_project.nx source

↩ module page · 3026 lines · 128039 B

1// nx_project.nx -- NISHI PROJECT: the team + contractor project layer for nishifamily.com/project. 2// Operator 2026-07-09: "get beyond microsoft project and leantime ... work with my team and contractors". 3// COMPOSES the existing substrate rather than re-implementing it: tasks/deps/completions live in the 4// nx_vizsla_plan record plane (tsk:/don: records; plan field == project id). This organ adds the layer 5// the projectmgmt census measured ABSENT: 6// prj: PROJECT meta (pid, owner, title) 7// mem: MEMBERSHIP with roles (owner|team|contractor) + data-driven capacity (cap = minutes, or -) 8// fail-closed ROLE-SCOPED VIEW: owner/team see all tasks; contractor sees ONLY own tasks; a 9// non-member is DENIED (deny by default -- access is granted by a record, never assumed) 10// WORKLOAD rollup: per-member open effort vs recorded capacity (over-flag only when cap is data) 11// Task state derives from the plan records: DONE (don: exists) / BLOCKED (a dep lacks don:) / READY. 12// Records (load <file> <prefix> [segid], additive + CID-idempotent): 13// PROJ <pid> <owner> <title> 14// MEMBER <pid> <person> <role> <cap|-> 15// Commands (pj_cli; built via the _hdl_build wrapper; ARGLESS = selftest gate incl a fork of the 16// REAL blessed nx_vizsla_plan.elf as the independent tsk:/don: writer -- cross-organ compose proof): 17// load <file> <prefix> [segid] | view <prefix> <pid> <person> | workload <prefix> <pid> 18// expect_exit: 0 license_tier: ORIGINAL 19import "nx_syscalls.nx" 20import "nx_canon_cid.nx" 21import "nx_seg_store.nx" 22const K_MAGIC_1970: i64 = 1970 23const K_MAGIC_146097: i64 = 146097 24const K_MAGIC_719468: i64 = 719468 25const K_MAGIC_2048: i64 = 2048 26const K_MAGIC_1024: i64 = 1024 27const K_MAGIC_65536: i64 = 65536 28const K_MAGIC_146096: i64 = 146096 29const K_MAGIC_1460: i64 = 1460 30const K_MAGIC_36524: i64 = 36524 31 32func pj_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 33func pj_p(s: *u8) -> i64 { sys_write(1, s, pj_slen(s)); return 0 } 34 35func pj_eq(a: *u8, b: *u8) -> i64 { 36 var i: i64 = 0 37 var go: i64 = 1 38 while go == 1 { 39 if a[i] != b[i] { return 0 } 40 if a[i] == (0 as u8) { return 1 } 41 i = i + 1 42 } 43 return 0 44} 45 46func pj_cmpgt(a: *u8, b: *u8) -> i64 { 47 var i: i64 = 0 48 var go: i64 = 1 49 while go == 1 { 50 let ca: i64 = a[i] as i64 51 let cb: i64 = b[i] as i64 52 if ca > cb { return 1 } 53 if ca < cb { return 0 } 54 if ca == 0 { return 0 } 55 i = i + 1 56 } 57 return 0 58} 59 60func pj_dup(s: *u8) -> *u8 { 61 let n: i64 = pj_slen(s) 62 let d: *u8 = sys_mmap(n + 2) 63 var i: i64 = 0 64 while i <= n { d[i] = s[i]; i = i + 1 } 65 return d 66} 67 68func pj_atoi(s: *u8) -> i64 { 69 var v: i64 = 0 70 var i: i64 = 0 71 while s[i] != (0 as u8) { 72 let d: i64 = (s[i] as i64) - 48 73 if d >= 0 { if d <= 9 { v = v * 10 + d } } 74 i = i + 1 75 } 76 return v 77} 78 79func pj_digits(s: *u8) -> i64 { 80 if s[0] == (0 as u8) { return 0 } 81 var i: i64 = 0 82 while s[i] != (0 as u8) { 83 let d: i64 = (s[i] as i64) - 48 84 if d < 0 { return 0 } 85 if d > 9 { return 0 } 86 i = i + 1 87 } 88 return 1 89} 90 91func pj_isws(c: i64) -> i64 { 92 if c == 32 { return 1 } 93 if c == 13 { return 1 } 94 if c == 9 { return 1 } 95 return 0 96} 97 98func pj_tok(b: *u8, off: i64, lend: i64, dst: *u8, cap: i64) -> i64 { 99 var p: i64 = off 100 var go: i64 = 1 101 while go == 1 { 102 if p >= lend { go = 0 } else { 103 if pj_isws(b[p] as i64) == 1 { p = p + 1 } else { go = 0 } 104 } 105 } 106 var t: i64 = 0 107 go = 1 108 while go == 1 { 109 if p >= lend { go = 0 } else { 110 if pj_isws(b[p] as i64) == 1 { go = 0 } else { 111 if t < cap - 1 { dst[t] = b[p]; t = t + 1 } 112 p = p + 1 113 } 114 } 115 } 116 dst[t] = 0 as u8 117 return p 118} 119 120func pj_cat(dst: *u8, off: i64, s: *u8) -> i64 { 121 var i: i64 = 0 122 while s[i] != (0 as u8) { dst[off + i] = s[i]; i = i + 1 } 123 return off + i 124} 125 126func pj_catn(dst: *u8, off: i64, v: i64) -> i64 { 127 var o: i64 = off 128 var m: i64 = v 129 if m < 0 { dst[o] = 45 as u8; o = o + 1; m = 0 - m } 130 let t: *u8 = sys_mmap(28) 131 var k: i64 = 0 132 if m == 0 { t[0] = 48 as u8; k = 1 } 133 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 134 var i: i64 = 0 135 while i < k { dst[o + i] = t[k - 1 - i]; i = i + 1 } 136 return o + k 137} 138 139func pj_kmemeq(b: *u8, off: i64, n: i64, s: *u8) -> i64 { 140 if pj_slen(s) != n { return 0 } 141 var i: i64 = 0 142 while i < n { 143 if b[off + i] != s[i] { return 0 } 144 i = i + 1 145 } 146 return 1 147} 148 149func pj_field(rec: *u8, rl: i64, want: *u8, out: *u8, cap: i64) -> i64 { 150 if rl < 8 { return 0 } 151 let nf: i64 = ss_r32(rec, 4) 152 var off: i64 = 8 153 var fi: i64 = 0 154 while fi < nf { 155 if off + 8 > rl { return 0 } 156 let kl: i64 = ss_r32(rec, off) 157 let koff: i64 = off + 4 158 let vl: i64 = ss_r32(rec, koff + kl) 159 let voff: i64 = koff + kl + 4 160 if pj_kmemeq(rec, koff, kl, want) == 1 { 161 var t: i64 = 0 162 while t < vl { 163 if t < cap - 1 { out[t] = rec[voff + t] } 164 t = t + 1 165 } 166 if t > cap - 1 { t = cap - 1 } 167 out[t] = 0 as u8 168 return 1 169 } 170 off = voff + vl 171 fi = fi + 1 172 } 173 return 0 174} 175 176func pj_iskind(b: *u8, koff: i64, kl: i64, pfx: *u8) -> i64 { 177 if kl <= 4 { return 0 } 178 return pj_kmemeq(b, koff, 4, pfx) 179} 180 181func pj_find(list: *i64, n: i64, s: *u8) -> i64 { 182 var i: i64 = 0 183 while i < n { 184 if pj_eq(list[i] as *u8, s) == 1 { return i } 185 i = i + 1 186 } 187 return 0 - 1 188} 189 190// civil date "YYYY-MM-DD" -> days since epoch; -1 on malformed (mirrors the proven planner date math) 191func pj_datedays(s: *u8) -> i64 { 192 let nums: *i64 = sys_mmap(8 * 4) as *i64 193 var nn: i64 = 0 194 var cur: i64 = 0 195 var indig: i64 = 0 196 var i: i64 = 0 197 var go: i64 = 1 198 while go == 1 { 199 let c: i64 = s[i] as i64 200 var isd: i64 = 0 201 if c >= 48 { if c <= 57 { isd = 1 } } 202 if isd == 1 { cur = cur * 10 + (c - 48); indig = 1 } 203 if isd == 0 { 204 if indig == 1 { 205 if nn < 4 { nums[nn] = cur; nn = nn + 1 } 206 cur = 0 207 indig = 0 208 } 209 } 210 if c == 0 { go = 0 } 211 i = i + 1 212 } 213 if nn != 3 { return 0 - 1 } 214 var y: i64 = nums[0] 215 let m: i64 = nums[1] 216 let d: i64 = nums[2] 217 if y < K_MAGIC_1970 { return 0 - 1 } 218 if m < 1 { return 0 - 1 } 219 if m > 12 { return 0 - 1 } 220 if d < 1 { return 0 - 1 } 221 if d > 31 { return 0 - 1 } 222 if m <= 2 { y = y - 1 } 223 let era: i64 = y / 400 224 let yoe: i64 = y - era * 400 225 var mp: i64 = m - 3 226 if m <= 2 { mp = m + 9 } 227 let doy: i64 = (153 * mp + 2) / 5 + d - 1 228 let doe: i64 = yoe * 365 + yoe / 4 - yoe / 100 + doy 229 return era * K_MAGIC_146097 + doe - K_MAGIC_719468 230} 231 232func pj_contains(b: *u8, sz: i64, s: *u8) -> i64 { 233 let n: i64 = pj_slen(s) 234 if n == 0 { return 1 } 235 var i: i64 = 0 236 while i + n <= sz { 237 var k: i64 = 0 238 var hit: i64 = 1 239 while k < n { 240 if b[i + k] != s[k] { hit = 0; k = n } else { k = k + 1 } 241 } 242 if hit == 1 { return 1 } 243 i = i + 1 244 } 245 return 0 246} 247 248func pj_wfile(path: *u8, s: *u8) -> i64 { 249 let fd: i64 = sys_openat_wr(path, 0x1a4) 250 if fd < 0 { return 1 } 251 sys_write(fd, s, pj_slen(s)) 252 sys_close(fd) 253 return 0 254} 255 256func pj_starts(s: *u8, pfx: *u8) -> i64 { 257 var i: i64 = 0 258 while pfx[i] != (0 as u8) { 259 if s[i] != pfx[i] { return 0 } 260 i = i + 1 261 } 262 return 1 263} 264 265// boundary guard for MCP callers: store paths live under knowledge/project/ or /tmp/, no dot-dot 266func pj_pathok(s: *u8) -> i64 { 267 var i: i64 = 0 268 while s[i] != (0 as u8) { 269 if s[i] == (46 as u8) { if s[i + 1] == (46 as u8) { return 0 } } 270 i = i + 1 271 } 272 if pj_starts(s, "knowledge/project/" as *u8) == 1 { return 1 } 273 if pj_starts(s, "/tmp/" as *u8) == 1 { return 1 } 274 return 0 275} 276 277func pj_pathfail() -> i64 { 278 pj_p("PROJECT path must live under knowledge/project/ or /tmp/ with no dot-dot -- fail loud\n" as *u8) 279 return 1 280} 281 282// content-addressed additive put (mirrors the proven planner writer; CID-idempotent by construction) 283func pj_put(keys: *i64, vals: *i64, nf: i64, kp: *u8, st: *i64) -> i64 { 284 let enc: *u8 = sys_mmap(K_MAGIC_2048) 285 let el: i64 = canon_encode(keys, vals, nf, enc) 286 let cid: *u8 = sys_mmap(96) 287 cid_of(enc, el, cid) 288 let kbuf: *u8 = sys_mmap(128) 289 var ko: i64 = 0 290 ko = pj_cat(kbuf, ko, kp) 291 ko = pj_cat(kbuf, ko, cid) 292 kbuf[ko] = 0 as u8 293 let seen: *i64 = st[1] as *i64 294 var fresh: i64 = 1 295 if pj_find(seen, st[2], kbuf) >= 0 { fresh = 0; st[4] = st[4] + 1 } 296 if fresh == 1 { 297 if st[2] < K_MAGIC_2048 { 298 let dk: *u8 = pj_dup(kbuf) 299 seen[st[2]] = dk as i64 300 st[2] = st[2] + 1 301 } 302 } 303 if fresh == 1 { 304 let pp: *i64 = sys_mmap(16) as *i64 305 let ll: *i64 = sys_mmap(16) as *i64 306 if ss_get_idx(st[6] as *u8, kbuf, pp, ll) == 1 { fresh = 0; st[5] = st[5] + 1 } 307 } 308 if fresh == 1 { 309 if ss_add(st[0] as *i64, 1, kbuf, enc, el) != 0 { return 0 - 1 } 310 st[3] = st[3] + 1 311 } 312 return 0 313} 314 315// ---------------- LOAD (PROJ / MEMBER lines -> prj: / mem: records) ---------------- 316func pj_cmd_load(argc: i64, argv: *i64) -> i64 { 317 if argc < 4 { pj_p("PROJECT load needs <file> <prefix> -- fail loud\n" as *u8); return 1 } 318 let efile: *u8 = argv[2] as *u8 319 let prefix: *u8 = argv[3] as *u8 320 if pj_pathok(efile) == 0 { return pj_pathfail() } 321 if pj_pathok(prefix) == 0 { return pj_pathfail() } 322 var segid: i64 = 0 323 if argc > 4 { segid = pj_atoi(argv[4] as *u8) } 324 if segid == 0 { segid = sys_now_us() } 325 326 let szp: *i64 = sys_mmap(16) as *i64 327 let b: *u8 = ss_readall(efile, szp) 328 let sz: i64 = szp[0] 329 if sz <= 0 { pj_p("PROJECT file MISSING/EMPTY -- fail loud\n" as *u8); return 1 } 330 331 let pkeys: *i64 = sys_mmap(8 * 8) as *i64 332 let pvals: *i64 = sys_mmap(8 * 8) as *i64 333 pkeys[0] = "kind" as *u8 as i64 334 pkeys[1] = "pid" as *u8 as i64 335 pkeys[2] = "owner" as *u8 as i64 336 pkeys[3] = "title" as *u8 as i64 337 pvals[0] = "prj" as *u8 as i64 338 let mkeys: *i64 = sys_mmap(8 * 8) as *i64 339 let mvals: *i64 = sys_mmap(8 * 8) as *i64 340 mkeys[0] = "kind" as *u8 as i64 341 mkeys[1] = "pid" as *u8 as i64 342 mkeys[2] = "person" as *u8 as i64 343 mkeys[3] = "role" as *u8 as i64 344 mkeys[4] = "cap" as *u8 as i64 345 mvals[0] = "mem" as *u8 as i64 346 let bkeys: *i64 = sys_mmap(8 * 8) as *i64 347 let bvals: *i64 = sys_mmap(8 * 8) as *i64 348 bkeys[0] = "kind" as *u8 as i64 349 bkeys[1] = "pid" as *u8 as i64 350 bkeys[2] = "amount" as *u8 as i64 351 bvals[0] = "bud" as *u8 as i64 352 let ckeys: *i64 = sys_mmap(8 * 8) as *i64 353 let cvals: *i64 = sys_mmap(8 * 8) as *i64 354 ckeys[0] = "kind" as *u8 as i64 355 ckeys[1] = "pid" as *u8 as i64 356 ckeys[2] = "tid" as *u8 as i64 357 ckeys[3] = "amount" as *u8 as i64 358 ckeys[4] = "date" as *u8 as i64 359 cvals[0] = "cst" as *u8 as i64 360 let wkeys: *i64 = sys_mmap(8 * 8) as *i64 361 let wvals: *i64 = sys_mmap(8 * 8) as *i64 362 wkeys[0] = "kind" as *u8 as i64 363 wkeys[1] = "pid" as *u8 as i64 364 wkeys[2] = "mpd" as *u8 as i64 365 wkeys[3] = "tight" as *u8 as i64 366 wvals[0] = "wcf" as *u8 as i64 367 let lkeys: *i64 = sys_mmap(8 * 8) as *i64 368 let lvals: *i64 = sys_mmap(8 * 8) as *i64 369 lkeys[0] = "kind" as *u8 as i64 370 lkeys[1] = "pid" as *u8 as i64 371 lkeys[2] = "tid" as *u8 as i64 372 lkeys[3] = "person" as *u8 as i64 373 lkeys[4] = "date" as *u8 as i64 374 lkeys[5] = "min" as *u8 as i64 375 lvals[0] = "log" as *u8 as i64 376 let nkeys: *i64 = sys_mmap(8 * 8) as *i64 377 let nvals: *i64 = sys_mmap(8 * 8) as *i64 378 nkeys[0] = "kind" as *u8 as i64 379 nkeys[1] = "pid" as *u8 as i64 380 nkeys[2] = "tid" as *u8 as i64 381 nkeys[3] = "person" as *u8 as i64 382 nkeys[4] = "date" as *u8 as i64 383 nkeys[5] = "text" as *u8 as i64 384 nvals[0] = "not" as *u8 as i64 385 let akeys: *i64 = sys_mmap(8 * 8) as *i64 386 let avals: *i64 = sys_mmap(8 * 8) as *i64 387 akeys[0] = "kind" as *u8 as i64 388 akeys[1] = "pid" as *u8 as i64 389 akeys[2] = "tid" as *u8 as i64 390 akeys[3] = "person" as *u8 as i64 391 akeys[4] = "date" as *u8 as i64 392 avals[0] = "asg" as *u8 as i64 393 let bkeys2: *i64 = sys_mmap(8 * 8) as *i64 394 let bvals2: *i64 = sys_mmap(8 * 8) as *i64 395 bkeys2[0] = "kind" as *u8 as i64 396 bkeys2[1] = "pid" as *u8 as i64 397 bkeys2[2] = "tid" as *u8 as i64 398 bkeys2[3] = "due" as *u8 as i64 399 bkeys2[4] = "effort" as *u8 as i64 400 bkeys2[5] = "date" as *u8 as i64 401 bvals2[0] = "bas" as *u8 as i64 402 403 let st: *i64 = sys_mmap(8 * 8) as *i64 404 st[0] = ss_begin() as i64 405 st[1] = sys_mmap(8 * K_MAGIC_2048) 406 st[2] = 0 407 st[3] = 0 408 st[4] = 0 409 st[5] = 0 410 st[6] = prefix as i64 411 412 let t0: *u8 = sys_mmap(128) 413 let f1: *u8 = sys_mmap(128) 414 let f2: *u8 = sys_mmap(128) 415 let f3: *u8 = sys_mmap(128) 416 let f4: *u8 = sys_mmap(256) 417 let f5b: *u8 = sys_mmap(64) 418 let txt: *u8 = sys_mmap(256) 419 var scanned: i64 = 0 420 421 var i: i64 = 0 422 while i < sz { 423 var e: i64 = i 424 var go: i64 = 1 425 while go == 1 { 426 if e >= sz { go = 0 } else { 427 if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } 428 } 429 } 430 var p: i64 = pj_tok(b, i, e, t0, 128) 431 if t0[0] == (35 as u8) { t0[0] = 0 as u8 } 432 if pj_eq(t0, "PROJ" as *u8) == 1 { 433 scanned = scanned + 1 434 p = pj_tok(b, p, e, f1, 128) 435 p = pj_tok(b, p, e, f2, 128) 436 p = pj_tok(b, p, e, f4, 256) 437 if f1[0] == (0 as u8) { pj_p("PROJECT PROJ missing pid -- fail loud\n" as *u8); return 1 } 438 if f2[0] == (0 as u8) { pj_p("PROJECT PROJ missing owner -- fail loud\n" as *u8); return 1 } 439 if f4[0] == (0 as u8) { pj_p("PROJECT PROJ missing title -- fail loud\n" as *u8); return 1 } 440 pvals[1] = f1 as i64 441 pvals[2] = f2 as i64 442 pvals[3] = f4 as i64 443 if pj_put(pkeys, pvals, 4, "prj:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 444 } 445 if pj_eq(t0, "MEMBER" as *u8) == 1 { 446 scanned = scanned + 1 447 p = pj_tok(b, p, e, f1, 128) 448 p = pj_tok(b, p, e, f2, 128) 449 p = pj_tok(b, p, e, f3, 128) 450 p = pj_tok(b, p, e, f4, 256) 451 if f1[0] == (0 as u8) { pj_p("PROJECT MEMBER missing pid -- fail loud\n" as *u8); return 1 } 452 if f2[0] == (0 as u8) { pj_p("PROJECT MEMBER missing person -- fail loud\n" as *u8); return 1 } 453 var rok: i64 = 0 454 if pj_eq(f3, "owner" as *u8) == 1 { rok = 1 } 455 if pj_eq(f3, "team" as *u8) == 1 { rok = 1 } 456 if pj_eq(f3, "contractor" as *u8) == 1 { rok = 1 } 457 if rok == 0 { pj_p("PROJECT MEMBER role must be owner|team|contractor -- fail loud\n" as *u8); return 1 } 458 var cok: i64 = 0 459 if f4[0] == (45 as u8) { if f4[1] == (0 as u8) { cok = 1 } } 460 if cok == 0 { if pj_digits(f4) == 1 { cok = 1 } } 461 if cok == 0 { pj_p("PROJECT MEMBER cap must be minutes or - -- fail loud\n" as *u8); return 1 } 462 mvals[1] = f1 as i64 463 mvals[2] = f2 as i64 464 mvals[3] = f3 as i64 465 mvals[4] = f4 as i64 466 if pj_put(mkeys, mvals, 5, "mem:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 467 } 468 if pj_eq(t0, "BUDGET" as *u8) == 1 { 469 scanned = scanned + 1 470 p = pj_tok(b, p, e, f1, 128) 471 p = pj_tok(b, p, e, f2, 128) 472 if f1[0] == (0 as u8) { pj_p("PROJECT BUDGET missing pid -- fail loud\n" as *u8); return 1 } 473 if pj_digits(f2) == 0 { pj_p("PROJECT BUDGET amount must be cents digits -- fail loud\n" as *u8); return 1 } 474 bvals[1] = f1 as i64 475 bvals[2] = f2 as i64 476 if pj_put(bkeys, bvals, 3, "bud:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 477 } 478 if pj_eq(t0, "COST" as *u8) == 1 { 479 scanned = scanned + 1 480 p = pj_tok(b, p, e, f1, 128) 481 p = pj_tok(b, p, e, f2, 128) 482 p = pj_tok(b, p, e, f3, 128) 483 p = pj_tok(b, p, e, f4, 256) 484 if f1[0] == (0 as u8) { pj_p("PROJECT COST missing pid -- fail loud\n" as *u8); return 1 } 485 if f2[0] == (0 as u8) { pj_p("PROJECT COST missing tid -- fail loud\n" as *u8); return 1 } 486 if pj_digits(f3) == 0 { pj_p("PROJECT COST amount must be cents digits -- fail loud\n" as *u8); return 1 } 487 if pj_datedays(f4) < 0 { pj_p("PROJECT COST bad date -- fail loud\n" as *u8); return 1 } 488 cvals[1] = f1 as i64 489 cvals[2] = f2 as i64 490 cvals[3] = f3 as i64 491 cvals[4] = f4 as i64 492 if pj_put(ckeys, cvals, 5, "cst:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 493 } 494 if pj_eq(t0, "WCONF" as *u8) == 1 { 495 scanned = scanned + 1 496 p = pj_tok(b, p, e, f1, 128) 497 p = pj_tok(b, p, e, f2, 128) 498 p = pj_tok(b, p, e, f3, 128) 499 if f1[0] == (0 as u8) { pj_p("PROJECT WCONF missing pid -- fail loud\n" as *u8); return 1 } 500 if pj_digits(f2) == 0 { pj_p("PROJECT WCONF min-per-day must be digits -- fail loud\n" as *u8); return 1 } 501 if pj_atoi(f2) < 1 { pj_p("PROJECT WCONF min-per-day must be >= 1 -- fail loud\n" as *u8); return 1 } 502 if pj_digits(f3) == 0 { pj_p("PROJECT WCONF tight-days must be digits -- fail loud\n" as *u8); return 1 } 503 wvals[1] = f1 as i64 504 wvals[2] = f2 as i64 505 wvals[3] = f3 as i64 506 if pj_put(wkeys, wvals, 4, "wcf:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 507 } 508 if pj_eq(t0, "LOG" as *u8) == 1 { 509 scanned = scanned + 1 510 p = pj_tok(b, p, e, f1, 128) 511 p = pj_tok(b, p, e, f2, 128) 512 p = pj_tok(b, p, e, f3, 128) 513 p = pj_tok(b, p, e, f4, 256) 514 p = pj_tok(b, p, e, f5b, 64) 515 if f1[0] == (0 as u8) { pj_p("PROJECT LOG missing pid -- fail loud\n" as *u8); return 1 } 516 if f2[0] == (0 as u8) { pj_p("PROJECT LOG missing tid -- fail loud\n" as *u8); return 1 } 517 if f3[0] == (0 as u8) { pj_p("PROJECT LOG missing person -- fail loud\n" as *u8); return 1 } 518 if pj_datedays(f4) < 0 { pj_p("PROJECT LOG bad date -- fail loud\n" as *u8); return 1 } 519 if pj_digits(f5b) == 0 { pj_p("PROJECT LOG minutes must be digits -- fail loud\n" as *u8); return 1 } 520 if pj_atoi(f5b) < 1 { pj_p("PROJECT LOG minutes must be >= 1 -- fail loud\n" as *u8); return 1 } 521 lvals[1] = f1 as i64 522 lvals[2] = f2 as i64 523 lvals[3] = f3 as i64 524 lvals[4] = f4 as i64 525 lvals[5] = f5b as i64 526 if pj_put(lkeys, lvals, 6, "log:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 527 } 528 if pj_eq(t0, "ASSIGN" as *u8) == 1 { 529 scanned = scanned + 1 530 p = pj_tok(b, p, e, f1, 128) 531 p = pj_tok(b, p, e, f2, 128) 532 p = pj_tok(b, p, e, f3, 128) 533 p = pj_tok(b, p, e, f4, 256) 534 if f1[0] == (0 as u8) { pj_p("PROJECT ASSIGN missing pid -- fail loud\n" as *u8); return 1 } 535 if f2[0] == (0 as u8) { pj_p("PROJECT ASSIGN missing tid -- fail loud\n" as *u8); return 1 } 536 if f3[0] == (0 as u8) { pj_p("PROJECT ASSIGN missing person -- fail loud\n" as *u8); return 1 } 537 if pj_datedays(f4) < 0 { pj_p("PROJECT ASSIGN bad date -- fail loud\n" as *u8); return 1 } 538 avals[1] = f1 as i64 539 avals[2] = f2 as i64 540 avals[3] = f3 as i64 541 avals[4] = f4 as i64 542 if pj_put(akeys, avals, 5, "asg:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 543 } 544 if pj_eq(t0, "BASE" as *u8) == 1 { 545 scanned = scanned + 1 546 p = pj_tok(b, p, e, f1, 128) 547 p = pj_tok(b, p, e, f2, 128) 548 p = pj_tok(b, p, e, f3, 128) 549 p = pj_tok(b, p, e, f5b, 64) 550 p = pj_tok(b, p, e, f4, 256) 551 if f1[0] == (0 as u8) { pj_p("PROJECT BASE missing pid -- fail loud\n" as *u8); return 1 } 552 if f2[0] == (0 as u8) { pj_p("PROJECT BASE missing tid -- fail loud\n" as *u8); return 1 } 553 if pj_datedays(f3) < 0 { pj_p("PROJECT BASE bad baseline due date -- fail loud\n" as *u8); return 1 } 554 if pj_digits(f5b) == 0 { pj_p("PROJECT BASE effort must be digits -- fail loud\n" as *u8); return 1 } 555 if pj_datedays(f4) < 0 { pj_p("PROJECT BASE bad snapshot date -- fail loud\n" as *u8); return 1 } 556 bvals2[1] = f1 as i64 557 bvals2[2] = f2 as i64 558 bvals2[3] = f3 as i64 559 bvals2[4] = f5b as i64 560 bvals2[5] = f4 as i64 561 if pj_put(bkeys2, bvals2, 6, "bas:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 562 } 563 if pj_eq(t0, "NOTE" as *u8) == 1 { 564 scanned = scanned + 1 565 p = pj_tok(b, p, e, f1, 128) 566 p = pj_tok(b, p, e, f2, 128) 567 p = pj_tok(b, p, e, f3, 128) 568 p = pj_tok(b, p, e, f4, 256) 569 if f1[0] == (0 as u8) { pj_p("PROJECT NOTE missing pid -- fail loud\n" as *u8); return 1 } 570 if f2[0] == (0 as u8) { pj_p("PROJECT NOTE missing tid -- fail loud\n" as *u8); return 1 } 571 if f3[0] == (0 as u8) { pj_p("PROJECT NOTE missing person -- fail loud\n" as *u8); return 1 } 572 if pj_datedays(f4) < 0 { pj_p("PROJECT NOTE bad date -- fail loud\n" as *u8); return 1 } 573 // text = the rest of the line, whitespace-trimmed both ends (multi-word) 574 var ts: i64 = p 575 var g1: i64 = 1 576 while g1 == 1 { 577 if ts >= e { g1 = 0 } else { 578 if pj_isws(b[ts] as i64) == 1 { ts = ts + 1 } else { g1 = 0 } 579 } 580 } 581 var te: i64 = e 582 var g2: i64 = 1 583 while g2 == 1 { 584 if te <= ts { g2 = 0 } else { 585 if pj_isws(b[te - 1] as i64) == 1 { te = te - 1 } else { g2 = 0 } 586 } 587 } 588 var tn: i64 = 0 589 while ts < te { if tn < 239 { txt[tn] = b[ts]; tn = tn + 1 } ts = ts + 1 } 590 txt[tn] = 0 as u8 591 if txt[0] == (0 as u8) { pj_p("PROJECT NOTE missing text -- fail loud\n" as *u8); return 1 } 592 nvals[1] = f1 as i64 593 nvals[2] = f2 as i64 594 nvals[3] = f3 as i64 595 nvals[4] = f4 as i64 596 nvals[5] = txt as i64 597 if pj_put(nkeys, nvals, 6, "not:" as *u8, st) != 0 { pj_p("PROJECT writer full -- fail loud\n" as *u8); return 1 } 598 } 599 i = e + 1 600 } 601 602 var committed: i64 = 0 603 if st[3] > 0 { 604 if ss_commit(prefix, st[0] as *i64, segid) != 0 { pj_p("PROJECT commit FAILED -- fail loud\n" as *u8); return 1 } 605 committed = 1 606 } 607 let rep: *u8 = sys_mmap(K_MAGIC_1024) 608 var o: i64 = 0 609 o = pj_cat(rep, o, "PROJECT-LOAD scanned=" as *u8) 610 o = pj_catn(rep, o, scanned) 611 o = pj_cat(rep, o, " new=" as *u8) 612 o = pj_catn(rep, o, st[3]) 613 o = pj_cat(rep, o, " dup_infile=" as *u8) 614 o = pj_catn(rep, o, st[4]) 615 o = pj_cat(rep, o, " dup_instore=" as *u8) 616 o = pj_catn(rep, o, st[5]) 617 o = pj_cat(rep, o, " segment=" as *u8) 618 if committed == 1 { 619 o = pj_cat(rep, o, "seg-" as *u8) 620 o = pj_catn(rep, o, segid) 621 } else { 622 o = pj_cat(rep, o, "none" as *u8) 623 } 624 o = pj_cat(rep, o, "\n" as *u8) 625 sys_write(1, rep, o) 626 return 0 627} 628 629// ---------------- GATHER (prj:/mem: + the planner's tsk:/don: for one project) ---------------- 630// st slots: 0=ntask 1=tids 2=towners 3=tdeps 4=tefforts(values) 5=ttitles 631// 6=ndone 7=dtids 8=nmem 9=mpersons 10=mroles 11=mcaps 632// 12=projfound 13=projowner 14=projtitle 633// 15=budfound 16=bud_cents 17=ac_cents_sum 18=wcffound 19=wcf_min_per_day 20=wcf_tight_days 634// 21=tdues 22=nlog 23=ltids 24=lpersons 25=lmins(values) 635// 26=nnote 27=ntids 28=npersons 29=ntexts 30=ndates 636// 31=nasg 32=atids 33=apersons 34=adays(values) (effective owner = latest ASSIGN wins) 637// 35=ddates(done dates parallel to dtids) 36=nbase 37=btids 38=bdues 39=befforts(values) 638func pj_st_new() -> *i64 { 639 let st: *i64 = sys_mmap(8 * 48) as *i64 640 st[0] = 0 641 st[1] = sys_mmap(8 * 128) 642 st[2] = sys_mmap(8 * 128) 643 st[3] = sys_mmap(8 * 128) 644 st[4] = sys_mmap(8 * 128) 645 st[5] = sys_mmap(8 * 128) 646 st[6] = 0 647 st[7] = sys_mmap(8 * 256) 648 st[8] = 0 649 st[9] = sys_mmap(8 * 64) 650 st[10] = sys_mmap(8 * 64) 651 st[11] = sys_mmap(8 * 64) 652 st[12] = 0 653 st[13] = 0 654 st[14] = 0 655 st[15] = 0 656 st[16] = 0 657 st[17] = 0 658 st[18] = 0 659 st[19] = 0 660 st[20] = 0 661 st[21] = sys_mmap(8 * 128) 662 st[22] = 0 663 st[23] = sys_mmap(8 * 256) 664 st[24] = sys_mmap(8 * 256) 665 st[25] = sys_mmap(8 * 256) 666 st[26] = 0 667 st[27] = sys_mmap(8 * 128) 668 st[28] = sys_mmap(8 * 128) 669 st[29] = sys_mmap(8 * 128) 670 st[30] = sys_mmap(8 * 128) 671 st[31] = 0 672 st[32] = sys_mmap(8 * 256) 673 st[33] = sys_mmap(8 * 256) 674 st[34] = sys_mmap(8 * 256) 675 st[35] = sys_mmap(8 * 256) 676 st[36] = 0 677 st[37] = sys_mmap(8 * 128) 678 st[38] = sys_mmap(8 * 128) 679 st[39] = sys_mmap(8 * 128) 680 return st 681} 682 683func pj_gather(prefix: *u8, pid: *u8, st: *i64) -> i64 { 684 let segp: *i64 = sys_mmap(16) as *i64 685 let ns: i64 = ss_manifest_dyn(prefix, segp) // uncapped: sees the WHOLE store 686 let segs: *i64 = segp[0] as *i64 687 let fp: *u8 = sys_mmap(128) 688 let fa: *u8 = sys_mmap(256) 689 let fb: *u8 = sys_mmap(256) 690 let fc: *u8 = sys_mmap(256) 691 let fd2: *u8 = sys_mmap(256) 692 let fe: *u8 = sys_mmap(256) 693 let fg: *u8 = sys_mmap(256) 694 var s: i64 = 0 695 while s < ns { 696 let path: *u8 = sys_mmap(512) 697 var po: i64 = 0 698 po = pj_cat(path, po, prefix) 699 po = pj_cat(path, po, segs[s] as *u8) 700 po = pj_cat(path, po, ".docs" as *u8) 701 path[po] = 0 as u8 702 let szp: *i64 = sys_mmap(16) as *i64 703 let b: *u8 = ss_readall(path, szp) 704 let sz: i64 = szp[0] 705 var j: i64 = 0 706 while j + 9 <= sz { 707 let kind: i64 = b[j] 708 let kl: i64 = ss_r32(b, j + 1) 709 let koff: i64 = j + 5 710 let vl: i64 = ss_r32(b, koff + kl) 711 let voff: i64 = koff + kl + 4 712 let rec: *u8 = (b as i64 + voff) as *u8 713 var isprj: i64 = 0 714 var ismem: i64 = 0 715 var istsk: i64 = 0 716 var isdon: i64 = 0 717 var isbud: i64 = 0 718 var iscst: i64 = 0 719 var iswcf: i64 = 0 720 var islog: i64 = 0 721 var isnot: i64 = 0 722 var isasg: i64 = 0 723 var isbas: i64 = 0 724 if kind == 1 { isprj = pj_iskind(b, koff, kl, "prj:" as *u8) } 725 if kind == 1 { ismem = pj_iskind(b, koff, kl, "mem:" as *u8) } 726 if kind == 1 { istsk = pj_iskind(b, koff, kl, "tsk:" as *u8) } 727 if kind == 1 { isdon = pj_iskind(b, koff, kl, "don:" as *u8) } 728 if kind == 1 { isbud = pj_iskind(b, koff, kl, "bud:" as *u8) } 729 if kind == 1 { iscst = pj_iskind(b, koff, kl, "cst:" as *u8) } 730 if kind == 1 { iswcf = pj_iskind(b, koff, kl, "wcf:" as *u8) } 731 if kind == 1 { islog = pj_iskind(b, koff, kl, "log:" as *u8) } 732 if kind == 1 { isnot = pj_iskind(b, koff, kl, "not:" as *u8) } 733 if kind == 1 { isasg = pj_iskind(b, koff, kl, "asg:" as *u8) } 734 if kind == 1 { isbas = pj_iskind(b, koff, kl, "bas:" as *u8) } 735 if isprj == 1 { 736 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { if st[12] == 0 { 737 pj_field(rec, vl, "owner" as *u8, fa, 256) 738 pj_field(rec, vl, "title" as *u8, fb, 256) 739 let d1: *u8 = pj_dup(fa) 740 let d2: *u8 = pj_dup(fb) 741 st[13] = d1 as i64 742 st[14] = d2 as i64 743 st[12] = 1 744 } } } 745 } 746 if ismem == 1 { 747 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 748 pj_field(rec, vl, "person" as *u8, fa, 256) 749 let mp: *i64 = st[9] as *i64 750 if pj_find(mp, st[8], fa) < 0 { 751 if st[8] >= 64 { pj_p("PROJECT gather members full -- fail loud\n" as *u8); return 1 } 752 pj_field(rec, vl, "role" as *u8, fb, 256) 753 pj_field(rec, vl, "cap" as *u8, fc, 256) 754 let mr: *i64 = st[10] as *i64 755 let mc: *i64 = st[11] as *i64 756 let n: i64 = st[8] 757 let d1: *u8 = pj_dup(fa) 758 let d2: *u8 = pj_dup(fb) 759 let d3: *u8 = pj_dup(fc) 760 mp[n] = d1 as i64 761 mr[n] = d2 as i64 762 mc[n] = d3 as i64 763 st[8] = n + 1 764 } 765 } } 766 } 767 if istsk == 1 { 768 if pj_field(rec, vl, "plan" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 769 pj_field(rec, vl, "tid" as *u8, fa, 256) 770 let tt: *i64 = st[1] as *i64 771 if pj_find(tt, st[0], fa) < 0 { 772 if st[0] >= 128 { pj_p("PROJECT gather tasks full -- fail loud\n" as *u8); return 1 } 773 pj_field(rec, vl, "owner" as *u8, fb, 256) 774 pj_field(rec, vl, "dep" as *u8, fc, 256) 775 pj_field(rec, vl, "effort" as *u8, fd2, 256) 776 pj_field(rec, vl, "title" as *u8, fe, 256) 777 pj_field(rec, vl, "due" as *u8, fg, 256) 778 let ow: *i64 = st[2] as *i64 779 let dp: *i64 = st[3] as *i64 780 let ef: *i64 = st[4] as *i64 781 let tl: *i64 = st[5] as *i64 782 let du: *i64 = st[21] as *i64 783 let n: i64 = st[0] 784 let d1: *u8 = pj_dup(fa) 785 let d2: *u8 = pj_dup(fb) 786 let d3: *u8 = pj_dup(fc) 787 let d4: *u8 = pj_dup(fe) 788 let d5: *u8 = pj_dup(fg) 789 let ev: i64 = pj_atoi(fd2) 790 tt[n] = d1 as i64 791 ow[n] = d2 as i64 792 dp[n] = d3 as i64 793 ef[n] = ev 794 tl[n] = d4 as i64 795 du[n] = d5 as i64 796 st[0] = n + 1 797 } 798 } } 799 } 800 if isdon == 1 { 801 if pj_field(rec, vl, "plan" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 802 pj_field(rec, vl, "tid" as *u8, fa, 256) 803 let dt: *i64 = st[7] as *i64 804 if pj_find(dt, st[6], fa) < 0 { 805 if st[6] >= 256 { pj_p("PROJECT gather done full -- fail loud\n" as *u8); return 1 } 806 pj_field(rec, vl, "date" as *u8, fb, 256) 807 let dd: *i64 = st[35] as *i64 808 let n: i64 = st[6] 809 let d1: *u8 = pj_dup(fa) 810 dt[n] = d1 as i64 811 dd[n] = pj_datedays(fb) 812 st[6] = n + 1 813 } 814 } } 815 } 816 if isbas == 1 { 817 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 818 if st[36] >= 128 { pj_p("PROJECT gather baseline full -- fail loud\n" as *u8); return 1 } 819 pj_field(rec, vl, "tid" as *u8, fa, 256) 820 let bt: *i64 = st[37] as *i64 821 if pj_find(bt, st[36], fa) < 0 { 822 pj_field(rec, vl, "due" as *u8, fb, 256) 823 pj_field(rec, vl, "effort" as *u8, fc, 256) 824 let bd: *i64 = st[38] as *i64 825 let be: *i64 = st[39] as *i64 826 let n: i64 = st[36] 827 let d1: *u8 = pj_dup(fa) 828 bt[n] = d1 as i64 829 bd[n] = pj_datedays(fb) 830 be[n] = pj_atoi(fc) 831 st[36] = n + 1 832 } 833 } } 834 } 835 if isbud == 1 { 836 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { if st[15] == 0 { 837 pj_field(rec, vl, "amount" as *u8, fa, 256) 838 st[16] = pj_atoi(fa) 839 st[15] = 1 840 } } } 841 } 842 if iscst == 1 { 843 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 844 pj_field(rec, vl, "amount" as *u8, fa, 256) 845 st[17] = st[17] + pj_atoi(fa) 846 } } 847 } 848 if iswcf == 1 { 849 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { if st[18] == 0 { 850 pj_field(rec, vl, "mpd" as *u8, fa, 256) 851 pj_field(rec, vl, "tight" as *u8, fb, 256) 852 st[19] = pj_atoi(fa) 853 st[20] = pj_atoi(fb) 854 if st[19] > 0 { st[18] = 1 } 855 } } } 856 } 857 if islog == 1 { 858 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 859 if st[22] >= 256 { pj_p("PROJECT gather logs full -- fail loud\n" as *u8); return 1 } 860 pj_field(rec, vl, "tid" as *u8, fa, 256) 861 pj_field(rec, vl, "person" as *u8, fb, 256) 862 pj_field(rec, vl, "min" as *u8, fc, 256) 863 let lt: *i64 = st[23] as *i64 864 let lp: *i64 = st[24] as *i64 865 let lm: *i64 = st[25] as *i64 866 let n: i64 = st[22] 867 let d1: *u8 = pj_dup(fa) 868 let d2: *u8 = pj_dup(fb) 869 let mv: i64 = pj_atoi(fc) 870 lt[n] = d1 as i64 871 lp[n] = d2 as i64 872 lm[n] = mv 873 st[22] = n + 1 874 } } 875 } 876 if isasg == 1 { 877 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 878 if st[31] >= 256 { pj_p("PROJECT gather assignments full -- fail loud\n" as *u8); return 1 } 879 pj_field(rec, vl, "tid" as *u8, fa, 256) 880 pj_field(rec, vl, "person" as *u8, fb, 256) 881 pj_field(rec, vl, "date" as *u8, fc, 256) 882 let at2: *i64 = st[32] as *i64 883 let ap2: *i64 = st[33] as *i64 884 let ad2: *i64 = st[34] as *i64 885 let n: i64 = st[31] 886 let d1: *u8 = pj_dup(fa) 887 let d2: *u8 = pj_dup(fb) 888 let dv: i64 = pj_datedays(fc) 889 at2[n] = d1 as i64 890 ap2[n] = d2 as i64 891 ad2[n] = dv 892 st[31] = n + 1 893 } } 894 } 895 if isnot == 1 { 896 if pj_field(rec, vl, "pid" as *u8, fp, 128) == 1 { if pj_eq(fp, pid) == 1 { 897 if st[26] >= 128 { pj_p("PROJECT gather notes full -- fail loud\n" as *u8); return 1 } 898 pj_field(rec, vl, "tid" as *u8, fa, 256) 899 pj_field(rec, vl, "person" as *u8, fb, 256) 900 pj_field(rec, vl, "text" as *u8, fd2, 256) 901 pj_field(rec, vl, "date" as *u8, fe, 256) 902 let nt: *i64 = st[27] as *i64 903 let np: *i64 = st[28] as *i64 904 let nx: *i64 = st[29] as *i64 905 let nd: *i64 = st[30] as *i64 906 let n: i64 = st[26] 907 let d1: *u8 = pj_dup(fa) 908 let d2: *u8 = pj_dup(fb) 909 let d3: *u8 = pj_dup(fd2) 910 let d4: *u8 = pj_dup(fe) 911 nt[n] = d1 as i64 912 np[n] = d2 as i64 913 nx[n] = d3 as i64 914 nd[n] = d4 as i64 915 st[26] = n + 1 916 } } 917 } 918 j = voff + vl 919 } 920 s = s + 1 921 } 922 // effective owner: the LATEST ASSIGN (by date) overrides the task's authored owner. 923 // Tie on date -> lexicographically greater person (deterministic regardless of scan order). 924 if st[31] > 0 { 925 let tt2: *i64 = st[1] as *i64 926 let ow2: *i64 = st[2] as *i64 927 let at3: *i64 = st[32] as *i64 928 let ap3: *i64 = st[33] as *i64 929 let ad3: *i64 = st[34] as *i64 930 var ti: i64 = 0 931 while ti < st[0] { 932 var bestd: i64 = 0 - 1 933 var bestp: i64 = 0 934 var ai: i64 = 0 935 while ai < st[31] { 936 if pj_eq(at3[ai] as *u8, tt2[ti] as *u8) == 1 { 937 var take: i64 = 0 938 if ad3[ai] > bestd { take = 1 } 939 if ad3[ai] == bestd { if bestp != 0 { if pj_cmpgt(ap3[ai] as *u8, bestp as *u8) == 1 { take = 1 } } } 940 if take == 1 { bestd = ad3[ai]; bestp = ap3[ai] } 941 } 942 ai = ai + 1 943 } 944 if bestp != 0 { ow2[ti] = bestp } 945 ti = ti + 1 946 } 947 } 948 return 0 949} 950 951// all deps of a task satisfied? dep field is "-" or "a,b,c" 952func pj_depsok(dep: *u8, dt: *i64, nd: i64) -> i64 { 953 if dep[0] == (45 as u8) { if dep[1] == (0 as u8) { return 1 } } 954 let nm: *u8 = sys_mmap(128) 955 var i: i64 = 0 956 var t: i64 = 0 957 var go: i64 = 1 958 while go == 1 { 959 let c: i64 = dep[i] as i64 960 var fin: i64 = 0 961 if c == 44 { fin = 1 } 962 if c == 0 { fin = 1 } 963 if fin == 1 { 964 nm[t] = 0 as u8 965 if t > 0 { if pj_find(dt, nd, nm) < 0 { return 0 } } 966 t = 0 967 if c == 0 { go = 0 } 968 } else { 969 if t < 127 { nm[t] = dep[i]; t = t + 1 } 970 } 971 i = i + 1 972 } 973 return 1 974} 975 976// 0=READY 1=BLOCKED 2=DONE for task index idx in a gathered st 977func pj_state(st: *i64, idx: i64) -> i64 { 978 let tids: *i64 = st[1] as *i64 979 let deps: *i64 = st[3] as *i64 980 let dt: *i64 = st[7] as *i64 981 let nd: i64 = st[6] 982 if pj_find(dt, nd, tids[idx] as *u8) >= 0 { return 2 } 983 if pj_depsok(deps[idx] as *u8, dt, nd) == 0 { return 1 } 984 return 0 985} 986 987// role of person in a gathered st: project owner is implicitly owner; else the mem: record; 0 = none 988func pj_roleof(st: *i64, person: *u8) -> i64 { 989 if st[12] == 1 { if pj_eq(st[13] as *u8, person) == 1 { return "owner" as *u8 as i64 } } 990 let mp: *i64 = st[9] as *i64 991 let mr: *i64 = st[10] as *i64 992 let idx: i64 = pj_find(mp, st[8], person) 993 if idx >= 0 { return mr[idx] } 994 return 0 995} 996 997func pj_statename(v: i64) -> i64 { 998 if v == 2 { return "DONE" as *u8 as i64 } 999 if v == 1 { return "BLOCKED" as *u8 as i64 } 1000 return "READY" as *u8 as i64 1001} 1002 1003// ---------------- VIEW (fail-closed role-scoped) ---------------- 1004// rc: 0 ok, 1 project not found, 2 access denied. out[0]=total tasks, out[1]=shown 1005func pj_view(prefix: *u8, pid: *u8, person: *u8, out: *i64) -> i64 { 1006 out[0] = 0 1007 out[1] = 0 1008 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1009 let st: *i64 = pj_st_new() 1010 if pj_gather(prefix, pid, st) != 0 { return 1 } 1011 let rep: *u8 = sys_mmap(K_MAGIC_2048) 1012 if st[12] == 0 { 1013 var o0: i64 = 0 1014 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 1015 o0 = pj_cat(rep, o0, pid) 1016 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 1017 sys_write(1, rep, o0) 1018 return 1 1019 } 1020 let role: i64 = pj_roleof(st, person) 1021 if role == 0 { 1022 var o1: i64 = 0 1023 o1 = pj_cat(rep, o1, "PROJECT-DENY pid=" as *u8) 1024 o1 = pj_cat(rep, o1, pid) 1025 o1 = pj_cat(rep, o1, " person=" as *u8) 1026 o1 = pj_cat(rep, o1, person) 1027 o1 = pj_cat(rep, o1, " -- not a member, access denied by default\n" as *u8) 1028 sys_write(1, rep, o1) 1029 return 2 1030 } 1031 var own_only: i64 = 0 1032 if pj_eq(role as *u8, "contractor" as *u8) == 1 { own_only = 1 } 1033 var o: i64 = 0 1034 o = pj_cat(rep, o, "PROJECT-VIEW pid=" as *u8) 1035 o = pj_cat(rep, o, pid) 1036 o = pj_cat(rep, o, " title=" as *u8) 1037 o = pj_cat(rep, o, st[14] as *u8) 1038 o = pj_cat(rep, o, " person=" as *u8) 1039 o = pj_cat(rep, o, person) 1040 o = pj_cat(rep, o, " role=" as *u8) 1041 o = pj_cat(rep, o, role as *u8) 1042 o = pj_cat(rep, o, " scope=" as *u8) 1043 if own_only == 1 { o = pj_cat(rep, o, "own\n" as *u8) } else { o = pj_cat(rep, o, "all\n" as *u8) } 1044 sys_write(1, rep, o) 1045 let tids: *i64 = st[1] as *i64 1046 let owners: *i64 = st[2] as *i64 1047 let titles: *i64 = st[5] as *i64 1048 let efforts: *i64 = st[4] as *i64 1049 var shown: i64 = 0 1050 var i: i64 = 0 1051 while i < st[0] { 1052 var vis: i64 = 1 1053 if own_only == 1 { if pj_eq(owners[i] as *u8, person) == 0 { vis = 0 } } 1054 if vis == 1 { 1055 let sv: i64 = pj_state(st, i) 1056 let sn: i64 = pj_statename(sv) 1057 var t: i64 = 0 1058 t = pj_cat(rep, t, "PROJECT-TASK tid=" as *u8) 1059 t = pj_cat(rep, t, tids[i] as *u8) 1060 t = pj_cat(rep, t, " owner=" as *u8) 1061 t = pj_cat(rep, t, owners[i] as *u8) 1062 t = pj_cat(rep, t, " state=" as *u8) 1063 t = pj_cat(rep, t, sn as *u8) 1064 t = pj_cat(rep, t, " effort_min=" as *u8) 1065 t = pj_catn(rep, t, efforts[i]) 1066 t = pj_cat(rep, t, " title=" as *u8) 1067 t = pj_cat(rep, t, titles[i] as *u8) 1068 t = pj_cat(rep, t, "\n" as *u8) 1069 sys_write(1, rep, t) 1070 shown = shown + 1 1071 } 1072 i = i + 1 1073 } 1074 var z: i64 = 0 1075 z = pj_cat(rep, z, "PROJECT-VIEW-END total=" as *u8) 1076 z = pj_catn(rep, z, st[0]) 1077 z = pj_cat(rep, z, " shown=" as *u8) 1078 z = pj_catn(rep, z, shown) 1079 z = pj_cat(rep, z, "\n" as *u8) 1080 sys_write(1, rep, z) 1081 out[0] = st[0] 1082 out[1] = shown 1083 return 0 1084} 1085 1086// per-person open effort (not-done tasks owned by person); -1 if project missing 1087func pj_open_min(prefix: *u8, pid: *u8, person: *u8) -> i64 { 1088 let st: *i64 = pj_st_new() 1089 if pj_gather(prefix, pid, st) != 0 { return 0 - 1 } 1090 if st[12] == 0 { return 0 - 1 } 1091 let owners: *i64 = st[2] as *i64 1092 let efforts: *i64 = st[4] as *i64 1093 var sum: i64 = 0 1094 var i: i64 = 0 1095 while i < st[0] { 1096 if pj_eq(owners[i] as *u8, person) == 1 { 1097 let sv: i64 = pj_state(st, i) 1098 if sv != 2 { sum = sum + efforts[i] } 1099 } 1100 i = i + 1 1101 } 1102 return sum 1103} 1104 1105// over-capacity: 1 over, 0 within, -1 no recorded capacity (or not a member) 1106func pj_over(prefix: *u8, pid: *u8, person: *u8) -> i64 { 1107 let st: *i64 = pj_st_new() 1108 if pj_gather(prefix, pid, st) != 0 { return 0 - 1 } 1109 let mp: *i64 = st[9] as *i64 1110 let mc: *i64 = st[11] as *i64 1111 let idx: i64 = pj_find(mp, st[8], person) 1112 if idx < 0 { return 0 - 1 } 1113 let cap: *u8 = mc[idx] as *u8 1114 if pj_digits(cap) == 0 { return 0 - 1 } 1115 let open: i64 = pj_open_min(prefix, pid, person) 1116 if open > pj_atoi(cap) { return 1 } 1117 return 0 1118} 1119 1120// task visibility for a person: 1 visible, 0 hidden, -1 no such task, -2 denied 1121func pj_visible(prefix: *u8, pid: *u8, person: *u8, tid: *u8) -> i64 { 1122 let st: *i64 = pj_st_new() 1123 if pj_gather(prefix, pid, st) != 0 { return 0 - 2 } 1124 if st[12] == 0 { return 0 - 2 } 1125 let role: i64 = pj_roleof(st, person) 1126 if role == 0 { return 0 - 2 } 1127 let tids: *i64 = st[1] as *i64 1128 let idx: i64 = pj_find(tids, st[0], tid) 1129 if idx < 0 { return 0 - 1 } 1130 if pj_eq(role as *u8, "contractor" as *u8) == 1 { 1131 let owners: *i64 = st[2] as *i64 1132 if pj_eq(owners[idx] as *u8, person) == 0 { return 0 } 1133 } 1134 return 1 1135} 1136 1137// per-task state by id: -1 missing, else pj_state code 1138func pj_task_state(prefix: *u8, pid: *u8, tid: *u8) -> i64 { 1139 let st: *i64 = pj_st_new() 1140 if pj_gather(prefix, pid, st) != 0 { return 0 - 1 } 1141 let tids: *i64 = st[1] as *i64 1142 let idx: i64 = pj_find(tids, st[0], tid) 1143 if idx < 0 { return 0 - 1 } 1144 return pj_state(st, idx) 1145} 1146 1147// open critical-chain minutes to finish task idx (own effort + deepest unfinished dep chain); 1148// -1 = dependency cycle (loud upstream). Done tasks contribute 0. 1149func pj_chain_min(st: *i64, idx: i64, depth: i64) -> i64 { 1150 if depth > st[0] { return 0 - 1 } 1151 if pj_state(st, idx) == 2 { return 0 } 1152 let tids: *i64 = st[1] as *i64 1153 let deps: *i64 = st[3] as *i64 1154 let efforts: *i64 = st[4] as *i64 1155 let dep: *u8 = deps[idx] as *u8 1156 var mx: i64 = 0 1157 var isdash: i64 = 0 1158 if dep[0] == (45 as u8) { if dep[1] == (0 as u8) { isdash = 1 } } 1159 if isdash == 0 { 1160 let nm: *u8 = sys_mmap(128) 1161 var i: i64 = 0 1162 var t: i64 = 0 1163 var go: i64 = 1 1164 while go == 1 { 1165 let c: i64 = dep[i] as i64 1166 var fin: i64 = 0 1167 if c == 44 { fin = 1 } 1168 if c == 0 { fin = 1 } 1169 if fin == 1 { 1170 nm[t] = 0 as u8 1171 if t > 0 { 1172 let di: i64 = pj_find(tids, st[0], nm) 1173 if di >= 0 { 1174 let cm: i64 = pj_chain_min(st, di, depth + 1) 1175 if cm < 0 { return 0 - 1 } 1176 if cm > mx { mx = cm } 1177 } 1178 } 1179 t = 0 1180 if c == 0 { go = 0 } 1181 } else { 1182 if t < 127 { nm[t] = dep[i]; t = t + 1 } 1183 } 1184 i = i + 1 1185 } 1186 } 1187 return efforts[idx] + mx 1188} 1189 1190// ---------------- LEVEL (workload leveling; aply="-" = advisory, aply=<date> = APPLY as ASSIGN records) ---------------- 1191// out[0]=moves, out[1]=members still over capacity after the moves 1192func pj_cmd_level(prefix: *u8, pid: *u8, out: *i64, aply: *u8) -> i64 { 1193 out[0] = 0 1194 out[1] = 0 1195 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1196 var apdays: i64 = 0 - 1 1197 if pj_eq(aply, "-" as *u8) == 0 { 1198 apdays = pj_datedays(aply) 1199 if apdays < 0 { pj_p("PROJECT level apply bad date -- fail loud\n" as *u8); return 1 } 1200 } 1201 let akeys2: *i64 = sys_mmap(8 * 8) as *i64 1202 let avals2: *i64 = sys_mmap(8 * 8) as *i64 1203 akeys2[0] = "kind" as *u8 as i64 1204 akeys2[1] = "pid" as *u8 as i64 1205 akeys2[2] = "tid" as *u8 as i64 1206 akeys2[3] = "person" as *u8 as i64 1207 akeys2[4] = "date" as *u8 as i64 1208 avals2[0] = "asg" as *u8 as i64 1209 let ast: *i64 = sys_mmap(8 * 8) as *i64 1210 if apdays >= 0 { 1211 ast[0] = ss_begin() as i64 1212 ast[1] = sys_mmap(8 * K_MAGIC_2048) 1213 ast[2] = 0 1214 ast[3] = 0 1215 ast[4] = 0 1216 ast[5] = 0 1217 ast[6] = prefix as i64 1218 } 1219 let st: *i64 = pj_st_new() 1220 if pj_gather(prefix, pid, st) != 0 { return 1 } 1221 let rep: *u8 = sys_mmap(K_MAGIC_2048) 1222 if st[12] == 0 { 1223 var o0: i64 = 0 1224 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 1225 o0 = pj_cat(rep, o0, pid) 1226 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 1227 sys_write(1, rep, o0) 1228 return 1 1229 } 1230 let mp: *i64 = st[9] as *i64 1231 let mc: *i64 = st[11] as *i64 1232 let owners: *i64 = st[2] as *i64 1233 let efforts: *i64 = st[4] as *i64 1234 let tids: *i64 = st[1] as *i64 1235 let tst: *i64 = sys_mmap(8 * 128) as *i64 1236 var i: i64 = 0 1237 while i < st[0] { 1238 let sv: i64 = pj_state(st, i) 1239 tst[i] = sv 1240 i = i + 1 1241 } 1242 let loads: *i64 = sys_mmap(8 * 64) as *i64 1243 var m: i64 = 0 1244 while m < st[8] { 1245 var sum: i64 = 0 1246 i = 0 1247 while i < st[0] { 1248 if pj_eq(owners[i] as *u8, mp[m] as *u8) == 1 { if tst[i] != 2 { sum = sum + efforts[i] } } 1249 i = i + 1 1250 } 1251 loads[m] = sum 1252 m = m + 1 1253 } 1254 let moved: *i64 = sys_mmap(8 * 128) as *i64 1255 var moves: i64 = 0 1256 m = 0 1257 while m < st[8] { 1258 var capm: i64 = 0 - 1 1259 if pj_digits(mc[m] as *u8) == 1 { capm = pj_atoi(mc[m] as *u8) } 1260 if capm >= 0 { 1261 var moving: i64 = 1 1262 var giter: i64 = 0 1263 while moving == 1 { 1264 if loads[m] <= capm { moving = 0 } else { 1265 giter = giter + 1 1266 if giter > st[0] + 1 { moving = 0 } else { 1267 var found: i64 = 0 - 1 1268 i = 0 1269 while i < st[0] { 1270 if found < 0 { if moved[i] == 0 { if tst[i] == 0 { if pj_eq(owners[i] as *u8, mp[m] as *u8) == 1 { found = i } } } } 1271 i = i + 1 1272 } 1273 if found < 0 { 1274 var s1: i64 = 0 1275 s1 = pj_cat(rep, s1, "PROJECT-LEVEL-STUCK person=" as *u8) 1276 s1 = pj_cat(rep, s1, mp[m] as *u8) 1277 s1 = pj_cat(rep, s1, " over_min=" as *u8) 1278 s1 = pj_catn(rep, s1, loads[m] - capm) 1279 s1 = pj_cat(rep, s1, " -- no READY task to move (blocked work cannot be leveled)\n" as *u8) 1280 sys_write(1, rep, s1) 1281 moving = 0 1282 } else { 1283 let e: i64 = efforts[found] 1284 var best: i64 = 0 - 1 1285 var besthr: i64 = 0 - 1 1286 var t2: i64 = 0 1287 while t2 < st[8] { 1288 if t2 != m { if pj_digits(mc[t2] as *u8) == 1 { 1289 let hr: i64 = pj_atoi(mc[t2] as *u8) - loads[t2] 1290 if hr >= e { if hr > besthr { best = t2; besthr = hr } } 1291 } } 1292 t2 = t2 + 1 1293 } 1294 if best < 0 { 1295 moved[found] = 1 1296 var s2: i64 = 0 1297 s2 = pj_cat(rep, s2, "PROJECT-LEVEL-NOTARGET tid=" as *u8) 1298 s2 = pj_cat(rep, s2, tids[found] as *u8) 1299 s2 = pj_cat(rep, s2, " -- no member has recorded headroom for it\n" as *u8) 1300 sys_write(1, rep, s2) 1301 } else { 1302 var s3: i64 = 0 1303 s3 = pj_cat(rep, s3, "PROJECT-LEVEL move tid=" as *u8) 1304 s3 = pj_cat(rep, s3, tids[found] as *u8) 1305 s3 = pj_cat(rep, s3, " from=" as *u8) 1306 s3 = pj_cat(rep, s3, mp[m] as *u8) 1307 s3 = pj_cat(rep, s3, " to=" as *u8) 1308 s3 = pj_cat(rep, s3, mp[best] as *u8) 1309 s3 = pj_cat(rep, s3, " effort_min=" as *u8) 1310 s3 = pj_catn(rep, s3, e) 1311 s3 = pj_cat(rep, s3, "\n" as *u8) 1312 sys_write(1, rep, s3) 1313 if apdays >= 0 { 1314 avals2[1] = pid as i64 1315 avals2[2] = tids[found] 1316 avals2[3] = mp[best] 1317 avals2[4] = aply as i64 1318 if pj_put(akeys2, avals2, 5, "asg:" as *u8, ast) != 0 { pj_p("PROJECT level apply writer full -- fail loud\n" as *u8); return 1 } 1319 } 1320 loads[m] = loads[m] - e 1321 loads[best] = loads[best] + e 1322 moved[found] = 1 1323 moves = moves + 1 1324 } 1325 } 1326 } 1327 } 1328 } 1329 } 1330 m = m + 1 1331 } 1332 var over_after: i64 = 0 1333 m = 0 1334 while m < st[8] { 1335 if pj_digits(mc[m] as *u8) == 1 { if loads[m] > pj_atoi(mc[m] as *u8) { over_after = over_after + 1 } } 1336 m = m + 1 1337 } 1338 var z: i64 = 0 1339 z = pj_cat(rep, z, "PROJECT-LEVEL-END moves=" as *u8) 1340 z = pj_catn(rep, z, moves) 1341 z = pj_cat(rep, z, " over_after=" as *u8) 1342 z = pj_catn(rep, z, over_after) 1343 if apdays >= 0 { z = pj_cat(rep, z, " mode=APPLY\n" as *u8) } else { z = pj_cat(rep, z, " mode=advisory (records untouched)\n" as *u8) } 1344 sys_write(1, rep, z) 1345 if apdays >= 0 { 1346 if ast[3] > 0 { 1347 if ss_commit(prefix, ast[0] as *i64, sys_now_us()) != 0 { pj_p("PROJECT level apply commit FAILED -- fail loud\n" as *u8); return 1 } 1348 } 1349 var z2: i64 = 0 1350 z2 = pj_cat(rep, z2, "PROJECT-LEVEL-APPLIED assigns=" as *u8) 1351 z2 = pj_catn(rep, z2, ast[3]) 1352 z2 = pj_cat(rep, z2, " date=" as *u8) 1353 z2 = pj_cat(rep, z2, aply) 1354 z2 = pj_cat(rep, z2, " -- ASSIGN records on the additive plane (latest wins)\n" as *u8) 1355 sys_write(1, rep, z2) 1356 } 1357 out[0] = moves 1358 out[1] = over_after 1359 return 0 1360} 1361 1362// ---------------- RISK (register + delay prediction; duration risk only when WCONF is recorded) ---------------- 1363// out: 0=overdue 1=late 2=tight 3=ok 4=unrated 1364func pj_cmd_risk(prefix: *u8, pid: *u8, today: *u8, out: *i64) -> i64 { 1365 out[0] = 0 1366 out[1] = 0 1367 out[2] = 0 1368 out[3] = 0 1369 out[4] = 0 1370 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1371 let td: i64 = pj_datedays(today) 1372 if td < 0 { pj_p("PROJECT risk bad today date -- fail loud\n" as *u8); return 1 } 1373 let st: *i64 = pj_st_new() 1374 if pj_gather(prefix, pid, st) != 0 { return 1 } 1375 let rep: *u8 = sys_mmap(K_MAGIC_2048) 1376 if st[12] == 0 { 1377 var o0: i64 = 0 1378 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 1379 o0 = pj_cat(rep, o0, pid) 1380 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 1381 sys_write(1, rep, o0) 1382 return 1 1383 } 1384 let tids: *i64 = st[1] as *i64 1385 let dues: *i64 = st[21] as *i64 1386 var i: i64 = 0 1387 while i < st[0] { 1388 let sv: i64 = pj_state(st, i) 1389 if sv != 2 { 1390 let dd: i64 = pj_datedays(dues[i] as *u8) 1391 if dd < 0 { pj_p("PROJECT risk bad stored due date -- fail loud\n" as *u8); return 1 } 1392 let dl: i64 = dd - td 1393 var t: i64 = 0 1394 t = pj_cat(rep, t, "PROJECT-RISK tid=" as *u8) 1395 t = pj_cat(rep, t, tids[i] as *u8) 1396 t = pj_cat(rep, t, " due=" as *u8) 1397 t = pj_cat(rep, t, dues[i] as *u8) 1398 t = pj_cat(rep, t, " days_left=" as *u8) 1399 t = pj_catn(rep, t, dl) 1400 if dl < 0 { 1401 out[0] = out[0] + 1 1402 t = pj_cat(rep, t, " level=OVERDUE" as *u8) 1403 } else { 1404 if st[18] == 1 { 1405 let cm: i64 = pj_chain_min(st, i, 0) 1406 if cm < 0 { pj_p("\nPROJECT-RISK-CYCLE dependency cycle -- fail loud\n" as *u8); return 1 } 1407 let dn: i64 = (cm + st[19] - 1) / st[19] 1408 let slack: i64 = dl - dn 1409 t = pj_cat(rep, t, " chain_min=" as *u8) 1410 t = pj_catn(rep, t, cm) 1411 t = pj_cat(rep, t, " days_needed=" as *u8) 1412 t = pj_catn(rep, t, dn) 1413 t = pj_cat(rep, t, " slack_days=" as *u8) 1414 t = pj_catn(rep, t, slack) 1415 if slack < 0 { 1416 out[1] = out[1] + 1 1417 t = pj_cat(rep, t, " level=LATE" as *u8) 1418 } else { 1419 if slack <= st[20] { 1420 out[2] = out[2] + 1 1421 t = pj_cat(rep, t, " level=TIGHT" as *u8) 1422 } else { 1423 out[3] = out[3] + 1 1424 t = pj_cat(rep, t, " level=OK" as *u8) 1425 } 1426 } 1427 } else { 1428 out[4] = out[4] + 1 1429 t = pj_cat(rep, t, " level=UNRATED (no WCONF min-per-day recorded)" as *u8) 1430 } 1431 } 1432 t = pj_cat(rep, t, "\n" as *u8) 1433 sys_write(1, rep, t) 1434 } 1435 i = i + 1 1436 } 1437 var z: i64 = 0 1438 z = pj_cat(rep, z, "PROJECT-RISK-END overdue=" as *u8) 1439 z = pj_catn(rep, z, out[0]) 1440 z = pj_cat(rep, z, " late=" as *u8) 1441 z = pj_catn(rep, z, out[1]) 1442 z = pj_cat(rep, z, " tight=" as *u8) 1443 z = pj_catn(rep, z, out[2]) 1444 z = pj_cat(rep, z, " ok=" as *u8) 1445 z = pj_catn(rep, z, out[3]) 1446 z = pj_cat(rep, z, " unrated=" as *u8) 1447 z = pj_catn(rep, z, out[4]) 1448 z = pj_cat(rep, z, "\n" as *u8) 1449 sys_write(1, rep, z) 1450 return 0 1451} 1452 1453// ---------------- EVM (budget / cost / earned value; integer permil, no floats) ---------------- 1454// out: 0=scope_done_permil 1=cpi_permil(-1 when no spend) 2=over_budget 1455func pj_cmd_evm(prefix: *u8, pid: *u8, out: *i64) -> i64 { 1456 out[0] = 0 1457 out[1] = 0 - 1 1458 out[2] = 0 1459 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1460 let st: *i64 = pj_st_new() 1461 if pj_gather(prefix, pid, st) != 0 { return 1 } 1462 let rep: *u8 = sys_mmap(K_MAGIC_1024) 1463 if st[12] == 0 { 1464 var o0: i64 = 0 1465 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 1466 o0 = pj_cat(rep, o0, pid) 1467 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 1468 sys_write(1, rep, o0) 1469 return 1 1470 } 1471 if st[15] == 0 { pj_p("PROJECT evm: no BUDGET recorded for this project -- fail loud\n" as *u8); return 1 } 1472 let efforts: *i64 = st[4] as *i64 1473 var total: i64 = 0 1474 var done: i64 = 0 1475 var i: i64 = 0 1476 while i < st[0] { 1477 total = total + efforts[i] 1478 let sv: i64 = pj_state(st, i) 1479 if sv == 2 { done = done + efforts[i] } 1480 i = i + 1 1481 } 1482 if total == 0 { pj_p("PROJECT evm: no tasks recorded -- fail loud\n" as *u8); return 1 } 1483 let scope: i64 = done * 1000 / total 1484 let bac: i64 = st[16] 1485 let ac: i64 = st[17] 1486 let ev: i64 = bac * scope / 1000 1487 var cpi: i64 = 0 - 1 1488 if ac > 0 { cpi = ev * 1000 / ac } 1489 var over: i64 = 0 1490 if ac > bac { over = 1 } 1491 var z: i64 = 0 1492 z = pj_cat(rep, z, "PROJECT-EVM bac_cents=" as *u8) 1493 z = pj_catn(rep, z, bac) 1494 z = pj_cat(rep, z, " ac_cents=" as *u8) 1495 z = pj_catn(rep, z, ac) 1496 z = pj_cat(rep, z, " scope_done_permil=" as *u8) 1497 z = pj_catn(rep, z, scope) 1498 z = pj_cat(rep, z, " earned_value_cents=" as *u8) 1499 z = pj_catn(rep, z, ev) 1500 z = pj_cat(rep, z, " cpi_permil=" as *u8) 1501 if cpi < 0 { z = pj_cat(rep, z, "-" as *u8) } else { z = pj_catn(rep, z, cpi) } 1502 z = pj_cat(rep, z, " over_budget=" as *u8) 1503 z = pj_catn(rep, z, over) 1504 z = pj_cat(rep, z, "\n" as *u8) 1505 sys_write(1, rep, z) 1506 out[0] = scope 1507 out[1] = cpi 1508 out[2] = over 1509 return 0 1510} 1511 1512// ---------------- HTML (organ-emitted 0-JS scoped page; the /project surface) ---------------- 1513// pj_head/pj_foot = the shared APP SHELL at the fleet UI bar (feedback-ui-quality-is-not-presence): 1514// canonical --nx- tokens + dark mode (prefers-color-scheme) + clamp() fluid type + skip-link + 1515// header/nav/main landmarks + :focus-visible + reduced-motion + entrance stagger + tablewrap 1516// (wide tables scroll INSIDE their container -- the page body never scrolls sideways on a phone). 1517func pj_head(hb: *u8, o0: i64, title: *u8) -> i64 { 1518 var o: i64 = o0 1519 o = pj_cat(hb, o, "<" as *u8) 1520 hb[o] = 33 as u8 1521 o = o + 1 1522 o = pj_cat(hb, o, "DOCTYPE html><html lang=\"en\"><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>" as *u8) 1523 o = pj_cat(hb, o, title) 1524 o = pj_cat(hb, o, "</title><style>:root{--nx-color-bg:rgb(247,248,250);--nx-color-surface:rgb(255,255,255);--nx-color-ink:rgb(24,28,35);--nx-color-muted:rgb(95,102,112);--nx-color-line:rgb(216,220,228);--nx-color-accent:rgb(11,84,196);--nx-color-ok:rgb(18,122,62);--nx-color-warn:rgb(176,108,14);--nx-color-bad:rgb(178,38,38);--nx-radius:10px}@media (prefers-color-scheme:dark){:root{--nx-color-bg:rgb(16,18,22);--nx-color-surface:rgb(26,29,35);--nx-color-ink:rgb(232,235,240);--nx-color-muted:rgb(150,157,167);--nx-color-line:rgb(58,63,72);--nx-color-accent:rgb(112,164,248);--nx-color-ok:rgb(96,196,136);--nx-color-warn:rgb(230,174,84);--nx-color-bad:rgb(240,126,126)}}*{box-sizing:border-box}body{font-family:system-ui,sans-serif;background:var(--nx-color-bg);color:var(--nx-color-ink);margin:0;font-size:clamp(15px,1vw + 12px,17px);line-height:1.55}.skip-link{position:absolute;left:-999px;top:0;background:var(--nx-color-accent);color:rgb(255,255,255);padding:12px 16px;border-radius:0 0 8px 0;z-index:9}.skip-link:focus{left:0}.hd{background:linear-gradient(90deg,var(--nx-color-accent),rgb(9,58,136))}.hd nav{display:flex;align-items:center;gap:10px;max-width:1040px;margin:0 auto;padding:8px clamp(12px,4vw,28px)}.hd a{color:rgb(255,255,255);text-decoration:none;padding:10px 14px;border-radius:8px;font-weight:600;min-height:44px;display:inline-flex;align-items:center}.hd a:hover{background:rgba(255,255,255,0.16)}.hd svg{color:rgb(255,255,255)}main{max-width:1040px;margin:0 auto;padding:clamp(14px,3vw,28px)}h1{font-size:clamp(22px,3vw,30px);margin:12px 0 6px}h2{font-size:clamp(16px,2vw,19px);margin:26px 0 8px}.meta{color:var(--nx-color-muted);font-size:13px}.tablewrap{overflow-x:auto;background:var(--nx-color-surface);border:1px solid var(--nx-color-line);border-radius:var(--nx-radius);box-shadow:0 1px 3px rgba(16,20,28,0.08)}table{border-collapse:collapse;width:100%;min-width:640px}td,th{border-bottom:1px solid var(--nx-color-line);padding:10px 12px;text-align:left;font-size:14px}th{color:var(--nx-color-muted);font-weight:600;text-transform:uppercase;font-size:12px;letter-spacing:0.4px}tr:hover td{background:rgba(11,84,196,0.06)}.DONE{color:var(--nx-color-ok);font-weight:700}.BLOCKED{color:var(--nx-color-bad);font-weight:700}.READY{color:var(--nx-color-accent);font-weight:700}.warn{color:var(--nx-color-warn);font-weight:700}@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}main>*{animation:rise 0.5s ease both}main>*:nth-child(2){animation-delay:0.05s}main>*:nth-child(3){animation-delay:0.1s}main>*:nth-child(4){animation-delay:0.15s}main>*:nth-child(5){animation-delay:0.2s}@media (prefers-reduced-motion:reduce){main>*{animation:none}}:focus-visible{outline:3px solid var(--nx-color-accent);outline-offset:2px}.gr{display:flex;align-items:center;margin:6px 0}.gl{width:70px;font-size:12px;color:var(--nx-color-muted)}.gwrap{flex:1;background:var(--nx-color-surface);border:1px solid var(--nx-color-line);border-radius:6px}.gb{height:14px;border-radius:6px}.gbR{background:var(--nx-color-accent)}.gbB{background:var(--nx-color-bad)}.gbD{background:var(--nx-color-ok)}a.card{display:block;padding:16px;margin:10px 0;background:var(--nx-color-surface);border:1px solid var(--nx-color-line);border-radius:var(--nx-radius);color:var(--nx-color-ink);text-decoration:none;box-shadow:0 1px 3px rgba(16,20,28,0.08)}a.card:hover{border-color:var(--nx-color-accent);box-shadow:0 4px 14px rgba(11,84,196,0.15)}@media (max-width:720px){td,th{padding:8px;font-size:13px}.gl{width:52px}}</style></head><body><a class='skip-link' href='" as *u8) 1525 hb[o] = 35 as u8 1526 o = o + 1 1527 o = pj_cat(hb, o, "main'>Skip to content</a><header class='hd'><nav><svg viewBox='0 0 24 24' width='22' height='22' aria-hidden='true'><circle cx='12' cy='12' r='9' fill='none' stroke='currentColor' stroke-width='2'/><path d='M7 12h10M12 7v10' stroke='currentColor' stroke-width='2'/></svg><a href='/project'>Projects</a><a href='/project/demo'>Demo</a></nav></header><main id='main'>" as *u8) 1528 return o 1529} 1530 1531func pj_foot(hb: *u8, o0: i64) -> i64 { 1532 var o: i64 = o0 1533 o = pj_cat(hb, o, "<p class='meta'>Generated by nx_project - sovereign 0-JS - additive never-lose records - access is role-scoped fail-closed</p></main></body></html>" as *u8) 1534 return o 1535} 1536// build the page into caller buffer hb; returns length, or -1 notfound, -2 deny, -3 bad path/date. 1537// SILENT on failure (the gateway daemon chooses the HTTP response); the CLI wrapper prints loud. 1538func pj_html_build(prefix: *u8, pid: *u8, person: *u8, today: *u8, hb: *u8) -> i64 { 1539 if pj_pathok(prefix) == 0 { return 0 - 3 } 1540 let td: i64 = pj_datedays(today) 1541 if td < 0 { return 0 - 3 } 1542 let st: *i64 = pj_st_new() 1543 if pj_gather(prefix, pid, st) != 0 { return 0 - 1 } 1544 if st[12] == 0 { return 0 - 1 } 1545 let role: i64 = pj_roleof(st, person) 1546 if role == 0 { return 0 - 2 } 1547 var own_only: i64 = 0 1548 if pj_eq(role as *u8, "contractor" as *u8) == 1 { own_only = 1 } 1549 let ttl: *u8 = sys_mmap(256) 1550 var to: i64 = 0 1551 to = pj_cat(ttl, to, "Nishi Project - " as *u8) 1552 to = pj_cat(ttl, to, pid) 1553 ttl[to] = 0 as u8 1554 var o: i64 = 0 1555 o = pj_head(hb, o, ttl) 1556 o = pj_cat(hb, o, "<h1>Nishi Project - " as *u8) 1557 o = pj_cat(hb, o, st[14] as *u8) 1558 o = pj_cat(hb, o, "</h1><p class='meta'>project=" as *u8) 1559 o = pj_cat(hb, o, pid) 1560 o = pj_cat(hb, o, " viewer=" as *u8) 1561 o = pj_cat(hb, o, person) 1562 o = pj_cat(hb, o, " role=" as *u8) 1563 o = pj_cat(hb, o, role as *u8) 1564 o = pj_cat(hb, o, " scope=" as *u8) 1565 if own_only == 1 { o = pj_cat(hb, o, "own-tasks-only" as *u8) } else { o = pj_cat(hb, o, "all-tasks" as *u8) } 1566 o = pj_cat(hb, o, " as-of=" as *u8) 1567 o = pj_cat(hb, o, today) 1568 o = pj_cat(hb, o, "</p><h2>Tasks</h2><div class='tablewrap'><table><tr><th>task</th><th>owner</th><th>state</th><th>due</th><th>days left</th><th>risk</th><th>effort min</th><th>title</th></tr>" as *u8) 1569 let tids: *i64 = st[1] as *i64 1570 let owners: *i64 = st[2] as *i64 1571 let titles: *i64 = st[5] as *i64 1572 let efforts: *i64 = st[4] as *i64 1573 let dues: *i64 = st[21] as *i64 1574 var r_ov: i64 = 0 1575 var r_la: i64 = 0 1576 var r_ti: i64 = 0 1577 var r_ok: i64 = 0 1578 var i: i64 = 0 1579 while i < st[0] { 1580 var vis: i64 = 1 1581 if own_only == 1 { if pj_eq(owners[i] as *u8, person) == 0 { vis = 0 } } 1582 if vis == 1 { 1583 let sv: i64 = pj_state(st, i) 1584 let sn: i64 = pj_statename(sv) 1585 o = pj_cat(hb, o, "<tr><td>" as *u8) 1586 o = pj_cat(hb, o, tids[i] as *u8) 1587 o = pj_cat(hb, o, "</td><td>" as *u8) 1588 o = pj_cat(hb, o, owners[i] as *u8) 1589 o = pj_cat(hb, o, "</td><td class='" as *u8) 1590 o = pj_cat(hb, o, sn as *u8) 1591 o = pj_cat(hb, o, "'>" as *u8) 1592 o = pj_cat(hb, o, sn as *u8) 1593 o = pj_cat(hb, o, "</td><td>" as *u8) 1594 o = pj_cat(hb, o, dues[i] as *u8) 1595 o = pj_cat(hb, o, "</td><td>" as *u8) 1596 let dd: i64 = pj_datedays(dues[i] as *u8) 1597 if dd >= 0 { o = pj_catn(hb, o, dd - td) } else { o = pj_cat(hb, o, "-" as *u8) } 1598 o = pj_cat(hb, o, "</td><td>" as *u8) 1599 // risk cell: OVERDUE by pure dates; LATE/TIGHT/OK by chain-slack when WCONF is recorded 1600 var rlvl: i64 = 0 - 1 1601 if sv != 2 { if dd >= 0 { 1602 let dl2: i64 = dd - td 1603 if dl2 < 0 { rlvl = 3 } else { 1604 if st[18] == 1 { 1605 let cm: i64 = pj_chain_min(st, i, 0) 1606 if cm < 0 { rlvl = 4 } else { 1607 let dnd: i64 = (cm + st[19] - 1) / st[19] 1608 let sl: i64 = dl2 - dnd 1609 if sl < 0 { rlvl = 2 } else { 1610 if sl <= st[20] { rlvl = 1 } else { rlvl = 0 } 1611 } 1612 } 1613 } 1614 } 1615 } } 1616 if rlvl == 0 { r_ok = r_ok + 1; o = pj_cat(hb, o, "OK" as *u8) } 1617 if rlvl == 1 { r_ti = r_ti + 1; o = pj_cat(hb, o, "<span class='warn'>TIGHT</span>" as *u8) } 1618 if rlvl == 2 { r_la = r_la + 1; o = pj_cat(hb, o, "<span class='BLOCKED'>LATE</span>" as *u8) } 1619 if rlvl == 3 { r_ov = r_ov + 1; o = pj_cat(hb, o, "<span class='BLOCKED'>OVERDUE</span>" as *u8) } 1620 if rlvl == 4 { o = pj_cat(hb, o, "<span class='BLOCKED'>CYCLE</span>" as *u8) } 1621 if rlvl < 0 { o = pj_cat(hb, o, "-" as *u8) } 1622 o = pj_cat(hb, o, "</td><td>" as *u8) 1623 o = pj_catn(hb, o, efforts[i]) 1624 o = pj_cat(hb, o, "</td><td>" as *u8) 1625 o = pj_cat(hb, o, titles[i] as *u8) 1626 o = pj_cat(hb, o, "</td></tr>" as *u8) 1627 } 1628 i = i + 1 1629 } 1630 if st[18] == 1 { 1631 o = pj_cat(hb, o, "</table></div><p class='meta'>risk: overdue=" as *u8) 1632 o = pj_catn(hb, o, r_ov) 1633 o = pj_cat(hb, o, " late=" as *u8) 1634 o = pj_catn(hb, o, r_la) 1635 o = pj_cat(hb, o, " tight=" as *u8) 1636 o = pj_catn(hb, o, r_ti) 1637 o = pj_cat(hb, o, " ok=" as *u8) 1638 o = pj_catn(hb, o, r_ok) 1639 o = pj_cat(hb, o, " (chain-slack vs WCONF " as *u8) 1640 o = pj_catn(hb, o, st[19]) 1641 o = pj_cat(hb, o, " min/day, tight band " as *u8) 1642 o = pj_catn(hb, o, st[20]) 1643 o = pj_cat(hb, o, " days)</p><h2>Workload</h2><div class='tablewrap'><table><tr><th>person</th><th>role</th><th>open min</th><th>cap min</th><th>over</th></tr>" as *u8) 1644 } else { 1645 o = pj_cat(hb, o, "</table></div><h2>Workload</h2><div class='tablewrap'><table><tr><th>person</th><th>role</th><th>open min</th><th>cap min</th><th>over</th></tr>" as *u8) 1646 } 1647 let mp: *i64 = st[9] as *i64 1648 let mr: *i64 = st[10] as *i64 1649 let mc: *i64 = st[11] as *i64 1650 var m: i64 = 0 1651 while m < st[8] { 1652 var sum: i64 = 0 1653 i = 0 1654 while i < st[0] { 1655 if pj_eq(owners[i] as *u8, mp[m] as *u8) == 1 { 1656 let sv2: i64 = pj_state(st, i) 1657 if sv2 != 2 { sum = sum + efforts[i] } 1658 } 1659 i = i + 1 1660 } 1661 o = pj_cat(hb, o, "<tr><td>" as *u8) 1662 o = pj_cat(hb, o, mp[m] as *u8) 1663 o = pj_cat(hb, o, "</td><td>" as *u8) 1664 o = pj_cat(hb, o, mr[m] as *u8) 1665 o = pj_cat(hb, o, "</td><td>" as *u8) 1666 o = pj_catn(hb, o, sum) 1667 o = pj_cat(hb, o, "</td><td>" as *u8) 1668 o = pj_cat(hb, o, mc[m] as *u8) 1669 o = pj_cat(hb, o, "</td><td>" as *u8) 1670 var overm: i64 = 0 - 1 1671 if pj_digits(mc[m] as *u8) == 1 { 1672 overm = 0 1673 if sum > pj_atoi(mc[m] as *u8) { overm = 1 } 1674 } 1675 if overm == 1 { o = pj_cat(hb, o, "<span class='BLOCKED'>over</span>" as *u8) } 1676 if overm == 0 { o = pj_cat(hb, o, "within" as *u8) } 1677 if overm < 0 { o = pj_cat(hb, o, "-" as *u8) } 1678 o = pj_cat(hb, o, "</td></tr>" as *u8) 1679 m = m + 1 1680 } 1681 o = pj_cat(hb, o, "</table></div>" as *u8) 1682 if st[15] == 1 { 1683 let efforts2: *i64 = st[4] as *i64 1684 var total: i64 = 0 1685 var done: i64 = 0 1686 i = 0 1687 while i < st[0] { 1688 total = total + efforts2[i] 1689 let sv3: i64 = pj_state(st, i) 1690 if sv3 == 2 { done = done + efforts2[i] } 1691 i = i + 1 1692 } 1693 if total > 0 { 1694 let scope: i64 = done * 1000 / total 1695 let ev: i64 = st[16] * scope / 1000 1696 o = pj_cat(hb, o, "<h2>Budget</h2><p>budget cents=" as *u8) 1697 o = pj_catn(hb, o, st[16]) 1698 o = pj_cat(hb, o, " spent cents=" as *u8) 1699 o = pj_catn(hb, o, st[17]) 1700 o = pj_cat(hb, o, " scope done permil=" as *u8) 1701 o = pj_catn(hb, o, scope) 1702 o = pj_cat(hb, o, " earned value cents=" as *u8) 1703 o = pj_catn(hb, o, ev) 1704 if st[17] > st[16] { o = pj_cat(hb, o, " <span class='BLOCKED'>OVER BUDGET</span>" as *u8) } 1705 o = pj_cat(hb, o, "</p>" as *u8) 1706 } 1707 } 1708 // ---- Schedule (latest-safe windows): bar = own-effort days ending at due; needs WCONF ---- 1709 if st[18] == 1 { if st[0] > 0 { 1710 let gs: *i64 = sys_mmap(8 * 128) as *i64 1711 let gw: *i64 = sys_mmap(8 * 128) as *i64 1712 let gv: *i64 = sys_mmap(8 * 128) as *i64 1713 var minS: i64 = 0 1714 var maxD: i64 = 0 1715 var haveg: i64 = 0 1716 var gi: i64 = 0 1717 while gi < st[0] { 1718 gv[gi] = 0 1719 var visg: i64 = 1 1720 if own_only == 1 { if pj_eq(owners[gi] as *u8, person) == 0 { visg = 0 } } 1721 if visg == 1 { 1722 let gdd: i64 = pj_datedays(dues[gi] as *u8) 1723 if gdd >= 0 { 1724 var gdn: i64 = (efforts[gi] + st[19] - 1) / st[19] 1725 if gdn < 1 { gdn = 1 } 1726 let gst2: i64 = gdd - gdn 1727 gs[gi] = gst2 1728 gw[gi] = gdn 1729 gv[gi] = 1 1730 if haveg == 0 { minS = gst2; maxD = gdd; haveg = 1 } else { 1731 if gst2 < minS { minS = gst2 } 1732 if gdd > maxD { maxD = gdd } 1733 } 1734 } 1735 } 1736 gi = gi + 1 1737 } 1738 if haveg == 1 { 1739 var rng: i64 = maxD - minS 1740 if rng < 1 { rng = 1 } 1741 o = pj_cat(hb, o, "<h2>Schedule (latest-safe windows)</h2>" as *u8) 1742 gi = 0 1743 while gi < st[0] { 1744 if gv[gi] == 1 { 1745 let svg: i64 = pj_state(st, gi) 1746 let lpct: i64 = (gs[gi] - minS) * 100 / rng 1747 var wpct: i64 = gw[gi] * 100 / rng 1748 if wpct < 2 { wpct = 2 } 1749 if lpct + wpct > 100 { wpct = 100 - lpct } 1750 if wpct < 1 { wpct = 1 } 1751 o = pj_cat(hb, o, "<div class='gr'><span class='gl'>" as *u8) 1752 o = pj_cat(hb, o, tids[gi] as *u8) 1753 o = pj_cat(hb, o, "</span><div class='gwrap'><div class='gb " as *u8) 1754 if svg == 2 { o = pj_cat(hb, o, "gbD" as *u8) } 1755 if svg == 1 { o = pj_cat(hb, o, "gbB" as *u8) } 1756 if svg == 0 { o = pj_cat(hb, o, "gbR" as *u8) } 1757 o = pj_cat(hb, o, "' style='margin-left:" as *u8) 1758 o = pj_catn(hb, o, lpct) 1759 o = pj_cat(hb, o, "%;width:" as *u8) 1760 o = pj_catn(hb, o, wpct) 1761 o = pj_cat(hb, o, "%'></div></div></div>" as *u8) 1762 } 1763 gi = gi + 1 1764 } 1765 o = pj_cat(hb, o, "<p class='meta'>bars = own-effort window ending at due (WCONF " as *u8) 1766 o = pj_catn(hb, o, st[19]) 1767 o = pj_cat(hb, o, " min/day) - blue ready, red blocked, green done</p>" as *u8) 1768 } 1769 } } 1770 // ---- Timesheet (logged minutes; contractor sees own logs only) ---- 1771 if st[22] > 0 { 1772 let lp2: *i64 = st[24] as *i64 1773 let lm2: *i64 = st[25] as *i64 1774 var hdr2: i64 = 0 1775 let seen2: *i64 = sys_mmap(8 * 256) as *i64 1776 var ns2: i64 = 0 1777 var li: i64 = 0 1778 while li < st[22] { 1779 let lper: *u8 = lp2[li] as *u8 1780 var okp: i64 = 1 1781 if own_only == 1 { if pj_eq(lper, person) == 0 { okp = 0 } } 1782 if okp == 1 { if pj_find(seen2, ns2, lper) < 0 { 1783 if hdr2 == 0 { 1784 o = pj_cat(hb, o, "<h2>Timesheet</h2><div class='tablewrap'><table><tr><th>person</th><th>logged min</th></tr>" as *u8) 1785 hdr2 = 1 1786 } 1787 seen2[ns2] = lper as i64 1788 ns2 = ns2 + 1 1789 var lsum: i64 = 0 1790 var lk: i64 = 0 1791 while lk < st[22] { 1792 if pj_eq(lp2[lk] as *u8, lper) == 1 { lsum = lsum + lm2[lk] } 1793 lk = lk + 1 1794 } 1795 o = pj_cat(hb, o, "<tr><td>" as *u8) 1796 o = pj_cat(hb, o, lper) 1797 o = pj_cat(hb, o, "</td><td>" as *u8) 1798 o = pj_catn(hb, o, lsum) 1799 o = pj_cat(hb, o, "</td></tr>" as *u8) 1800 } } 1801 li = li + 1 1802 } 1803 if hdr2 == 1 { o = pj_cat(hb, o, "</table></div>" as *u8) } 1804 } 1805 // ---- Notes (task discussion; a note is visible only when its TASK is visible) ---- 1806 if st[26] > 0 { 1807 let nt2: *i64 = st[27] as *i64 1808 let np2: *i64 = st[28] as *i64 1809 let nx2: *i64 = st[29] as *i64 1810 let nd2: *i64 = st[30] as *i64 1811 var hdr3: i64 = 0 1812 var ni: i64 = 0 1813 while ni < st[26] { 1814 var okn: i64 = 1 1815 if own_only == 1 { 1816 let ti2: i64 = pj_find(tids, st[0], nt2[ni] as *u8) 1817 if ti2 < 0 { okn = 0 } else { if pj_eq(owners[ti2] as *u8, person) == 0 { okn = 0 } } 1818 } 1819 if okn == 1 { 1820 if hdr3 == 0 { o = pj_cat(hb, o, "<h2>Notes</h2>" as *u8); hdr3 = 1 } 1821 o = pj_cat(hb, o, "<p class='meta'><b>" as *u8) 1822 o = pj_cat(hb, o, nt2[ni] as *u8) 1823 o = pj_cat(hb, o, "</b> " as *u8) 1824 o = pj_cat(hb, o, np2[ni] as *u8) 1825 o = pj_cat(hb, o, " " as *u8) 1826 o = pj_cat(hb, o, nd2[ni] as *u8) 1827 o = pj_cat(hb, o, " - " as *u8) 1828 o = pj_cat(hb, o, nx2[ni] as *u8) 1829 o = pj_cat(hb, o, "</p>" as *u8) 1830 } 1831 ni = ni + 1 1832 } 1833 } 1834 o = pj_foot(hb, o) 1835 return o 1836} 1837 1838// CLI wrapper: build + write to outfile (or - for stdout); loud on failure (the MCP/CLI surface) 1839func pj_html(prefix: *u8, pid: *u8, person: *u8, today: *u8, outpath: *u8) -> i64 { 1840 let hb: *u8 = sys_mmap(K_MAGIC_65536) 1841 let n: i64 = pj_html_build(prefix, pid, person, today, hb) 1842 if n == 0 - 3 { pj_p("PROJECT html bad path or today date -- fail loud\n" as *u8); return 1 } 1843 if n == 0 - 2 { pj_p("PROJECT-DENY -- not a member\n" as *u8); return 2 } 1844 if n < 0 { pj_p("PROJECT-NOTFOUND -- fail loud\n" as *u8); return 1 } 1845 var fd: i64 = 1 1846 var opened: i64 = 0 1847 var isdash: i64 = 0 1848 if outpath[0] == (45 as u8) { if outpath[1] == (0 as u8) { isdash = 1 } } 1849 if isdash == 0 { 1850 let f2: i64 = sys_openat_wr(outpath, 0x1a4) 1851 if f2 < 0 { pj_p("PROJECT html cannot open outfile -- fail loud\n" as *u8); return 1 } 1852 fd = f2 1853 opened = 1 1854 } 1855 sys_write(fd, hb, n) 1856 if opened == 1 { sys_close(fd) } 1857 return 0 1858} 1859 1860// epoch-days -> civil (Howard Hinnant); out = y,m,d 1861func pj_civil(z0: i64, out: *i64) -> i64 { 1862 var z: i64 = z0 + K_MAGIC_719468 1863 var era: i64 = z / K_MAGIC_146097 1864 if z < 0 { era = (z - K_MAGIC_146096) / K_MAGIC_146097 } 1865 let doe: i64 = z - era * K_MAGIC_146097 1866 let yoe: i64 = (doe - doe / K_MAGIC_1460 + doe / K_MAGIC_36524 - doe / K_MAGIC_146096) / 365 1867 let y0: i64 = yoe + era * 400 1868 let doy: i64 = doe - (365 * yoe + yoe / 4 - yoe / 100) 1869 let mp: i64 = (5 * doy + 2) / 153 1870 let d: i64 = doy - (153 * mp + 2) / 5 + 1 1871 var m: i64 = mp + 3 1872 if mp >= 10 { m = mp - 9 } 1873 var y: i64 = y0 1874 if m <= 2 { y = y0 + 1 } 1875 out[0] = y 1876 out[1] = m 1877 out[2] = d 1878 return 0 1879} 1880 1881// epoch-days -> "YYYY-MM-DD" into out; returns length 1882func pj_fmtdate(days: i64, out: *u8) -> i64 { 1883 let ymd: *i64 = sys_mmap(32) as *i64 1884 pj_civil(days, ymd) 1885 var o: i64 = 0 1886 o = pj_catn(out, o, ymd[0]) 1887 out[o] = 45 as u8 1888 o = o + 1 1889 if ymd[1] < 10 { out[o] = 48 as u8; o = o + 1 } 1890 o = pj_catn(out, o, ymd[1]) 1891 out[o] = 45 as u8 1892 o = o + 1 1893 if ymd[2] < 10 { out[o] = 48 as u8; o = o + 1 } 1894 o = pj_catn(out, o, ymd[2]) 1895 out[o] = 0 as u8 1896 return o 1897} 1898 1899// ---------------- TIMESHEET (logged minutes per person; out: 0=total 1=persons-with-logs) ---------------- 1900func pj_cmd_timesheet(prefix: *u8, pid: *u8, out: *i64) -> i64 { 1901 out[0] = 0 1902 out[1] = 0 1903 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1904 let st: *i64 = pj_st_new() 1905 if pj_gather(prefix, pid, st) != 0 { return 1 } 1906 let rep: *u8 = sys_mmap(K_MAGIC_2048) 1907 if st[12] == 0 { 1908 var o0: i64 = 0 1909 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 1910 o0 = pj_cat(rep, o0, pid) 1911 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 1912 sys_write(1, rep, o0) 1913 return 1 1914 } 1915 let lp: *i64 = st[24] as *i64 1916 let lm: *i64 = st[25] as *i64 1917 let seen: *i64 = sys_mmap(8 * 256) as *i64 1918 var nseen: i64 = 0 1919 var total: i64 = 0 1920 var i: i64 = 0 1921 while i < st[22] { total = total + lm[i]; i = i + 1 } 1922 i = 0 1923 while i < st[22] { 1924 let person: *u8 = lp[i] as *u8 1925 if pj_find(seen, nseen, person) < 0 { 1926 seen[nseen] = person as i64 1927 nseen = nseen + 1 1928 var sum: i64 = 0 1929 var k: i64 = 0 1930 while k < st[22] { 1931 if pj_eq(lp[k] as *u8, person) == 1 { sum = sum + lm[k] } 1932 k = k + 1 1933 } 1934 var t: i64 = 0 1935 t = pj_cat(rep, t, "PROJECT-TIME person=" as *u8) 1936 t = pj_cat(rep, t, person) 1937 t = pj_cat(rep, t, " logged_min=" as *u8) 1938 t = pj_catn(rep, t, sum) 1939 t = pj_cat(rep, t, "\n" as *u8) 1940 sys_write(1, rep, t) 1941 } 1942 i = i + 1 1943 } 1944 var z: i64 = 0 1945 z = pj_cat(rep, z, "PROJECT-TIME-END persons=" as *u8) 1946 z = pj_catn(rep, z, nseen) 1947 z = pj_cat(rep, z, " total_min=" as *u8) 1948 z = pj_catn(rep, z, total) 1949 z = pj_cat(rep, z, "\n" as *u8) 1950 sys_write(1, rep, z) 1951 out[0] = total 1952 out[1] = nseen 1953 return 0 1954} 1955 1956// ---------------- NOTES (task discussion; out: 0=count shown) ---------------- 1957func pj_cmd_notes(prefix: *u8, pid: *u8, tid: *u8, out: *i64) -> i64 { 1958 out[0] = 0 1959 if pj_pathok(prefix) == 0 { return pj_pathfail() } 1960 let st: *i64 = pj_st_new() 1961 if pj_gather(prefix, pid, st) != 0 { return 1 } 1962 if st[12] == 0 { pj_p("PROJECT-NOTFOUND -- fail loud\n" as *u8); return 1 } 1963 let tids: *i64 = st[1] as *i64 1964 if pj_find(tids, st[0], tid) < 0 { pj_p("PROJECT notes: no such task -- fail loud\n" as *u8); return 1 } 1965 let rep: *u8 = sys_mmap(K_MAGIC_2048) 1966 let nt: *i64 = st[27] as *i64 1967 let np: *i64 = st[28] as *i64 1968 let nx: *i64 = st[29] as *i64 1969 let nd: *i64 = st[30] as *i64 1970 var shown: i64 = 0 1971 var i: i64 = 0 1972 while i < st[26] { 1973 if pj_eq(nt[i] as *u8, tid) == 1 { 1974 var t: i64 = 0 1975 t = pj_cat(rep, t, "PROJECT-NOTE tid=" as *u8) 1976 t = pj_cat(rep, t, tid) 1977 t = pj_cat(rep, t, " person=" as *u8) 1978 t = pj_cat(rep, t, np[i] as *u8) 1979 t = pj_cat(rep, t, " date=" as *u8) 1980 t = pj_cat(rep, t, nd[i] as *u8) 1981 t = pj_cat(rep, t, " text=" as *u8) 1982 t = pj_cat(rep, t, nx[i] as *u8) 1983 t = pj_cat(rep, t, "\n" as *u8) 1984 sys_write(1, rep, t) 1985 shown = shown + 1 1986 } 1987 i = i + 1 1988 } 1989 var z: i64 = 0 1990 z = pj_cat(rep, z, "PROJECT-NOTES-END tid=" as *u8) 1991 z = pj_cat(rep, z, tid) 1992 z = pj_cat(rep, z, " count=" as *u8) 1993 z = pj_catn(rep, z, shown) 1994 z = pj_cat(rep, z, "\n" as *u8) 1995 sys_write(1, rep, z) 1996 out[0] = shown 1997 return 0 1998} 1999 2000// ---------------- TEMPLATE (blueprint -> dated TASK/MILE file the planner loads) ---------------- 2001// tpl rows: TPLTASK <tid> <owner> <offset-days> <effort> <imp> <deps|-> <title> 2002// TPLMILE <mid> <offset-days> <title> 2003func pj_cmd_template(argc: i64, argv: *i64) -> i64 { 2004 if argc < 6 { pj_p("PROJECT template needs <tplfile> <pid> <startdate> <outfile> -- fail loud\n" as *u8); return 1 } 2005 let tplf: *u8 = argv[2] as *u8 2006 let pid: *u8 = argv[3] as *u8 2007 let start: *u8 = argv[4] as *u8 2008 let outf: *u8 = argv[5] as *u8 2009 if pj_pathok(tplf) == 0 { return pj_pathfail() } 2010 if pj_pathok(outf) == 0 { return pj_pathfail() } 2011 let sd: i64 = pj_datedays(start) 2012 if sd < 0 { pj_p("PROJECT template bad start date -- fail loud\n" as *u8); return 1 } 2013 let szp: *i64 = sys_mmap(16) as *i64 2014 let b: *u8 = ss_readall(tplf, szp) 2015 let sz: i64 = szp[0] 2016 if sz <= 0 { pj_p("PROJECT template file MISSING/EMPTY -- fail loud\n" as *u8); return 1 } 2017 let ob: *u8 = sys_mmap(K_MAGIC_65536) 2018 var oo: i64 = 0 2019 let t0: *u8 = sys_mmap(128) 2020 let g1: *u8 = sys_mmap(128) 2021 let g2: *u8 = sys_mmap(128) 2022 let g3: *u8 = sys_mmap(128) 2023 let g4: *u8 = sys_mmap(128) 2024 let g5: *u8 = sys_mmap(128) 2025 let g6: *u8 = sys_mmap(256) 2026 let g7: *u8 = sys_mmap(256) 2027 let dbuf: *u8 = sys_mmap(32) 2028 var rows: i64 = 0 2029 var i: i64 = 0 2030 while i < sz { 2031 var e: i64 = i 2032 var go: i64 = 1 2033 while go == 1 { 2034 if e >= sz { go = 0 } else { 2035 if b[e] == (10 as u8) { go = 0 } else { e = e + 1 } 2036 } 2037 } 2038 var p: i64 = pj_tok(b, i, e, t0, 128) 2039 if t0[0] == (35 as u8) { t0[0] = 0 as u8 } 2040 if pj_eq(t0, "TPLTASK" as *u8) == 1 { 2041 p = pj_tok(b, p, e, g1, 128) 2042 p = pj_tok(b, p, e, g2, 128) 2043 p = pj_tok(b, p, e, g3, 128) 2044 p = pj_tok(b, p, e, g4, 128) 2045 p = pj_tok(b, p, e, g5, 128) 2046 p = pj_tok(b, p, e, g6, 256) 2047 p = pj_tok(b, p, e, g7, 256) 2048 if g1[0] == (0 as u8) { pj_p("PROJECT template TPLTASK missing tid -- fail loud\n" as *u8); return 1 } 2049 if g2[0] == (0 as u8) { pj_p("PROJECT template TPLTASK missing owner -- fail loud\n" as *u8); return 1 } 2050 if pj_digits(g3) == 0 { pj_p("PROJECT template TPLTASK offset must be digits -- fail loud\n" as *u8); return 1 } 2051 if pj_digits(g4) == 0 { pj_p("PROJECT template TPLTASK effort must be digits -- fail loud\n" as *u8); return 1 } 2052 if pj_digits(g5) == 0 { pj_p("PROJECT template TPLTASK imp must be digits -- fail loud\n" as *u8); return 1 } 2053 if g6[0] == (0 as u8) { pj_p("PROJECT template TPLTASK missing deps -- fail loud\n" as *u8); return 1 } 2054 if g7[0] == (0 as u8) { pj_p("PROJECT template TPLTASK missing title -- fail loud\n" as *u8); return 1 } 2055 pj_fmtdate(sd + pj_atoi(g3), dbuf) 2056 oo = pj_cat(ob, oo, "TASK " as *u8) 2057 oo = pj_cat(ob, oo, pid) 2058 ob[oo] = 32 as u8 2059 oo = oo + 1 2060 oo = pj_cat(ob, oo, g1) 2061 ob[oo] = 32 as u8 2062 oo = oo + 1 2063 oo = pj_cat(ob, oo, g2) 2064 ob[oo] = 32 as u8 2065 oo = oo + 1 2066 oo = pj_cat(ob, oo, dbuf) 2067 ob[oo] = 32 as u8 2068 oo = oo + 1 2069 oo = pj_cat(ob, oo, g4) 2070 ob[oo] = 32 as u8 2071 oo = oo + 1 2072 oo = pj_cat(ob, oo, g5) 2073 ob[oo] = 32 as u8 2074 oo = oo + 1 2075 oo = pj_cat(ob, oo, g6) 2076 ob[oo] = 32 as u8 2077 oo = oo + 1 2078 oo = pj_cat(ob, oo, g7) 2079 oo = pj_cat(ob, oo, "\n" as *u8) 2080 rows = rows + 1 2081 } 2082 if pj_eq(t0, "TPLMILE" as *u8) == 1 { 2083 p = pj_tok(b, p, e, g1, 128) 2084 p = pj_tok(b, p, e, g3, 128) 2085 p = pj_tok(b, p, e, g7, 256) 2086 if g1[0] == (0 as u8) { pj_p("PROJECT template TPLMILE missing mid -- fail loud\n" as *u8); return 1 } 2087 if pj_digits(g3) == 0 { pj_p("PROJECT template TPLMILE offset must be digits -- fail loud\n" as *u8); return 1 } 2088 if g7[0] == (0 as u8) { pj_p("PROJECT template TPLMILE missing title -- fail loud\n" as *u8); return 1 } 2089 pj_fmtdate(sd + pj_atoi(g3), dbuf) 2090 oo = pj_cat(ob, oo, "MILE " as *u8) 2091 oo = pj_cat(ob, oo, pid) 2092 ob[oo] = 32 as u8 2093 oo = oo + 1 2094 oo = pj_cat(ob, oo, g1) 2095 ob[oo] = 32 as u8 2096 oo = oo + 1 2097 oo = pj_cat(ob, oo, dbuf) 2098 ob[oo] = 32 as u8 2099 oo = oo + 1 2100 oo = pj_cat(ob, oo, g7) 2101 oo = pj_cat(ob, oo, "\n" as *u8) 2102 rows = rows + 1 2103 } 2104 i = e + 1 2105 } 2106 if rows == 0 { pj_p("PROJECT template: no TPLTASK/TPLMILE rows -- fail loud\n" as *u8); return 1 } 2107 if pj_wfile(outf, ob) != 0 { pj_p("PROJECT template cannot write outfile -- fail loud\n" as *u8); return 1 } 2108 let rep: *u8 = sys_mmap(256) 2109 var z: i64 = 0 2110 z = pj_cat(rep, z, "PROJECT-TEMPLATE rows=" as *u8) 2111 z = pj_catn(rep, z, rows) 2112 z = pj_cat(rep, z, " start=" as *u8) 2113 z = pj_cat(rep, z, start) 2114 z = pj_cat(rep, z, " -- load the outfile with the planner to instantiate\n" as *u8) 2115 sys_write(1, rep, z) 2116 return 0 2117} 2118 2119// ---------------- SNAPSHOT (write a BASELINE = each task's current due+effort, dated) ---------------- 2120func pj_cmd_snapshot(prefix: *u8, pid: *u8, snapdate: *u8, outfile: *u8) -> i64 { 2121 if pj_pathok(prefix) == 0 { return pj_pathfail() } 2122 if pj_pathok(outfile) == 0 { return pj_pathfail() } 2123 if pj_datedays(snapdate) < 0 { pj_p("PROJECT snapshot bad date -- fail loud\n" as *u8); return 1 } 2124 let st: *i64 = pj_st_new() 2125 if pj_gather(prefix, pid, st) != 0 { return 1 } 2126 if st[12] == 0 { pj_p("PROJECT-NOTFOUND -- fail loud\n" as *u8); return 1 } 2127 let tids: *i64 = st[1] as *i64 2128 let dues: *i64 = st[21] as *i64 2129 let efforts: *i64 = st[4] as *i64 2130 let ob: *u8 = sys_mmap(K_MAGIC_65536) 2131 var o: i64 = 0 2132 var i: i64 = 0 2133 while i < st[0] { 2134 o = pj_cat(ob, o, "BASE " as *u8) 2135 o = pj_cat(ob, o, pid) 2136 ob[o] = 32 as u8 2137 o = o + 1 2138 o = pj_cat(ob, o, tids[i] as *u8) 2139 ob[o] = 32 as u8 2140 o = o + 1 2141 o = pj_cat(ob, o, dues[i] as *u8) 2142 ob[o] = 32 as u8 2143 o = o + 1 2144 o = pj_catn(ob, o, efforts[i]) 2145 ob[o] = 32 as u8 2146 o = o + 1 2147 o = pj_cat(ob, o, snapdate) 2148 o = pj_cat(ob, o, "\n" as *u8) 2149 i = i + 1 2150 } 2151 ob[o] = 0 as u8 2152 if pj_wfile(outfile, ob) != 0 { pj_p("PROJECT snapshot cannot write -- fail loud\n" as *u8); return 1 } 2153 let rep: *u8 = sys_mmap(256) 2154 var z: i64 = 0 2155 z = pj_cat(rep, z, "PROJECT-SNAPSHOT tasks=" as *u8) 2156 z = pj_catn(rep, z, st[0]) 2157 z = pj_cat(rep, z, " date=" as *u8) 2158 z = pj_cat(rep, z, snapdate) 2159 z = pj_cat(rep, z, " -- load the outfile to record the baseline (BASE records, additive)\n" as *u8) 2160 sys_write(1, rep, z) 2161 return 0 2162} 2163 2164// ---------------- BASELINE VARIANCE (current plan vs the recorded baseline) ---------------- 2165// out: 0=tasks-with-baseline 1=total-slip-days 2=tasks-slipped 3=tasks-no-baseline 2166func pj_cmd_baseline(prefix: *u8, pid: *u8, out: *i64) -> i64 { 2167 out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0 2168 if pj_pathok(prefix) == 0 { return pj_pathfail() } 2169 let st: *i64 = pj_st_new() 2170 if pj_gather(prefix, pid, st) != 0 { return 1 } 2171 if st[12] == 0 { pj_p("PROJECT-NOTFOUND -- fail loud\n" as *u8); return 1 } 2172 if st[36] == 0 { pj_p("PROJECT baseline: none recorded (run snapshot first) -- fail loud\n" as *u8); return 1 } 2173 let rep: *u8 = sys_mmap(K_MAGIC_2048) 2174 let tids: *i64 = st[1] as *i64 2175 let dues: *i64 = st[21] as *i64 2176 let efforts: *i64 = st[4] as *i64 2177 let bt: *i64 = st[37] as *i64 2178 let bd: *i64 = st[38] as *i64 2179 let be: *i64 = st[39] as *i64 2180 var i: i64 = 0 2181 while i < st[0] { 2182 let bi: i64 = pj_find(bt, st[36], tids[i] as *u8) 2183 if bi < 0 { 2184 out[3] = out[3] + 1 2185 } else { 2186 let curdue: i64 = pj_datedays(dues[i] as *u8) 2187 let slip: i64 = curdue - bd[bi] 2188 let edelta: i64 = efforts[i] - be[bi] 2189 out[0] = out[0] + 1 2190 out[1] = out[1] + slip 2191 if slip > 0 { out[2] = out[2] + 1 } 2192 var t: i64 = 0 2193 t = pj_cat(rep, t, "PROJECT-BASELINE tid=" as *u8) 2194 t = pj_cat(rep, t, tids[i] as *u8) 2195 t = pj_cat(rep, t, " due_slip_days=" as *u8) 2196 t = pj_catn(rep, t, slip) 2197 t = pj_cat(rep, t, " effort_delta_min=" as *u8) 2198 t = pj_catn(rep, t, edelta) 2199 if slip > 0 { t = pj_cat(rep, t, " SLIPPED" as *u8) } 2200 t = pj_cat(rep, t, "\n" as *u8) 2201 sys_write(1, rep, t) 2202 } 2203 i = i + 1 2204 } 2205 var z: i64 = 0 2206 z = pj_cat(rep, z, "PROJECT-BASELINE-END baselined=" as *u8) 2207 z = pj_catn(rep, z, out[0]) 2208 z = pj_cat(rep, z, " total_slip_days=" as *u8) 2209 z = pj_catn(rep, z, out[1]) 2210 z = pj_cat(rep, z, " slipped=" as *u8) 2211 z = pj_catn(rep, z, out[2]) 2212 z = pj_cat(rep, z, " no_baseline=" as *u8) 2213 z = pj_catn(rep, z, out[3]) 2214 z = pj_cat(rep, z, "\n" as *u8) 2215 sys_write(1, rep, z) 2216 return 0 2217} 2218 2219// ---------------- BURNDOWN (remaining effort over time from DONE + effort records) ---------------- 2220// out: 0=total_effort 1=done_effort 2=remaining 3=done_points 2221func pj_cmd_burndown(prefix: *u8, pid: *u8, out: *i64) -> i64 { 2222 out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0 2223 if pj_pathok(prefix) == 0 { return pj_pathfail() } 2224 let st: *i64 = pj_st_new() 2225 if pj_gather(prefix, pid, st) != 0 { return 1 } 2226 if st[12] == 0 { pj_p("PROJECT-NOTFOUND -- fail loud\n" as *u8); return 1 } 2227 let rep: *u8 = sys_mmap(K_MAGIC_2048) 2228 let tids: *i64 = st[1] as *i64 2229 let efforts: *i64 = st[4] as *i64 2230 var total: i64 = 0 2231 var i: i64 = 0 2232 while i < st[0] { total = total + efforts[i]; i = i + 1 } 2233 // sort done entries by date (insertion sort on parallel dtids/ddates copies) 2234 let dt: *i64 = st[7] as *i64 2235 let dd: *i64 = st[35] as *i64 2236 let sidx: *i64 = sys_mmap(8 * 260) as *i64 2237 var nd: i64 = st[6] 2238 i = 0 2239 while i < nd { sidx[i] = i; i = i + 1 } 2240 i = 1 2241 while i < nd { 2242 let key: i64 = sidx[i] 2243 var j: i64 = i - 1 2244 var go: i64 = 1 2245 while go == 1 { 2246 if j < 0 { go = 0 } else { 2247 if dd[sidx[j]] > dd[key] { sidx[j + 1] = sidx[j]; j = j - 1 } else { go = 0 } 2248 } 2249 } 2250 sidx[j + 1] = key 2251 i = i + 1 2252 } 2253 var remaining: i64 = total 2254 var done_pts: i64 = 0 2255 i = 0 2256 while i < nd { 2257 let de: i64 = sidx[i] 2258 let ti: i64 = pj_find(tids, st[0], dt[de] as *u8) 2259 var eff: i64 = 0 2260 if ti >= 0 { eff = efforts[ti] } 2261 remaining = remaining - eff 2262 done_pts = done_pts + eff 2263 let ymd: *i64 = sys_mmap(32) as *i64 2264 pj_civil(dd[de], ymd) 2265 var t: i64 = 0 2266 t = pj_cat(rep, t, "PROJECT-BURN date=" as *u8) 2267 t = pj_catn(rep, t, ymd[0]); rep[t] = 45 as u8; t = t + 1 2268 if ymd[1] < 10 { rep[t] = 48 as u8; t = t + 1 } 2269 t = pj_catn(rep, t, ymd[1]); rep[t] = 45 as u8; t = t + 1 2270 if ymd[2] < 10 { rep[t] = 48 as u8; t = t + 1 } 2271 t = pj_catn(rep, t, ymd[2]) 2272 t = pj_cat(rep, t, " tid=" as *u8) 2273 t = pj_cat(rep, t, dt[de] as *u8) 2274 t = pj_cat(rep, t, " burned=" as *u8) 2275 t = pj_catn(rep, t, eff) 2276 t = pj_cat(rep, t, " remaining_min=" as *u8) 2277 t = pj_catn(rep, t, remaining) 2278 t = pj_cat(rep, t, "\n" as *u8) 2279 sys_write(1, rep, t) 2280 i = i + 1 2281 } 2282 var z: i64 = 0 2283 z = pj_cat(rep, z, "PROJECT-BURN-END total_min=" as *u8) 2284 z = pj_catn(rep, z, total) 2285 z = pj_cat(rep, z, " done_min=" as *u8) 2286 z = pj_catn(rep, z, done_pts) 2287 z = pj_cat(rep, z, " remaining_min=" as *u8) 2288 z = pj_catn(rep, z, remaining) 2289 z = pj_cat(rep, z, " done_points=" as *u8) 2290 z = pj_catn(rep, z, st[6]) 2291 z = pj_cat(rep, z, "\n" as *u8) 2292 sys_write(1, rep, z) 2293 out[0] = total 2294 out[1] = done_pts 2295 out[2] = remaining 2296 out[3] = st[6] 2297 return 0 2298} 2299 2300// ---------------- WORKLOAD ---------------- 2301func pj_cmd_workload(prefix: *u8, pid: *u8) -> i64 { 2302 if pj_pathok(prefix) == 0 { return pj_pathfail() } 2303 let st: *i64 = pj_st_new() 2304 if pj_gather(prefix, pid, st) != 0 { return 1 } 2305 let rep: *u8 = sys_mmap(K_MAGIC_2048) 2306 if st[12] == 0 { 2307 var o0: i64 = 0 2308 o0 = pj_cat(rep, o0, "PROJECT-NOTFOUND pid=" as *u8) 2309 o0 = pj_cat(rep, o0, pid) 2310 o0 = pj_cat(rep, o0, " -- fail loud\n" as *u8) 2311 sys_write(1, rep, o0) 2312 return 1 2313 } 2314 let mp: *i64 = st[9] as *i64 2315 let mr: *i64 = st[10] as *i64 2316 let mc: *i64 = st[11] as *i64 2317 let owners: *i64 = st[2] as *i64 2318 let efforts: *i64 = st[4] as *i64 2319 var o: i64 = 0 2320 o = pj_cat(rep, o, "PROJECT-WORKLOAD pid=" as *u8) 2321 o = pj_cat(rep, o, pid) 2322 o = pj_cat(rep, o, " members=" as *u8) 2323 o = pj_catn(rep, o, st[8]) 2324 o = pj_cat(rep, o, " tasks=" as *u8) 2325 o = pj_catn(rep, o, st[0]) 2326 o = pj_cat(rep, o, "\n" as *u8) 2327 sys_write(1, rep, o) 2328 var m: i64 = 0 2329 while m < st[8] { 2330 let person: *u8 = mp[m] as *u8 2331 var sum: i64 = 0 2332 var i: i64 = 0 2333 while i < st[0] { 2334 if pj_eq(owners[i] as *u8, person) == 1 { 2335 let sv: i64 = pj_state(st, i) 2336 if sv != 2 { sum = sum + efforts[i] } 2337 } 2338 i = i + 1 2339 } 2340 let cap: *u8 = mc[m] as *u8 2341 var over: i64 = 0 - 1 2342 if pj_digits(cap) == 1 { 2343 over = 0 2344 if sum > pj_atoi(cap) { over = 1 } 2345 } 2346 var t: i64 = 0 2347 t = pj_cat(rep, t, "PROJECT-LOAD person=" as *u8) 2348 t = pj_cat(rep, t, person) 2349 t = pj_cat(rep, t, " role=" as *u8) 2350 t = pj_cat(rep, t, mr[m] as *u8) 2351 t = pj_cat(rep, t, " open_min=" as *u8) 2352 t = pj_catn(rep, t, sum) 2353 t = pj_cat(rep, t, " cap_min=" as *u8) 2354 t = pj_cat(rep, t, cap) 2355 t = pj_cat(rep, t, " over=" as *u8) 2356 if over == 1 { t = pj_cat(rep, t, "1" as *u8) } 2357 if over == 0 { t = pj_cat(rep, t, "0" as *u8) } 2358 if over < 0 { t = pj_cat(rep, t, "-" as *u8) } 2359 t = pj_cat(rep, t, "\n" as *u8) 2360 sys_write(1, rep, t) 2361 m = m + 1 2362 } 2363 // orphan owners: tasks assigned to someone with NO membership record (loud visibility) 2364 var i2: i64 = 0 2365 while i2 < st[0] { 2366 let ow: *u8 = owners[i2] as *u8 2367 var ismem: i64 = 0 2368 if pj_find(mp, st[8], ow) >= 0 { ismem = 1 } 2369 if st[12] == 1 { if pj_eq(st[13] as *u8, ow) == 1 { ismem = 1 } } 2370 if ismem == 0 { 2371 var t2: i64 = 0 2372 t2 = pj_cat(rep, t2, "PROJECT-LOAD-ORPHAN owner=" as *u8) 2373 t2 = pj_cat(rep, t2, ow) 2374 t2 = pj_cat(rep, t2, " -- task owner has no MEMBER record\n" as *u8) 2375 sys_write(1, rep, t2) 2376 } 2377 i2 = i2 + 1 2378 } 2379 return 0 2380} 2381 2382// ---------------- SELFTEST (the gate; forks the REAL blessed planner as the tsk:/don: writer) ---------------- 2383func pj_run(elf: *u8, args: *i64, outpath: *u8) -> i64 { 2384 let pid: i64 = sys_fork() 2385 if pid == 0 { 2386 if (outpath as i64) != 0 { 2387 let ofd: i64 = sys_openat_wr(outpath, 0x1a4) 2388 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 2389 } 2390 let argv: *i64 = sys_mmap(128) as *i64 2391 argv[0] = elf as i64 2392 var i: i64 = 0 2393 var go: i64 = 1 2394 while go == 1 { 2395 if args[i] == 0 { go = 0 } else { 2396 argv[i + 1] = args[i] 2397 i = i + 1 2398 } 2399 } 2400 argv[i + 1] = 0 2401 let envp: *i64 = sys_mmap(16) as *i64 2402 envp[0] = 0 2403 sys_execve(elf, argv, envp) 2404 sys_exit(127) 2405 } 2406 let stp: *i64 = sys_mmap(16) as *i64 2407 sys_wait4(pid, stp, 0) 2408 let sig: i64 = stp[0] & 0x7f 2409 if sig != 0 { return 128 + sig } 2410 return (stp[0] >> 8) & 0xff 2411} 2412 2413func pj_check(idx: i64, ok: i64, name: *u8) -> i64 { 2414 let rep: *u8 = sys_mmap(256) 2415 var o: i64 = 0 2416 o = pj_cat(rep, o, "PJ-GATE T" as *u8) 2417 o = pj_catn(rep, o, idx) 2418 o = pj_cat(rep, o, " " as *u8) 2419 o = pj_cat(rep, o, name) 2420 if ok == 1 { o = pj_cat(rep, o, " PASS\n" as *u8) } else { o = pj_cat(rep, o, " FAIL\n" as *u8) } 2421 sys_write(1, rep, o) 2422 return ok 2423} 2424 2425func pj_selftest() -> i64 { 2426 let uniq: i64 = sys_now_us() 2427 let planfile: *u8 = sys_mmap(256) 2428 var o: i64 = 0 2429 o = pj_cat(planfile, o, "/tmp/pjt_" as *u8) 2430 o = pj_catn(planfile, o, uniq) 2431 o = pj_cat(planfile, o, "_plan.txt" as *u8) 2432 planfile[o] = 0 as u8 2433 let projfile: *u8 = sys_mmap(256) 2434 o = 0 2435 o = pj_cat(projfile, o, "/tmp/pjt_" as *u8) 2436 o = pj_catn(projfile, o, uniq) 2437 o = pj_cat(projfile, o, "_proj.txt" as *u8) 2438 projfile[o] = 0 as u8 2439 let badfile: *u8 = sys_mmap(256) 2440 o = 0 2441 o = pj_cat(badfile, o, "/tmp/pjt_" as *u8) 2442 o = pj_catn(badfile, o, uniq) 2443 o = pj_cat(badfile, o, "_bad.txt" as *u8) 2444 badfile[o] = 0 as u8 2445 let prefix: *u8 = sys_mmap(256) 2446 o = 0 2447 o = pj_cat(prefix, o, "/tmp/pjt_" as *u8) 2448 o = pj_catn(prefix, o, uniq) 2449 o = pj_cat(prefix, o, "_st-" as *u8) 2450 prefix[o] = 0 as u8 2451 let outf: *u8 = sys_mmap(256) 2452 o = 0 2453 o = pj_cat(outf, o, "/tmp/pjt_" as *u8) 2454 o = pj_catn(outf, o, uniq) 2455 o = pj_cat(outf, o, "_run.out" as *u8) 2456 outf[o] = 0 as u8 2457 2458 pj_wfile(planfile, "TASK demo t1 alice 2026-07-10 60 3 - setup\nTASK demo t2 bob 2026-07-12 120 4 t1 build\nTASK demo t3 carol 2026-07-14 90 5 t2 install\nDONE demo t1 2026-07-09\n" as *u8) 2459 pj_wfile(projfile, "PROJ demo elder website-build\nMEMBER demo alice team -\nMEMBER demo bob team -\nMEMBER demo carol contractor 60\n" as *u8) 2460 pj_wfile(badfile, "MEMBER demo dave boss -\n" as *u8) 2461 2462 var pass: i64 = 0 2463 // T1 the INDEPENDENT planner organ writes tsk:/don: records into the shared store 2464 let a1: *i64 = sys_mmap(8 * 8) as *i64 2465 a1[0] = "load" as *u8 as i64 2466 a1[1] = planfile as i64 2467 a1[2] = prefix as i64 2468 a1[3] = "7001" as *u8 as i64 2469 a1[4] = 0 2470 let rc1: i64 = pj_run("_offc/nx_vizsla_plan.elf" as *u8, a1, outf) 2471 var ok: i64 = 0 2472 if rc1 == 0 { ok = 1 } 2473 pass = pass + pj_check(1, ok, "planner-writes-store" as *u8) 2474 // T2 our loader adds prj:/mem: beside them 2475 let av: *i64 = sys_mmap(8 * 8) as *i64 2476 av[0] = "pj" as *u8 as i64 2477 av[1] = "load" as *u8 as i64 2478 av[2] = projfile as i64 2479 av[3] = prefix as i64 2480 av[4] = "7002" as *u8 as i64 2481 let rc2: i64 = pj_cmd_load(5, av) 2482 ok = 0 2483 if rc2 == 0 { ok = 1 } 2484 pass = pass + pj_check(2, ok, "project-load" as *u8) 2485 // T3 states: t1 DONE, t2 READY (dep done), t3 BLOCKED (dep open) 2486 ok = 0 2487 let s1: i64 = pj_task_state(prefix, "demo" as *u8, "t1" as *u8) 2488 let s2: i64 = pj_task_state(prefix, "demo" as *u8, "t2" as *u8) 2489 let s3: i64 = pj_task_state(prefix, "demo" as *u8, "t3" as *u8) 2490 if s1 == 2 { if s2 == 0 { if s3 == 1 { ok = 1 } } } 2491 pass = pass + pj_check(3, ok, "dep-states" as *u8) 2492 // T4 contractor sees ONLY own task 2493 let out4: *i64 = sys_mmap(32) as *i64 2494 let rc4: i64 = pj_view(prefix, "demo" as *u8, "carol" as *u8, out4) 2495 ok = 0 2496 if rc4 == 0 { if out4[0] == 3 { if out4[1] == 1 { ok = 1 } } } 2497 pass = pass + pj_check(4, ok, "contractor-scope-own" as *u8) 2498 // T5 NEG: carol must NOT see t1; must see t3 2499 ok = 0 2500 let v1: i64 = pj_visible(prefix, "demo" as *u8, "carol" as *u8, "t1" as *u8) 2501 let v3: i64 = pj_visible(prefix, "demo" as *u8, "carol" as *u8, "t3" as *u8) 2502 if v1 == 0 { if v3 == 1 { ok = 1 } } 2503 pass = pass + pj_check(5, ok, "neg-contractor-hidden" as *u8) 2504 // T6 NEG: non-member denied fail-closed 2505 let out6: *i64 = sys_mmap(32) as *i64 2506 let rc6: i64 = pj_view(prefix, "demo" as *u8, "mallory" as *u8, out6) 2507 ok = 0 2508 if rc6 == 2 { ok = 1 } 2509 pass = pass + pj_check(6, ok, "neg-nonmember-deny" as *u8) 2510 // T7 NEG: unknown project fails loud 2511 let out7: *i64 = sys_mmap(32) as *i64 2512 let rc7: i64 = pj_view(prefix, "ghost" as *u8, "carol" as *u8, out7) 2513 ok = 0 2514 if rc7 == 1 { ok = 1 } 2515 pass = pass + pj_check(7, ok, "neg-unknown-project" as *u8) 2516 // T8 workload sums + data-driven over-capacity 2517 ok = 0 2518 let w1: i64 = pj_open_min(prefix, "demo" as *u8, "carol" as *u8) 2519 let w2: i64 = pj_open_min(prefix, "demo" as *u8, "alice" as *u8) 2520 let w3: i64 = pj_open_min(prefix, "demo" as *u8, "bob" as *u8) 2521 let ov1: i64 = pj_over(prefix, "demo" as *u8, "carol" as *u8) 2522 let ov2: i64 = pj_over(prefix, "demo" as *u8, "bob" as *u8) 2523 if w1 == 90 { if w2 == 0 { if w3 == 120 { if ov1 == 1 { if ov2 == 0 - 1 { ok = 1 } } } } } 2524 pass = pass + pj_check(8, ok, "workload-capacity" as *u8) 2525 // T9 NEG: malformed role refuses load 2526 let av9: *i64 = sys_mmap(8 * 8) as *i64 2527 av9[0] = "pj" as *u8 as i64 2528 av9[1] = "load" as *u8 as i64 2529 av9[2] = badfile as i64 2530 av9[3] = prefix as i64 2531 av9[4] = "7003" as *u8 as i64 2532 let rc9: i64 = pj_cmd_load(5, av9) 2533 ok = 0 2534 if rc9 != 0 { ok = 1 } 2535 pass = pass + pj_check(9, ok, "neg-bad-role-refused" as *u8) 2536 // T10 idempotent re-load: no duplicate members 2537 let av10: *i64 = sys_mmap(8 * 8) as *i64 2538 av10[0] = "pj" as *u8 as i64 2539 av10[1] = "load" as *u8 as i64 2540 av10[2] = projfile as i64 2541 av10[3] = prefix as i64 2542 av10[4] = "7004" as *u8 as i64 2543 let rc10: i64 = pj_cmd_load(5, av10) 2544 let st10: *i64 = pj_st_new() 2545 pj_gather(prefix, "demo" as *u8, st10) 2546 ok = 0 2547 if rc10 == 0 { if st10[8] == 3 { if st10[0] == 3 { ok = 1 } } } 2548 pass = pass + pj_check(10, ok, "idempotent-reload" as *u8) 2549 // T11 owner sees all 2550 let out11: *i64 = sys_mmap(32) as *i64 2551 let rc11: i64 = pj_view(prefix, "demo" as *u8, "elder" as *u8, out11) 2552 ok = 0 2553 if rc11 == 0 { if out11[1] == 3 { ok = 1 } } 2554 pass = pass + pj_check(11, ok, "owner-sees-all" as *u8) 2555 // T12 NEG: path guard refuses out-of-namespace and dot-dot stores 2556 let av12: *i64 = sys_mmap(8 * 8) as *i64 2557 av12[0] = "pj" as *u8 as i64 2558 av12[1] = "load" as *u8 as i64 2559 av12[2] = projfile as i64 2560 av12[3] = "knowledge/evil-" as *u8 as i64 2561 av12[4] = "7005" as *u8 as i64 2562 let rc12a: i64 = pj_cmd_load(5, av12) 2563 let av12b: *i64 = sys_mmap(8 * 8) as *i64 2564 av12b[0] = "pj" as *u8 as i64 2565 av12b[1] = "load" as *u8 as i64 2566 av12b[2] = "/tmp/../etc/passwd" as *u8 as i64 2567 av12b[3] = prefix as i64 2568 av12b[4] = "7006" as *u8 as i64 2569 let rc12b: i64 = pj_cmd_load(5, av12b) 2570 ok = 0 2571 if rc12a != 0 { if rc12b != 0 { ok = 1 } } 2572 pass = pass + pj_check(12, ok, "neg-path-guard" as *u8) 2573 2574 // ---- fence project fixtures (level / risk / evm / html) ---- 2575 let plan2: *u8 = sys_mmap(256) 2576 o = 0 2577 o = pj_cat(plan2, o, "/tmp/pjt_" as *u8) 2578 o = pj_catn(plan2, o, uniq) 2579 o = pj_cat(plan2, o, "_plan2.txt" as *u8) 2580 plan2[o] = 0 as u8 2581 let proj2: *u8 = sys_mmap(256) 2582 o = 0 2583 o = pj_cat(proj2, o, "/tmp/pjt_" as *u8) 2584 o = pj_catn(proj2, o, uniq) 2585 o = pj_cat(proj2, o, "_proj2.txt" as *u8) 2586 proj2[o] = 0 as u8 2587 let prefix2: *u8 = sys_mmap(256) 2588 o = 0 2589 o = pj_cat(prefix2, o, "/tmp/pjt_" as *u8) 2590 o = pj_catn(prefix2, o, uniq) 2591 o = pj_cat(prefix2, o, "_st2-" as *u8) 2592 prefix2[o] = 0 as u8 2593 let htmlfile: *u8 = sys_mmap(256) 2594 o = 0 2595 o = pj_cat(htmlfile, o, "/tmp/pjt_" as *u8) 2596 o = pj_catn(htmlfile, o, uniq) 2597 o = pj_cat(htmlfile, o, "_page.html" as *u8) 2598 htmlfile[o] = 0 as u8 2599 pj_wfile(plan2, "TASK fence f0 amber 2026-07-01 30 1 - permits\nTASK fence f1 amber 2026-07-20 200 3 - posts\nTASK fence f2 bo 2026-07-18 100 4 - rails\nTASK fence f3 bo 2026-07-19 80 2 f2 gates\nTASK fence f4 cade 2026-07-21 60 3 - paint\nTASK fence f5 cade 2026-08-15 60 2 - cleanup\nDONE fence f4 2026-07-16\n" as *u8) 2600 pj_wfile(proj2, "PROJ fence elder fence-build\nMEMBER fence amber team 240\nMEMBER fence bo contractor 120\nMEMBER fence cade team 480\nBUDGET fence 100000\nWCONF fence 120 1\nCOST fence f2 30000 2026-07-15\nCOST fence f4 10000 2026-07-16\n" as *u8) 2601 let a2: *i64 = sys_mmap(8 * 8) as *i64 2602 a2[0] = "load" as *u8 as i64 2603 a2[1] = plan2 as i64 2604 a2[2] = prefix2 as i64 2605 a2[3] = "7101" as *u8 as i64 2606 a2[4] = 0 2607 let rcf1: i64 = pj_run("_offc/nx_vizsla_plan.elf" as *u8, a2, outf) 2608 let avf: *i64 = sys_mmap(8 * 8) as *i64 2609 avf[0] = "pj" as *u8 as i64 2610 avf[1] = "load" as *u8 as i64 2611 avf[2] = proj2 as i64 2612 avf[3] = prefix2 as i64 2613 avf[4] = "7102" as *u8 as i64 2614 let rcf2: i64 = pj_cmd_load(5, avf) 2615 if rcf1 != 0 { pj_p("PJ-GATE fence planner load FAILED\n" as *u8) } 2616 if rcf2 != 0 { pj_p("PJ-GATE fence project load FAILED\n" as *u8) } 2617 // time logs + task notes into the same fence store (T19-24 substrate) 2618 let logsf: *u8 = sys_mmap(256) 2619 o = 0 2620 o = pj_cat(logsf, o, "/tmp/pjt_" as *u8) 2621 o = pj_catn(logsf, o, uniq) 2622 o = pj_cat(logsf, o, "_logs.txt" as *u8) 2623 logsf[o] = 0 as u8 2624 pj_wfile(logsf, "LOG fence f2 bo 2026-07-16 60\nLOG fence f2 bo 2026-07-17 30\nLOG fence f1 amber 2026-07-16 45\nNOTE fence f2 bo 2026-07-16 rails cut and primed\nNOTE fence f1 amber 2026-07-17 posts delivered late\n" as *u8) 2625 let avl: *i64 = sys_mmap(8 * 8) as *i64 2626 avl[0] = "pj" as *u8 as i64 2627 avl[1] = "load" as *u8 as i64 2628 avl[2] = logsf as i64 2629 avl[3] = prefix2 as i64 2630 avl[4] = "7103" as *u8 as i64 2631 let rcl: i64 = pj_cmd_load(5, avl) 2632 if rcl != 0 { pj_p("PJ-GATE logs load FAILED\n" as *u8) } 2633 // T13 leveling KAT: bo (cap 120, open 180) moves READY f2 (100) to cade (headroom 420) -> balanced 2634 let out13: *i64 = sys_mmap(32) as *i64 2635 let rc13: i64 = pj_cmd_level(prefix2, "fence" as *u8, out13, "-" as *u8) 2636 ok = 0 2637 if rc13 == 0 { if out13[0] == 1 { if out13[1] == 0 { ok = 1 } } } 2638 pass = pass + pj_check(13, ok, "level-move-kat" as *u8) 2639 // T14 NEG: demo carol is over-cap but her only task is BLOCKED -> no move, still over (named STUCK) 2640 let out14: *i64 = sys_mmap(32) as *i64 2641 let rc14: i64 = pj_cmd_level(prefix, "demo" as *u8, out14, "-" as *u8) 2642 ok = 0 2643 if rc14 == 0 { if out14[0] == 0 { if out14[1] == 1 { ok = 1 } } } 2644 pass = pass + pj_check(14, ok, "neg-level-blocked-stuck" as *u8) 2645 // T15 risk KATs (today 2026-07-17, WCONF 120/1): f0 OVERDUE; f1/f2/f3 TIGHT; f5 OK; f4 done skipped 2646 let out15: *i64 = sys_mmap(48) as *i64 2647 let rc15: i64 = pj_cmd_risk(prefix2, "fence" as *u8, "2026-07-17" as *u8, out15) 2648 ok = 0 2649 if rc15 == 0 { if out15[0] == 1 { if out15[1] == 0 { if out15[2] == 3 { if out15[3] == 1 { if out15[4] == 0 { ok = 1 } } } } } } 2650 pass = pass + pj_check(15, ok, "risk-slack-kats" as *u8) 2651 // T16 risk without WCONF (demo, today 2026-07-13): t2 OVERDUE, t3 UNRATED (honest no-duration-claim) 2652 let out16: *i64 = sys_mmap(48) as *i64 2653 let rc16: i64 = pj_cmd_risk(prefix, "demo" as *u8, "2026-07-13" as *u8, out16) 2654 ok = 0 2655 if rc16 == 0 { if out16[0] == 1 { if out16[1] == 0 { if out16[2] == 0 { if out16[3] == 0 { if out16[4] == 1 { ok = 1 } } } } } } 2656 pass = pass + pj_check(16, ok, "risk-no-wconf-unrated" as *u8) 2657 // T17 evm KAT: scope 60/530 -> 113 permil; cpi 11300*1000/40000 -> 282; not over budget 2658 let out17: *i64 = sys_mmap(32) as *i64 2659 let rc17: i64 = pj_cmd_evm(prefix2, "fence" as *u8, out17) 2660 ok = 0 2661 if rc17 == 0 { if out17[0] == 113 { if out17[1] == 282 { if out17[2] == 0 { ok = 1 } } } } 2662 pass = pass + pj_check(17, ok, "evm-kat" as *u8) 2663 // T18 NEG: evm without a BUDGET record fails loud 2664 let out18: *i64 = sys_mmap(32) as *i64 2665 let rc18: i64 = pj_cmd_evm(prefix, "demo" as *u8, out18) 2666 ok = 0 2667 if rc18 != 0 { ok = 1 } 2668 pass = pass + pj_check(18, ok, "neg-evm-no-budget" as *u8) 2669 // T19 html: contractor bo sees f2+f3 only; own note visible, amber's note HIDDEN; own timesheet row 2670 let rc19: i64 = pj_html(prefix2, "fence" as *u8, "bo" as *u8, "2026-07-17" as *u8, htmlfile) 2671 ok = 0 2672 if rc19 == 0 { 2673 let hszp: *i64 = sys_mmap(16) as *i64 2674 let hbuf: *u8 = ss_readall(htmlfile, hszp) 2675 let hsz: i64 = hszp[0] 2676 if hsz > 0 { 2677 let c1: i64 = pj_contains(hbuf, hsz, "<td>f2</td>" as *u8) 2678 let c2: i64 = pj_contains(hbuf, hsz, "<td>f3</td>" as *u8) 2679 let c3: i64 = pj_contains(hbuf, hsz, "<td>f1</td>" as *u8) 2680 let c4: i64 = pj_contains(hbuf, hsz, "<script" as *u8) 2681 let c5: i64 = pj_contains(hbuf, hsz, "<table" as *u8) 2682 let c6: i64 = pj_contains(hbuf, hsz, "posts delivered late" as *u8) 2683 let c7: i64 = pj_contains(hbuf, hsz, "rails cut and primed" as *u8) 2684 let c8: i64 = pj_contains(hbuf, hsz, "Timesheet" as *u8) 2685 if c1 == 1 { if c2 == 1 { if c3 == 0 { if c4 == 0 { if c5 == 1 { if c6 == 0 { if c7 == 1 { if c8 == 1 { ok = 1 } } } } } } } } 2686 } 2687 } 2688 pass = pass + pj_check(19, ok, "html-scoped-notes-timesheet" as *u8) 2689 // T20 timesheet KAT: bo 90 + amber 45 = 135 across 2 persons 2690 let out20: *i64 = sys_mmap(32) as *i64 2691 let rc20: i64 = pj_cmd_timesheet(prefix2, "fence" as *u8, out20) 2692 ok = 0 2693 if rc20 == 0 { if out20[0] == 135 { if out20[1] == 2 { ok = 1 } } } 2694 pass = pass + pj_check(20, ok, "timesheet-kat" as *u8) 2695 // T21 notes: f2 has exactly one; unknown tid fails loud 2696 let out21: *i64 = sys_mmap(32) as *i64 2697 let rc21: i64 = pj_cmd_notes(prefix2, "fence" as *u8, "f2" as *u8, out21) 2698 let out21b: *i64 = sys_mmap(32) as *i64 2699 let rc21b: i64 = pj_cmd_notes(prefix2, "fence" as *u8, "zz9" as *u8, out21b) 2700 ok = 0 2701 if rc21 == 0 { if out21[0] == 1 { if rc21b != 0 { ok = 1 } } } 2702 pass = pass + pj_check(21, ok, "notes-roundtrip-neg" as *u8) 2703 // T22 template: blueprint -> dated TASK/MILE file -> the REAL planner loads it (E2E) 2704 let tplf: *u8 = sys_mmap(256) 2705 o = 0 2706 o = pj_cat(tplf, o, "/tmp/pjt_" as *u8) 2707 o = pj_catn(tplf, o, uniq) 2708 o = pj_cat(tplf, o, "_tpl.txt" as *u8) 2709 tplf[o] = 0 as u8 2710 let genf: *u8 = sys_mmap(256) 2711 o = 0 2712 o = pj_cat(genf, o, "/tmp/pjt_" as *u8) 2713 o = pj_catn(genf, o, uniq) 2714 o = pj_cat(genf, o, "_gen.txt" as *u8) 2715 genf[o] = 0 as u8 2716 let prefixC: *u8 = sys_mmap(256) 2717 o = 0 2718 o = pj_cat(prefixC, o, "/tmp/pjt_" as *u8) 2719 o = pj_catn(prefixC, o, uniq) 2720 o = pj_cat(prefixC, o, "_stC-" as *u8) 2721 prefixC[o] = 0 as u8 2722 pj_wfile(tplf, "TPLTASK t1 amber 0 60 3 - dig\nTPLTASK t2 bo 9 120 4 t1 pour\nTPLMILE m1 12 done-pour\n" as *u8) 2723 let av22: *i64 = sys_mmap(8 * 8) as *i64 2724 av22[0] = "pj" as *u8 as i64 2725 av22[1] = "template" as *u8 as i64 2726 av22[2] = tplf as i64 2727 av22[3] = "shed" as *u8 as i64 2728 av22[4] = "2026-07-25" as *u8 as i64 2729 av22[5] = genf as i64 2730 let rc22: i64 = pj_cmd_template(6, av22) 2731 ok = 0 2732 if rc22 == 0 { 2733 let gszp: *i64 = sys_mmap(16) as *i64 2734 let gbuf: *u8 = ss_readall(genf, gszp) 2735 let gsz: i64 = gszp[0] 2736 if gsz > 0 { 2737 let k1: i64 = pj_contains(gbuf, gsz, "TASK shed t1 amber 2026-07-25 60 3 - dig" as *u8) 2738 let k2: i64 = pj_contains(gbuf, gsz, "TASK shed t2 bo 2026-08-03 120 4 t1 pour" as *u8) 2739 let k3: i64 = pj_contains(gbuf, gsz, "MILE shed m1 2026-08-06 done-pour" as *u8) 2740 if k1 == 1 { if k2 == 1 { if k3 == 1 { 2741 let a22: *i64 = sys_mmap(8 * 8) as *i64 2742 a22[0] = "load" as *u8 as i64 2743 a22[1] = genf as i64 2744 a22[2] = prefixC as i64 2745 a22[3] = "7401" as *u8 as i64 2746 a22[4] = 0 2747 let rc22b: i64 = pj_run("_offc/nx_vizsla_plan.elf" as *u8, a22, outf) 2748 if rc22b == 0 { 2749 let st22: *i64 = pj_st_new() 2750 pj_gather(prefixC, "shed" as *u8, st22) 2751 if st22[0] == 2 { ok = 1 } 2752 } 2753 } } } 2754 } 2755 } 2756 pass = pass + pj_check(22, ok, "template-to-planner-e2e" as *u8) 2757 // T23 owner html carries the schedule bars + risk summary 2758 let htmlf2: *u8 = sys_mmap(256) 2759 o = 0 2760 o = pj_cat(htmlf2, o, "/tmp/pjt_" as *u8) 2761 o = pj_catn(htmlf2, o, uniq) 2762 o = pj_cat(htmlf2, o, "_page2.html" as *u8) 2763 htmlf2[o] = 0 as u8 2764 let rc23: i64 = pj_html(prefix2, "fence" as *u8, "elder" as *u8, "2026-07-17" as *u8, htmlf2) 2765 ok = 0 2766 if rc23 == 0 { 2767 let hszp2: *i64 = sys_mmap(16) as *i64 2768 let hbuf2: *u8 = ss_readall(htmlf2, hszp2) 2769 let hsz2: i64 = hszp2[0] 2770 if hsz2 > 0 { 2771 let d1: i64 = pj_contains(hbuf2, hsz2, "Schedule (latest-safe windows)" as *u8) 2772 let d2: i64 = pj_contains(hbuf2, hsz2, "risk: overdue=" as *u8) 2773 let d3: i64 = pj_contains(hbuf2, hsz2, "posts delivered late" as *u8) 2774 if d1 == 1 { if d2 == 1 { if d3 == 1 { ok = 1 } } } 2775 } 2776 } 2777 pass = pass + pj_check(23, ok, "html-gantt-risk-owner" as *u8) 2778 // T24 NEG: non-digit LOG minutes refused 2779 let badl: *u8 = sys_mmap(256) 2780 o = 0 2781 o = pj_cat(badl, o, "/tmp/pjt_" as *u8) 2782 o = pj_catn(badl, o, uniq) 2783 o = pj_cat(badl, o, "_badlog.txt" as *u8) 2784 badl[o] = 0 as u8 2785 pj_wfile(badl, "LOG fence f2 bo 2026-07-16 abc\n" as *u8) 2786 let av24: *i64 = sys_mmap(8 * 8) as *i64 2787 av24[0] = "pj" as *u8 as i64 2788 av24[1] = "load" as *u8 as i64 2789 av24[2] = badl as i64 2790 av24[3] = prefix2 as i64 2791 av24[4] = "7104" as *u8 as i64 2792 let rc24: i64 = pj_cmd_load(5, av24) 2793 ok = 0 2794 if rc24 != 0 { ok = 1 } 2795 pass = pass + pj_check(24, ok, "neg-bad-log-minutes" as *u8) 2796 // T25 ASSIGN override: t2 (bob's) reassigned to carol -> effective owner flips everywhere 2797 let asgf: *u8 = sys_mmap(256) 2798 o = 0 2799 o = pj_cat(asgf, o, "/tmp/pjt_" as *u8) 2800 o = pj_catn(asgf, o, uniq) 2801 o = pj_cat(asgf, o, "_asg.txt" as *u8) 2802 asgf[o] = 0 as u8 2803 pj_wfile(asgf, "ASSIGN demo t2 carol 2026-07-15\n" as *u8) 2804 let av25: *i64 = sys_mmap(8 * 8) as *i64 2805 av25[0] = "pj" as *u8 as i64 2806 av25[1] = "load" as *u8 as i64 2807 av25[2] = asgf as i64 2808 av25[3] = prefix as i64 2809 av25[4] = "7501" as *u8 as i64 2810 let rc25: i64 = pj_cmd_load(5, av25) 2811 let out25: *i64 = sys_mmap(32) as *i64 2812 let rc25b: i64 = pj_view(prefix, "demo" as *u8, "carol" as *u8, out25) 2813 let w25: i64 = pj_open_min(prefix, "demo" as *u8, "carol" as *u8) 2814 ok = 0 2815 if rc25 == 0 { if rc25b == 0 { if out25[1] == 2 { if w25 == 210 { ok = 1 } } } } 2816 pass = pass + pj_check(25, ok, "assign-effective-owner" as *u8) 2817 // T26 level APPLY: the fence move becomes ASSIGN records; a re-run finds nothing left to move 2818 let out26: *i64 = sys_mmap(32) as *i64 2819 let rc26: i64 = pj_cmd_level(prefix2, "fence" as *u8, out26, "2026-07-18" as *u8) 2820 let out26b: *i64 = sys_mmap(32) as *i64 2821 let rc26b: i64 = pj_cmd_level(prefix2, "fence" as *u8, out26b, "-" as *u8) 2822 let st26: *i64 = pj_st_new() 2823 pj_gather(prefix2, "fence" as *u8, st26) 2824 let t26i: i64 = pj_find(st26[1] as *i64, st26[0], "f2" as *u8) 2825 var f2owner_ok: i64 = 0 2826 if t26i >= 0 { 2827 let ow26: *i64 = st26[2] as *i64 2828 if pj_eq(ow26[t26i] as *u8, "cade" as *u8) == 1 { f2owner_ok = 1 } 2829 } 2830 ok = 0 2831 if rc26 == 0 { if out26[0] == 1 { if rc26b == 0 { if out26b[0] == 0 { if out26b[1] == 0 { if f2owner_ok == 1 { ok = 1 } } } } } } 2832 pass = pass + pj_check(26, ok, "level-apply-assign-e2e" as *u8) 2833 // T27 latest-date wins (an EARLIER assign never regresses the owner) + NEG bad assign date 2834 let asgf2: *u8 = sys_mmap(256) 2835 o = 0 2836 o = pj_cat(asgf2, o, "/tmp/pjt_" as *u8) 2837 o = pj_catn(asgf2, o, uniq) 2838 o = pj_cat(asgf2, o, "_asg2.txt" as *u8) 2839 asgf2[o] = 0 as u8 2840 pj_wfile(asgf2, "ASSIGN demo t2 dave 2026-07-14\n" as *u8) 2841 let av27: *i64 = sys_mmap(8 * 8) as *i64 2842 av27[0] = "pj" as *u8 as i64 2843 av27[1] = "load" as *u8 as i64 2844 av27[2] = asgf2 as i64 2845 av27[3] = prefix as i64 2846 av27[4] = "7502" as *u8 as i64 2847 let rc27: i64 = pj_cmd_load(5, av27) 2848 let st27: *i64 = pj_st_new() 2849 pj_gather(prefix, "demo" as *u8, st27) 2850 let t27i: i64 = pj_find(st27[1] as *i64, st27[0], "t2" as *u8) 2851 var still_carol: i64 = 0 2852 if t27i >= 0 { 2853 let ow27: *i64 = st27[2] as *i64 2854 if pj_eq(ow27[t27i] as *u8, "carol" as *u8) == 1 { still_carol = 1 } 2855 } 2856 let badaf: *u8 = sys_mmap(256) 2857 o = 0 2858 o = pj_cat(badaf, o, "/tmp/pjt_" as *u8) 2859 o = pj_catn(badaf, o, uniq) 2860 o = pj_cat(badaf, o, "_badasg.txt" as *u8) 2861 badaf[o] = 0 as u8 2862 pj_wfile(badaf, "ASSIGN demo t2 carol not-a-date\n" as *u8) 2863 let av27b: *i64 = sys_mmap(8 * 8) as *i64 2864 av27b[0] = "pj" as *u8 as i64 2865 av27b[1] = "load" as *u8 as i64 2866 av27b[2] = badaf as i64 2867 av27b[3] = prefix as i64 2868 av27b[4] = "7503" as *u8 as i64 2869 let rc27b: i64 = pj_cmd_load(5, av27b) 2870 ok = 0 2871 if rc27 == 0 { if still_carol == 1 { if rc27b != 0 { ok = 1 } } } 2872 pass = pass + pj_check(27, ok, "assign-latest-wins-neg" as *u8) 2873 2874 // T28 snapshot -> BASE records; T29 baseline variance detects a due slip 2875 let snapf: *u8 = sys_mmap(256) 2876 o = 0 2877 o = pj_cat(snapf, o, "/tmp/pjt_" as *u8) 2878 o = pj_catn(snapf, o, uniq) 2879 o = pj_cat(snapf, o, "_snap.txt" as *u8) 2880 snapf[o] = 0 as u8 2881 let rc28: i64 = pj_cmd_snapshot(prefix2, "fence" as *u8, "2026-07-05" as *u8, snapf) 2882 let av28: *i64 = sys_mmap(8 * 8) as *i64 2883 av28[0] = "pj" as *u8 as i64 2884 av28[1] = "load" as *u8 as i64 2885 av28[2] = snapf as i64 2886 av28[3] = prefix2 as i64 2887 av28[4] = "7601" as *u8 as i64 2888 let rc28b: i64 = pj_cmd_load(5, av28) 2889 ok = 0 2890 if rc28 == 0 { if rc28b == 0 { 2891 let st28: *i64 = pj_st_new() 2892 pj_gather(prefix2, "fence" as *u8, st28) 2893 if st28[36] == 6 { ok = 1 } 2894 } } 2895 pass = pass + pj_check(28, ok, "snapshot-writes-baseline" as *u8) 2896 // T29 slip detection in a FRESH store: one task due 2026-07-20, baseline due 2026-07-12 -> +8 days slipped 2897 let prefixV: *u8 = sys_mmap(256) 2898 o = 0 2899 o = pj_cat(prefixV, o, "/tmp/pjt_" as *u8) 2900 o = pj_catn(prefixV, o, uniq) 2901 o = pj_cat(prefixV, o, "_stV-" as *u8) 2902 prefixV[o] = 0 as u8 2903 let vplan: *u8 = sys_mmap(256) 2904 o = 0 2905 o = pj_cat(vplan, o, "/tmp/pjt_" as *u8) 2906 o = pj_catn(vplan, o, uniq) 2907 o = pj_cat(vplan, o, "_vplan.txt" as *u8) 2908 vplan[o] = 0 as u8 2909 let vproj: *u8 = sys_mmap(256) 2910 o = 0 2911 o = pj_cat(vproj, o, "/tmp/pjt_" as *u8) 2912 o = pj_catn(vproj, o, uniq) 2913 o = pj_cat(vproj, o, "_vproj.txt" as *u8) 2914 vproj[o] = 0 as u8 2915 pj_wfile(vplan, "TASK dam d1 amber 2026-07-20 100 3 - build\n" as *u8) 2916 pj_wfile(vproj, "PROJ dam elder dam-build\nMEMBER dam amber team -\nBASE dam d1 2026-07-12 100 2026-07-01\n" as *u8) 2917 let avp: *i64 = sys_mmap(8 * 8) as *i64 2918 avp[0] = "load" as *u8 as i64 2919 avp[1] = vplan as i64 2920 avp[2] = prefixV as i64 2921 avp[3] = "7602" as *u8 as i64 2922 avp[4] = 0 2923 let rc29p: i64 = pj_run("_offc/nx_vizsla_plan.elf" as *u8, avp, outf) 2924 let av29: *i64 = sys_mmap(8 * 8) as *i64 2925 av29[0] = "pj" as *u8 as i64 2926 av29[1] = "load" as *u8 as i64 2927 av29[2] = vproj as i64 2928 av29[3] = prefixV as i64 2929 av29[4] = "7603" as *u8 as i64 2930 let rc29: i64 = pj_cmd_load(5, av29) 2931 let out29: *i64 = sys_mmap(32) as *i64 2932 let rc29b: i64 = pj_cmd_baseline(prefixV, "dam" as *u8, out29) 2933 ok = 0 2934 if rc29 == 0 { if rc29p == 0 { if rc29b == 0 { if out29[0] == 1 { if out29[1] == 8 { if out29[2] == 1 { ok = 1 } } } } } } 2935 pass = pass + pj_check(29, ok, "baseline-slip-detected" as *u8) 2936 // T30 burndown: fence has f4 DONE (effort 60); remaining = total - 60, done_points=1 2937 let out30: *i64 = sys_mmap(32) as *i64 2938 let rc30: i64 = pj_cmd_burndown(prefix2, "fence" as *u8, out30) 2939 ok = 0 2940 if rc30 == 0 { if out30[1] == 60 { if out30[3] == 1 { if out30[2] == out30[0] - 60 { ok = 1 } } } } 2941 pass = pass + pj_check(30, ok, "burndown-from-done" as *u8) 2942 // T31 NEG: baseline with no BASE records fails loud (demo has none) 2943 let out31: *i64 = sys_mmap(32) as *i64 2944 let rc31: i64 = pj_cmd_baseline(prefix, "demo" as *u8, out31) 2945 ok = 0 2946 if rc31 != 0 { ok = 1 } 2947 pass = pass + pj_check(31, ok, "neg-baseline-none" as *u8) 2948 2949 let rep: *u8 = sys_mmap(256) 2950 var z: i64 = 0 2951 z = pj_cat(rep, z, "NX-PROJECT-GATE pass=" as *u8) 2952 z = pj_catn(rep, z, pass) 2953 z = pj_cat(rep, z, "/31 verdict=" as *u8) 2954 if pass == 31 { z = pj_cat(rep, z, "GREEN\n" as *u8) } else { z = pj_cat(rep, z, "RED\n" as *u8) } 2955 sys_write(1, rep, z) 2956 if pass == 31 { return 0 } 2957 return 1 2958} 2959 2960// ---------------- CLI ---------------- 2961func pj_usage() -> i64 { 2962 pj_p("nx_project: load | view | workload | level [apply <date>] | risk <today> | evm | timesheet | notes <tid> | template <tpl> <pid> <start> <out> | snapshot <prefix> <pid> <date> <out> | baseline <prefix> <pid> | burndown <prefix> <pid> | html <prefix> <pid> <person> <today> <out|-> | (argless = selftest)\n" as *u8) 2963 return 1 2964} 2965 2966func pj_cli(argc: i64, argv: *i64) -> i64 { 2967 if argc < 2 { return pj_selftest() } 2968 let cmd: *u8 = argv[1] as *u8 2969 if pj_eq(cmd, "load" as *u8) == 1 { return pj_cmd_load(argc, argv) } 2970 if pj_eq(cmd, "view" as *u8) == 1 { 2971 if argc < 5 { pj_p("PROJECT view needs <prefix> <pid> <person> -- fail loud\n" as *u8); return 1 } 2972 let out: *i64 = sys_mmap(32) as *i64 2973 return pj_view(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, out) 2974 } 2975 if pj_eq(cmd, "workload" as *u8) == 1 { 2976 if argc < 4 { pj_p("PROJECT workload needs <prefix> <pid> -- fail loud\n" as *u8); return 1 } 2977 return pj_cmd_workload(argv[2] as *u8, argv[3] as *u8) 2978 } 2979 if pj_eq(cmd, "level" as *u8) == 1 { 2980 if argc < 4 { pj_p("PROJECT level needs <prefix> <pid> [apply <date>] -- fail loud\n" as *u8); return 1 } 2981 let out2: *i64 = sys_mmap(32) as *i64 2982 var aply: *u8 = "-" as *u8 2983 if argc >= 6 { if pj_eq(argv[4] as *u8, "apply" as *u8) == 1 { aply = argv[5] as *u8 } } 2984 return pj_cmd_level(argv[2] as *u8, argv[3] as *u8, out2, aply) 2985 } 2986 if pj_eq(cmd, "risk" as *u8) == 1 { 2987 if argc < 5 { pj_p("PROJECT risk needs <prefix> <pid> <today> -- fail loud\n" as *u8); return 1 } 2988 let out3: *i64 = sys_mmap(48) as *i64 2989 return pj_cmd_risk(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, out3) 2990 } 2991 if pj_eq(cmd, "evm" as *u8) == 1 { 2992 if argc < 4 { pj_p("PROJECT evm needs <prefix> <pid> -- fail loud\n" as *u8); return 1 } 2993 let out4: *i64 = sys_mmap(32) as *i64 2994 return pj_cmd_evm(argv[2] as *u8, argv[3] as *u8, out4) 2995 } 2996 if pj_eq(cmd, "html" as *u8) == 1 { 2997 if argc < 7 { pj_p("PROJECT html needs <prefix> <pid> <person> <today> <outfile|-> -- fail loud\n" as *u8); return 1 } 2998 return pj_html(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) 2999 } 3000 if pj_eq(cmd, "timesheet" as *u8) == 1 { 3001 if argc < 4 { pj_p("PROJECT timesheet needs <prefix> <pid> -- fail loud\n" as *u8); return 1 } 3002 let out5: *i64 = sys_mmap(32) as *i64 3003 return pj_cmd_timesheet(argv[2] as *u8, argv[3] as *u8, out5) 3004 } 3005 if pj_eq(cmd, "notes" as *u8) == 1 { 3006 if argc < 5 { pj_p("PROJECT notes needs <prefix> <pid> <tid> -- fail loud\n" as *u8); return 1 } 3007 let out6: *i64 = sys_mmap(32) as *i64 3008 return pj_cmd_notes(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, out6) 3009 } 3010 if pj_eq(cmd, "template" as *u8) == 1 { return pj_cmd_template(argc, argv) } 3011 if pj_eq(cmd, "snapshot" as *u8) == 1 { 3012 if argc < 6 { pj_p("PROJECT snapshot needs <prefix> <pid> <date> <outfile> -- fail loud\n" as *u8); return 1 } 3013 return pj_cmd_snapshot(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) 3014 } 3015 if pj_eq(cmd, "baseline" as *u8) == 1 { 3016 if argc < 4 { pj_p("PROJECT baseline needs <prefix> <pid> -- fail loud\n" as *u8); return 1 } 3017 let out7: *i64 = sys_mmap(32) as *i64 3018 return pj_cmd_baseline(argv[2] as *u8, argv[3] as *u8, out7) 3019 } 3020 if pj_eq(cmd, "burndown" as *u8) == 1 { 3021 if argc < 4 { pj_p("PROJECT burndown needs <prefix> <pid> -- fail loud\n" as *u8); return 1 } 3022 let out8: *i64 = sys_mmap(32) as *i64 3023 return pj_cmd_burndown(argv[2] as *u8, argv[3] as *u8, out8) 3024 } 3025 return pj_usage() 3026}