@ngockhoale/ukit 3.4.72 → 3.4.73
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 +5 -0
- package/manifests/documentation.yaml +1 -0
- package/package.json +1 -1
- package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +8 -15
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +6 -4
- package/template_project/.claude/ukit/runtime/operation-evidence.mjs +5 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 3.4.73 - 2026-10-28
|
|
6
|
+
|
|
7
|
+
- Git history proof tolerates unrelated untracked files; tracked/staged drift and explicit whole-tree clean obligations remain strict. No filename exemptions.
|
|
8
|
+
- Native ledger-to-Stop fixtures cover Claude Code/Codex/omp, repeated Stop and untracked shipped-source publication drift; not live-host certification.
|
|
9
|
+
|
|
5
10
|
## 3.4.72 - 2026-10-28
|
|
6
11
|
|
|
7
12
|
- Native evidence: reject background, masked and test-expression exits as operation/verification proof; retain supported foreground controls across shared Claude Code/Codex/OMP fixtures. No live-host certification.
|
|
@@ -4,6 +4,7 @@ description: >-
|
|
|
4
4
|
Machine-readable ownership/source registry for every documentation surface.
|
|
5
5
|
Hand-authored per SPEC §7; enforced by tests/consistency/docRegistry.test.js.
|
|
6
6
|
entries:
|
|
7
|
+
- {id: post-3472-git, path: docs/analysis/POST-3472-GIT-OPERATION-VERIFICATION.md, class: canonical, audience: [maintainer], owner: release, source_of_truth: docs/analysis/POST-3472-GIT-OPERATION-VERIFICATION.md, merge_strategy: none, load_policy: on-demand, validation: [manual], archive_policy: never}
|
|
7
8
|
- {id: release-3472, path: docs/analysis/RELEASE-3472-EVIDENCE-INTEGRITY.md, class: canonical, audience: [maintainer, agent], owner: release, source_of_truth: docs/analysis/RELEASE-3472-EVIDENCE-INTEGRITY.md, merge_strategy: none, load_policy: on-demand, validation: [manual], archive_policy: never}
|
|
8
9
|
- id: systematic-loop-audit-2026-10-26
|
|
9
10
|
path: docs/analysis/SYSTEMATIC-LOOP-AUDIT-2026-10-26.md
|
package/package.json
CHANGED
|
@@ -50,17 +50,12 @@ const SERVICE_MUTATION = /^(?:docker(?:\s+compose)?|docker-compose)\s+(?:restart
|
|
|
50
50
|
// Remote authority, not stale tracking refs: gate binds sha to clean live HEAD/tracking
|
|
51
51
|
// remote. Native mutation success remains a separate requirement.
|
|
52
52
|
const GIT_LS_REMOTE = /^git\s+ls-remote\s+(\S+)\s+(refs\/heads\/\S+)\s*$/;
|
|
53
|
-
// Native GitHub
|
|
54
|
-
// (like ls-remote asks the Git remote). Strict: unmasked single command, BOTH JSON fields present, enum
|
|
55
|
-
// visibility — the gate then binds the observed url to the gate-time Git remote URL
|
|
56
|
-
// (ledger.gitAuthority.remoteUrl), so a masked/public/wrong-repo reply never completes publication.
|
|
53
|
+
// Native GitHub visibility needs both fields; the gate binds the observed URL to the live remote.
|
|
57
54
|
const GH_REPO_VIEW = /^gh\s+repo\s+view(?:\s+(\S+))?\s+--json\s+([A-Za-z0-9_,]+)\s*$/;
|
|
58
55
|
const GH_VISIBILITY = /^(?:PUBLIC|PRIVATE|INTERNAL)$/;
|
|
59
56
|
const DRY_RUN = /(?:^|\s)(?:--dry-run|--check|--no-act|-n)(?:\s|=|$)/;
|
|
60
57
|
const CURL = /^curl\s/;
|
|
61
|
-
//
|
|
62
|
-
// exit status is the next command's, so it must not turn an otherwise single, checked command into a "masked"
|
|
63
|
-
// compound. Only ONE leading cd is dropped; any other `;`, `||`, pipe or extra segment still masks.
|
|
58
|
+
// Drop one leading cd; remaining separators still mask native exit attribution.
|
|
64
59
|
const LEADING_CD = /^\s*cd\s+(?:"[^"\n$`\\]*"|'[^'\n]*'|[^\s;&|<>$`()]+)\s*(?:;|&&)\s*(?=\S)/;
|
|
65
60
|
export function stripLeadingCd(command) {
|
|
66
61
|
return typeof command === 'string' ? command.replace(LEADING_CD, '') : command;
|
|
@@ -154,7 +149,10 @@ export function bashOperationReceipt(rawCommand, response, transportSuccess, goa
|
|
|
154
149
|
const wholeTree = kind !== 'status' || !/\s--\s|\s--porcelain=\S*\s+\S|(?:--porcelain|-s|--short)\s+[^\s-]/.test(seg);
|
|
155
150
|
if (!wholeTree || (wanted && kind !== wanted)) continue;
|
|
156
151
|
role = 'state';
|
|
157
|
-
|
|
152
|
+
const historyOnly = required.length > 0 && required.every((verb) => ['commit', 'push', 'tag'].includes(verb));
|
|
153
|
+
const untrackedOnly = /^git\s+status\s+--porcelain\s*$/.test(seg)
|
|
154
|
+
&& text.split('\n').every((line) => !line || line.startsWith('?? '));
|
|
155
|
+
proven = kind === 'status' ? hasOutputField && (text === '' || (historyOnly && untrackedOnly)) : true;
|
|
158
156
|
} else if (GIT_VERB.test(seg) && role !== 'state') {
|
|
159
157
|
role = 'mutation';
|
|
160
158
|
target = seg.match(GIT_VERB)[1];
|
|
@@ -177,15 +175,10 @@ export function bashOperationReceipt(rawCommand, response, transportSuccess, goa
|
|
|
177
175
|
}
|
|
178
176
|
}
|
|
179
177
|
if (!role) return null;
|
|
180
|
-
//
|
|
181
|
-
// compound/masked chain the stdout of its parts is mixed, so it can neither prove nor refute the state and
|
|
182
|
-
// must not overwrite earlier authoritative evidence. (A standalone dirty status still records as failed.)
|
|
178
|
+
// Mixed/masked stdout cannot prove or refute state; standalone dirty status still fails.
|
|
183
179
|
if (domain === 'git-integrity' && role === 'state'
|
|
184
180
|
&& (masked || real.filter((seg) => seg && !/^cd\s+\S+$/.test(seg)).length !== 1)) return null;
|
|
185
|
-
//
|
|
186
|
-
// state receipt (no github field) so it invalidates any earlier PRIVATE proof until a fresh successful
|
|
187
|
-
// view lands — the postcondition must hold at the LATEST check, not at some earlier one. Other git
|
|
188
|
-
// state commands keep the rule that a transport failure is unattributable and records nothing.
|
|
181
|
+
// Failed GitHub views invalidate prior visibility; other failed Git transports mint no state proof.
|
|
189
182
|
if (domain === 'git-integrity' && role === 'state' && transportSuccess !== true && !ghObserved) return null;
|
|
190
183
|
const ok = transportSuccess === true && !masked && Boolean(scope) && (role === 'mutation' || proven);
|
|
191
184
|
return { kind: 'operation', success: ok, transportSuccess: transportSuccess === true, harness,
|
|
@@ -3780,15 +3780,17 @@ function gitAuthority(cwd) {
|
|
|
3780
3780
|
const remote = gitOutput(['config', `branch.${branch}.remote`], cwd);
|
|
3781
3781
|
const remoteUrl = remote ? gitOutput(['config', `remote.${remote}.url`], cwd) : null;
|
|
3782
3782
|
// gitOutput conflates empty output with failure; cleanliness needs an explicit successful status read.
|
|
3783
|
-
let clean = false;
|
|
3783
|
+
let clean = false; let trackedClean = false;
|
|
3784
3784
|
try {
|
|
3785
|
-
|
|
3785
|
+
const status = execFileSync('git', ['status', '--porcelain'], {
|
|
3786
3786
|
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000,
|
|
3787
|
-
})
|
|
3787
|
+
});
|
|
3788
|
+
clean = status.trim() === '';
|
|
3789
|
+
trackedClean = status.split('\n').every((line) => !line || line.startsWith('?? '));
|
|
3788
3790
|
} catch { /* an unreadable working tree is not clean */ }
|
|
3789
3791
|
// Annotated tags are peeled by --points-at; this state corroborates successful native tag execution.
|
|
3790
3792
|
const tagsAtHead = (gitOutput(['tag', '--points-at', 'HEAD'], cwd) || '').split('\n').map((t) => t.trim()).filter(Boolean).slice(0, 16);
|
|
3791
|
-
return { head, branch, remote, remoteUrl, tagsAtHead, clean };
|
|
3793
|
+
return { head, branch, remote, remoteUrl, tagsAtHead, clean, trackedClean };
|
|
3792
3794
|
}
|
|
3793
3795
|
function withGitAuthority(ledger, cwd) {
|
|
3794
3796
|
const gitAuth = ledger && Object.keys(ledger).length ? gitAuthority(cwd) : null;
|
|
@@ -49,10 +49,7 @@ export function foldOperationEvidence(bank = {}, receipt, ledger) {
|
|
|
49
49
|
if (receipt.toolUseId && (seen.includes(receipt.toolUseId) || Object.values(bank).some((entry) =>
|
|
50
50
|
[entry?.mutation, entry?.state].some((proof) => proof?.toolUseId === receipt.toolUseId)))) return bank;
|
|
51
51
|
if (receipt.toolUseId && (!bounded(receipt.toolUseId) || seen.length >= 256)) return { ...bank, overflow: true };
|
|
52
|
-
//
|
|
53
|
-
// it: both are `state` receipts under one key, so the later status used to overwrite the remote proof. The
|
|
54
|
-
// proof is stamped with the sequence it was observed at and carried over only while no mutation (failed or
|
|
55
|
-
// not) of this domain/scope was recorded after it; the gate still compares its sha to live HEAD.
|
|
52
|
+
// Carry remote parity across successful state checks only until the next mutation; gate checks live HEAD.
|
|
56
53
|
const lastMutation = Object.values(bank).reduce((max, entry) => {
|
|
57
54
|
const m = entry?.mutation;
|
|
58
55
|
return m?.operation?.domain === op.domain && m.operation.scope === op.scope && Number.isSafeInteger(m.sequence) ? Math.max(max, m.sequence) : max;
|
|
@@ -127,15 +124,15 @@ export function operationEvidenceStatus(goal, ledger = {}) {
|
|
|
127
124
|
if (goal.visibility === 'PRIVATE' && !(validGithubView(op?.github)
|
|
128
125
|
&& op.github.visibility === 'PRIVATE' && githubRepository(auth?.remoteUrl) !== null
|
|
129
126
|
&& githubRepository(op.github.url) === githubRepository(auth.remoteUrl))) return incomplete;
|
|
130
|
-
|
|
127
|
+
// Commit/push prove committed history; an explicit clean goal still owns the whole tree.
|
|
128
|
+
const cleanEnough = auth?.clean === true || (auth?.trackedClean === true && !requiredTargets.includes('clean'));
|
|
129
|
+
if (goal.domain === 'git-integrity' && executed && validRemoteSync(op.remoteSync) && cleanEnough
|
|
131
130
|
&& requiredTargets.every((t) => t === 'commit' || t === 'push' || t === 'clean' || (t === 'tag' && Array.isArray(auth.tagsAtHead) && auth.tagsAtHead.length > 0))
|
|
132
131
|
&& attempts.every((r) => (Number.isSafeInteger(op.remoteSync.observedSequence) ? op.remoteSync.observedSequence : observation.sequence) > r.sequence)
|
|
133
132
|
&& op.remoteSync.sha === auth.head && op.remoteSync.ref === `refs/heads/${auth.branch}`
|
|
134
133
|
&& op.remoteSync.remote === auth.remote) return { complete: true, operationProof: 'remote-parity' };
|
|
135
|
-
// Push
|
|
134
|
+
// Push needs remote proof; divergent observations cannot fall back to local checks.
|
|
136
135
|
if (goal.domain === 'git-integrity' && requiredTargets.includes('push')) return incomplete;
|
|
137
|
-
// A remote observation that disagrees with live local truth is divergence, not a verified state: it can never
|
|
138
|
-
// be discharged by the fallback below just because a push command exited 0.
|
|
139
136
|
if (goal.domain === 'git-integrity' && validRemoteSync(op.remoteSync) && auth
|
|
140
137
|
&& (op.remoteSync.sha !== auth.head || op.remoteSync.ref !== `refs/heads/${auth.branch}`
|
|
141
138
|
|| (auth.remote && op.remoteSync.remote !== auth.remote))) return incomplete;
|