@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
|
@@ -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
|
-
//
|
|
534
|
-
//
|
|
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
|
-
//
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
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
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
614
|
-
|
|
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
|
-
|
|
620
|
-
|
|
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') {
|
|
618
|
+
if (char === ';' || char === '\n' || char === '\r') { close(';'); continue; }
|
|
624
619
|
buffer += char;
|
|
625
620
|
}
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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:
|
|
650
|
-
+ '
|
|
651
|
-
+ 'Re-run
|
|
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 */ }
|