@ngockhoale/ukit 3.4.49 → 3.4.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.50 - 2026-10-21
6
+
7
+ **Verification exit-proof integrity (long-run OMP field audit).**
8
+ - Fix execution-ledger false PASS receipts: a bare `pipefail`/`PIPESTATUS` word (e.g. `echo pipefail`) no longer exempts a masked pipe; only real `set -o pipefail` before the pipe or an immediate `exit ${PIPESTATUS[n]}` with the correct index proves the runner status.
9
+ - A runner followed by `|| …`, backgrounded with `&`, inside a `for`/`while`/`case` flow, behind `bash -c` hiding a masked pipe, or followed by `exit 0`/`true`/`:`/leading `!` is recorded as `exitStatusUnproven` (not passing). Claude `tool_response.interrupted:true` now counts as failure.
10
+ - Bare runners, `cd repo; runner`, `&&` chains, real pipefail and terminal subshell/group runners stay accepted. Sequential consumers (including `; echo "exit=$?"`) and `if runner; then echo ok; fi` no longer receive PASS credit: recognizing a runner attempt does not prove its exit. Loop keywords as runner arguments (`vitest -t case`) are not loops.
11
+ - Audit report: `UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md`. Verified locally on macOS/Node 22 only; OMP live host-ordering attribution remains UNVERIFIED. No CI dispatch.
12
+
5
13
  ## 3.4.49 - 2026-10-19
6
14
 
7
15
  **Conditional consumer verification intent closure.**
@@ -71,6 +71,17 @@ entries:
71
71
  validation: [manual]
72
72
  archive_policy: never
73
73
  notes: release record
74
+ - id: root-long-run-orchestration-audit
75
+ path: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
76
+ class: canonical
77
+ audience: [owner, maintainer]
78
+ owner: runtime
79
+ source_of_truth: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
80
+ merge_strategy: none
81
+ load_policy: on-demand
82
+ validation: [manual, test:tests/hooks/executionLedger.test.js, test:tests/hooks/verificationExitProof.test.js]
83
+ archive_policy: replace-with-snapshot
84
+ notes: evidence-based long-run field audit; current reproductions are distinct from incident attribution
74
85
  - id: registry-self
75
86
  path: manifests/documentation.yaml
76
87
  class: canonical
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.49",
3
+ "version": "3.4.50",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -460,6 +460,10 @@ function explicitError(payload = {}) {
460
460
  return [
461
461
  payload.isError,
462
462
  payload.is_error,
463
+ payload.interrupted,
464
+ payload.tool_output?.interrupted,
465
+ payload.tool_result?.interrupted,
466
+ payload.tool_response?.interrupted,
463
467
  payload.tool_output?.isError,
464
468
  payload.tool_output?.is_error,
465
469
  payload.tool_result?.isError,
@@ -530,8 +534,8 @@ function extractExitCode(payload = {}) {
530
534
  return null;
531
535
  }
532
536
 
533
- // The trailing boundary accepts shell terminators (`;`, `&`, `|`, `)`) so
534
- // `yarn release:verify; echo "exit=$?"` — the real-exit-code idiom — still mints a receipt.
537
+ // Recognize runner presence separately from exit proof. A trailing echo of `$?`
538
+ // can report a status in text but cannot make the wrapper's exit authoritative.
535
539
  // Blank out quoted literals so `echo '(jest passed)'` / `git commit -m "run yarn test"` do not
536
540
  // look like a runner invocation. A `bash|sh|zsh -c '<script>'` wrapper really executes its
537
541
  // quoted script, so that script is tested on its own (bounded recursion).
@@ -584,71 +588,94 @@ function isVerificationCommand(command, depth = 0) {
584
588
  ].some((pattern) => pattern.test(text));
585
589
  }
586
590
 
587
- // Split a shell command into pipelines of stages (quote/escape aware). `&&`, `||`, `;` and
588
- // newlines separate pipelines; a lone `|` (or `|&`) separates stages. Redirections such as
589
- // `2>&1` / `&>` are not separators.
590
- function shellPipelineStages(command) {
591
- const text = String(command || '');
592
- const pipelines = [];
593
- let stages = [];
591
+ // Bounded shell exit-proof recognition, not a shell interpreter. Keep separators so a
592
+ // later echo/OR/background job cannot turn a child runner failure into verification PASS.
593
+ function verificationShellUnits(command) {
594
+ const units = [];
594
595
  let buffer = '';
595
596
  let quote = null;
596
597
  let escaped = false;
597
- const closeStage = () => {
598
- if (buffer.trim()) stages.push(buffer.trim());
599
- buffer = '';
600
- };
601
- const closePipeline = () => {
602
- closeStage();
603
- if (stages.length) pipelines.push(stages);
604
- stages = [];
605
- };
606
- for (let i = 0; i < text.length; i += 1) {
607
- const char = text[i];
598
+ const close = (separator) => { units.push({ text: buffer.trim(), separator }); buffer = ''; };
599
+ for (let i = 0; i < command.length; i += 1) {
600
+ const char = command[i];
608
601
  if (escaped) { escaped = false; buffer += char; continue; }
609
- if (char === '\\') { escaped = true; buffer += char; continue; }
602
+ if (char === '\\' && quote !== "'") { escaped = true; buffer += char; continue; }
610
603
  if (quote) { if (char === quote) quote = null; buffer += char; continue; }
611
604
  if (char === "'" || char === '"') { quote = char; buffer += char; continue; }
612
- if (char === '|') {
613
- if (text[i + 1] === '|') { closePipeline(); i += 1; continue; }
614
- closeStage();
615
- if (text[i + 1] === '&') i += 1;
605
+ if (char === '#' && (!buffer || /\s$/.test(buffer))) {
606
+ while (i < command.length && command[i] !== '\n') i += 1;
607
+ if (i < command.length) close(';');
616
608
  continue;
617
609
  }
618
- if (char === '&') {
619
- if (text[i + 1] === '&') { closePipeline(); i += 1; continue; }
620
- buffer += char; // `2>&1`, `&>file`, trailing background `&`
610
+ if (char === '&' && (/[<>]$/.test(buffer) || command[i + 1] === '>')) { buffer += char; continue; }
611
+ if (char === '|' || char === '&') {
612
+ const next = command[i + 1];
613
+ const separator = next === char ? char + next : char === '|' && next === '&' ? '|&' : char;
614
+ close(separator);
615
+ if (separator.length === 2) i += 1;
621
616
  continue;
622
617
  }
623
- if (char === ';' || char === '\n') { closePipeline(); continue; }
618
+ if (char === ';' || char === '\n' || char === '\r') { close(';'); continue; }
624
619
  buffer += char;
625
620
  }
626
- closePipeline();
627
- return pipelines;
621
+ if (quote || escaped) return null;
622
+ if (buffer.trim()) close('');
623
+ return units.filter((unit) => unit.text || unit.separator !== ';');
628
624
  }
629
625
 
630
- // A shell pipeline exits with its LAST stage's status, so `npx vitest run | tail` is exit 0
631
- // even when vitest failed. A verification command in a non-final stage therefore has an
632
- // unproven exit status — unless the command enables `pipefail` or reads `PIPESTATUS`.
633
- // Returns the masked verification stage (string) or null.
634
- function maskedVerificationStage(command) {
635
- const text = String(command || '');
636
- if (/\bpipefail\b|\bPIPESTATUS\b|\bpipestatus\b/.test(text)) return null;
637
- for (const stages of shellPipelineStages(text)) {
638
- for (const stage of stages.slice(0, -1)) {
639
- if (isVerificationCommand(stage)) return stage;
626
+ function maskedVerificationStage(command, depth = 0) {
627
+ const text = String(command || '').trim();
628
+ const units = verificationShellUnits(text);
629
+ if (!units?.length || depth > 2) return text;
630
+ let pipefail = false;
631
+ let sawRunner = false;
632
+ for (let i = 0; i < units.length; i += 1) {
633
+ const unit = units[i];
634
+ const plain = maskQuotedLiterals(unit.text);
635
+ // Unsupported flow may skip a runner or discard an earlier iteration's failure.
636
+ // Parens, braces and `if` are not interpreted here. A sequential consumer must not
637
+ // turn the status of an earlier runner into PASS, even when it prints that status.
638
+ if (sawRunner && units[i - 1]?.separator === ';' && !/^[)}]+$/.test(plain)) return unit.text;
639
+ // Command position only: `vitest -t case` is an argument, not a loop.
640
+ if (/^(?:for|while|until|case|function|done)\b/.test(plain)) return unit.text;
641
+ // A later `exit 0`/`true`/`:` replaces the runner's status; a leading `!` inverts it.
642
+ if (sawRunner && /^(?:exit\s+0|true|:)$/.test(plain)) return unit.text;
643
+ if (/^!\s/.test(plain) && isVerificationCommand(unit.text)) return unit.text;
644
+ if (unit.separator === '||' || unit.separator === '&') return unit.text;
645
+ const enable = /^set\s+-(?:[a-z]*o)\s+pipefail$/.test(plain);
646
+ const disable = /^set\s+\+(?:[a-z]*o)\s+pipefail$/.test(plain);
647
+ if (enable || disable) pipefail = enable;
648
+ let runner = isVerificationCommand(unit.text);
649
+ // Only an entire simple shell wrapper can inherit the inner script's proof.
650
+ const wrapper = unit.text.match(/^(?:bash|sh|zsh|dash|ksh)\s+(?:-o\s+\w+\s+|-[a-z]+\s+)*(-[a-z]*c)\s+(?:'([^']*)'|"((?:[^"\\]|\\.)*)")$/);
651
+ if (runner && wrapper) {
652
+ if (maskedVerificationStage(wrapper[2] ?? wrapper[3] ?? '', depth + 1)) return unit.text;
653
+ } else if (runner && /(?:^|\s)(?:bash|sh|zsh|dash|ksh)\s/.test(plain)) return unit.text;
654
+ if (runner) sawRunner = true;
655
+ if (unit.separator === '|' || unit.separator === '|&') {
656
+ if (runner && !pipefail) {
657
+ // A single runner's immediate Bash PIPESTATUS read can propagate its status.
658
+ let end = i;
659
+ while (units[end]?.separator === '|' || units[end]?.separator === '|&') end += 1;
660
+ const proof = units[end + 1];
661
+ const expected = `exit ${'${'}PIPESTATUS[${i - (units.slice(0, i).findLastIndex((u) => u.separator !== '|' && u.separator !== '|&') + 1)}]}`;
662
+ if (units[end]?.separator !== ';' || proof?.text !== expected || proof.separator
663
+ || units.slice(i + 1, end + 1).some((u) => isVerificationCommand(u.text))) return unit.text;
664
+ return null;
665
+ }
666
+ continue;
640
667
  }
641
668
  }
642
- return null;
669
+ return sawRunner ? null : text;
643
670
  }
644
671
 
645
672
  // Announce through the in-proc hook emitter only (never fd 1 from library callers).
646
673
  function noteUnprovenExitStatus(stage) {
647
674
  if (!ledgerMessageEmitter) return;
648
675
  const message =
649
- `UKit execution-ledger: \`${stage}\` ran in a pipeline, so the exit status belongs to the last pipe stage `
650
- + '(e.g. tail/grep), not the test runner — it is NOT counted as passing verification. '
651
- + 'Re-run with `set -o pipefail; <cmd> | tail` (or without the pipe) to prove the result.';
676
+ `UKit execution-ledger: runner exit status is unproven for \`${stage}\` — `
677
+ + 'the shell wrapper may mask or skip the runner; it is NOT counted as passing verification. '
678
+ + 'Re-run the bare verification command in the same workspace, without loops, backgrounding or status-swallowing consumers.';
652
679
  try {
653
680
  ledgerMessageEmitter(`${JSON.stringify({ systemMessage: message })}\n`);
654
681
  } catch { /* an emitter failure never loses the ledger event */ }