weir-harness 1.2.0 → 1.2.1
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/lib/client.blackboard-model.js +172 -56
- package/lib/client.capability-manager.js +0 -1
- package/lib/client.capability-model.js +378 -31
- package/lib/client.chain-model.js +80 -0
- package/lib/client.disabled-categories-editor.js +22 -0
- package/lib/client.edit-lock-maintenance.js +239 -246
- package/lib/client.edit-lock-panel.js +110 -0
- package/lib/client.hash-edit-model.js +48 -0
- package/lib/client.js +267 -1549
- package/lib/client.lsp-model.js +92 -0
- package/lib/client.lsp-panel.js +11 -16
- package/lib/client.lsp-toggle.js +18 -0
- package/lib/client.notify-permissions.js +112 -20
- package/lib/client.notify-web.js +14 -11
- package/lib/client.robash-model.js +26 -0
- package/lib/client.settings-page.js +287 -17
- package/lib/client.worktree-model.js +225 -0
- package/package.json +1 -1
- package/src/blackboard/durable.js +12 -0
- package/src/blackboard/index.js +2 -2
- package/src/blackboard/kernel.js +23 -6
- package/src/blackboard/notify.js +10 -3
- package/src/blackboard/remote.js +31 -13
- package/src/blackboard/storage.js +13 -5
- package/src/capabilities/apply-engine.js +189 -101
- package/src/capabilities/defaults-transaction.js +136 -1
- package/src/capabilities/lifecycle-snapshot.js +83 -204
- package/src/capabilities/mcp-admission.js +1 -23
- package/src/capabilities/mcp-drain.js +30 -19
- package/src/capabilities/mcp-facade.js +23 -3
- package/src/capabilities/preset-library.js +247 -0
- package/src/capabilities/preset-package.js +212 -1
- package/src/capabilities/same-name-policy.js +3 -41
- package/src/capabilities/selection-notify.js +324 -21
- package/src/capabilities/skill-selection-plugin.js +315 -729
- package/src/capabilities/store/checked-contract.d.ts +5 -0
- package/src/capabilities/store/fs-adapter.js +52 -0
- package/src/capabilities/store/lock.js +86 -0
- package/src/capabilities/store/paths.js +5 -0
- package/src/capabilities/store/store.js +105 -5
- package/src/delegate/group-coordinator.js +1 -17
- package/src/delegate/index.js +4 -2
- package/src/delegate/rehydrate.js +2 -1
- package/src/delegate/robash-guard-core.js +2 -2
- package/src/delegate/supervision-mount.js +5 -4
- package/src/delegate/tool.js +26 -3
- package/src/{shared → delegate}/whitelist-defaults.js +2 -2
- package/src/edit-lock/admin-ledger.js +8 -11
- package/src/edit-lock/admin-recovery.js +2 -1
- package/src/edit-lock/admission.js +12 -2
- package/src/edit-lock/binding-guidance.js +28 -10
- package/src/edit-lock/binding-machine.js +312 -0
- package/src/edit-lock/derived-facts.js +67 -0
- package/src/edit-lock/index.js +44 -244
- package/src/edit-lock/inspect.js +113 -1
- package/src/edit-lock/lifecycle.js +8 -5
- package/src/edit-lock/limits.js +61 -0
- package/src/edit-lock/maintenance.js +5 -2
- package/src/edit-lock/manager.js +6 -2
- package/src/edit-lock/operation-history.js +1 -1
- package/src/edit-lock/peer-client.js +9 -2
- package/src/edit-lock/peer-transport.js +7 -1
- package/src/edit-lock/remote.js +11 -4
- package/src/edit-lock/state.js +2 -4
- package/src/edit-lock/view.js +10 -11
- package/src/lsp/admin.js +2 -24
- package/src/lsp/index.js +1 -1
- package/src/lsp/manager.js +1 -1
- package/src/notify/permissions-admin.js +1 -1
- package/src/{lsp/child-process.js → process/index.js} +12 -4
- package/src/settings/env-admin.js +1 -1
- package/src/settings/index.js +1 -1
- package/src/settings/sections.js +0 -54
- package/src/shared/audit.js +5 -0
- package/src/shared/http-json.js +28 -0
- package/src/{delegate/audit-readers.js → shared/terminal-evidence.js} +34 -4
- package/src/worktree/errors.js +3 -0
- package/src/worktree/index.js +1 -2
- package/src/worktree/lane-core.js +641 -0
- package/src/worktree/lane-views.js +147 -0
- package/src/worktree/lane-workflows.js +853 -0
- package/src/worktree/lanes.js +24 -1513
- package/src/worktree/runner.js +5 -4
- package/src/capabilities/content-pin.js +0 -88
- package/src/capabilities/content-refresh.js +0 -331
- package/src/capabilities/distribution-manifest.js +0 -78
- package/src/capabilities/isolated-exec.js +0 -106
- package/src/capabilities/publisher.js +0 -129
- package/src/capabilities/skill-admission.js +0 -181
- package/src/capabilities/update-lifecycle.js +0 -106
- package/src/edit-lock/remote-service.js +0 -28
- package/src/worktree/contract-guidance.test.js +0 -100
|
@@ -10,7 +10,9 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
// Edit Lock maintenance panel (settings "Editing" group): the
|
|
11
11
|
// profile-wide switch status (SAVED vs actually mounted, restart
|
|
12
12
|
// semantics) plus read-only authority inspection per server-derived
|
|
13
|
-
// domain root, over
|
|
13
|
+
// domain root, over the composition root's HTTP channel adapter
|
|
14
|
+
// (props.channel: (endpoint, payload?) => Promise<{ ok: true, value } |
|
|
15
|
+
// { ok: false, error }>, never rejecting; "/api/weir-edit-lock/maintenance/*").
|
|
14
16
|
// The panel never sends a path the server did not list first, and it
|
|
15
17
|
// offers recovery only while the manager is mounted. The switch itself
|
|
16
18
|
// field. Style constants are verbatim copies of the settings-page
|
|
@@ -28,136 +30,39 @@ window.__ModuleLoader__.load({
|
|
|
28
30
|
const chainButtonStyle = { background: "none", border: "none", cursor: "pointer", fontSize: "12px", color: "var(--dsw-alias-label-secondary)", textDecoration: "underline" };
|
|
29
31
|
const domainRowStyle = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", padding: "6px 0" };
|
|
30
32
|
const monoStyle = { fontSize: "12px", lineHeight: "16px", fontFamily: "monospace", wordBreak: "break-all" };
|
|
31
|
-
// ---- Online administrative recovery (
|
|
32
|
-
// The
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let code = text.charCodeAt(i);
|
|
55
|
-
if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length) {
|
|
56
|
-
const next = text.charCodeAt(i + 1);
|
|
57
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
58
|
-
code = 0x10000 + ((code - 0xd800) << 10) + (next - 0xdc00);
|
|
59
|
-
i++;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (code < 0x80) bytes.push(code);
|
|
63
|
-
else if (code < 0x800) bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
|
|
64
|
-
else if (code < 0x10000) bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
|
65
|
-
else bytes.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
|
66
|
-
}
|
|
67
|
-
return bytes;
|
|
68
|
-
}
|
|
69
|
-
/** @param {number} x @param {number} n */
|
|
70
|
-
function rotr(x, n) { return ((x >>> n) | (x << (32 - n))) >>> 0; }
|
|
71
|
-
const SHA256_K = [
|
|
72
|
-
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
73
|
-
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
74
|
-
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
75
|
-
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
76
|
-
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
77
|
-
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
78
|
-
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
79
|
-
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
80
|
-
];
|
|
81
|
-
/** Pure-JS SHA-256 of the UTF-8 encoding, lowercase hex (the client has
|
|
82
|
-
* no node:crypto; the chunk test pins this against the server digest).
|
|
83
|
-
* @param {string} text @returns {string} */
|
|
84
|
-
function sha256Hex(text) {
|
|
85
|
-
const bytes = utf8Bytes(text);
|
|
86
|
-
const bitLength = bytes.length * 8;
|
|
87
|
-
bytes.push(0x80);
|
|
88
|
-
while (bytes.length % 64 !== 56) bytes.push(0);
|
|
89
|
-
for (let i = 7; i >= 0; i--) bytes.push(Math.floor(bitLength / 2 ** (8 * i)) % 256);
|
|
90
|
-
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a;
|
|
91
|
-
let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;
|
|
92
|
-
const w = new Array(64).fill(0);
|
|
93
|
-
for (let block = 0; block < bytes.length; block += 64) {
|
|
94
|
-
for (let i = 0; i < 16; i++) w[i] = ((bytes[block + 4 * i] << 24) | (bytes[block + 4 * i + 1] << 16) | (bytes[block + 4 * i + 2] << 8) | bytes[block + 4 * i + 3]) >>> 0;
|
|
95
|
-
for (let i = 16; i < 64; i++) {
|
|
96
|
-
const s0 = (rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ (w[i - 15] >>> 3)) >>> 0;
|
|
97
|
-
const s1 = (rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ (w[i - 2] >>> 10)) >>> 0;
|
|
98
|
-
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
|
|
99
|
-
}
|
|
100
|
-
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
|
|
101
|
-
for (let i = 0; i < 64; i++) {
|
|
102
|
-
const S1 = (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) >>> 0;
|
|
103
|
-
const ch = ((e & f) ^ (~e & g)) >>> 0;
|
|
104
|
-
const t1 = (h + S1 + ch + SHA256_K[i] + w[i]) >>> 0;
|
|
105
|
-
const S0 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) >>> 0;
|
|
106
|
-
const maj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0;
|
|
107
|
-
const t2 = (S0 + maj) >>> 0;
|
|
108
|
-
h = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0;
|
|
109
|
-
}
|
|
110
|
-
h0 = (h0 + a) >>> 0; h1 = (h1 + b) >>> 0; h2 = (h2 + c) >>> 0; h3 = (h3 + d) >>> 0;
|
|
111
|
-
h4 = (h4 + e) >>> 0; h5 = (h5 + f) >>> 0; h6 = (h6 + g) >>> 0; h7 = (h7 + h) >>> 0;
|
|
112
|
-
}
|
|
113
|
-
return [h0, h1, h2, h3, h4, h5, h6, h7].map((word) => word.toString(16).padStart(8, "0")).join("");
|
|
114
|
-
}
|
|
115
|
-
/** The exact offline confirmation string for one recovery scope.
|
|
116
|
-
* @param {{root: string, owner: string, expectedRevision: number, operationIds: string[]}} scope */
|
|
117
|
-
function recoveryConfirmation(scope) {
|
|
118
|
-
return `ADMIN OVERRIDE ${sha256Hex(canonicalJson({ root: scope.root, owner: scope.owner, expectedRevision: scope.expectedRevision, operationIds: [...scope.operationIds].sort(), risk: LATE_WRITER_RISK }))}`;
|
|
119
|
-
}
|
|
33
|
+
// ---- Online administrative recovery (proposals from the host) ------
|
|
34
|
+
// The recovery derivation — eligibility, the scoped confirmation
|
|
35
|
+
// digest, the failing-precondition diagnosis — lives HOST-side
|
|
36
|
+
// (src/edit-lock/inspect.js recoveryProposals), computed from the same
|
|
37
|
+
// parsed authority image the inspection summarizes. The panel renders
|
|
38
|
+
// the server proposals and submits their scope and confirmation
|
|
39
|
+
// VERBATIM; it never re-derives a safety-sensitive rule. The manager
|
|
40
|
+
// re-verifies every precondition at commit time either way.
|
|
41
|
+
/** The unmet reason CODES (schemaVersion 1) mapped to dictionary keys;
|
|
42
|
+
* an unknown code falls back to a generic message naming the code. */
|
|
43
|
+
const RECOVERY_REASON_KEYS = {
|
|
44
|
+
"disposition-recorded": "editLockMaintRecoverReasonDispositionRecorded",
|
|
45
|
+
"owner-unknown": "editLockMaintRecoverReasonOwnerUnknown",
|
|
46
|
+
"not-interrupted": "editLockMaintRecoverReasonNotInterrupted",
|
|
47
|
+
"prepared-outstanding": "editLockMaintRecoverReasonPreparedOutstanding",
|
|
48
|
+
"publishing-outstanding": "editLockMaintRecoverReasonPublishingOutstanding",
|
|
49
|
+
"no-unresolved": "editLockMaintRecoverReasonNoUnresolved",
|
|
50
|
+
};
|
|
51
|
+
/** The fail-closed gate (a new client meeting an old or evolved host):
|
|
52
|
+
* only a schemaVersion-1 proposals payload is rendered at all; anything
|
|
53
|
+
* else disables the online recovery action with refresh guidance.
|
|
54
|
+
* @param {any} value the inspect response value */
|
|
55
|
+
const supportedProposals = (value) => value?.recoveryProposals?.schemaVersion === 1 ? value.recoveryProposals : null;
|
|
120
56
|
/** Recovery state is scoped exactly like the authority: per domain root
|
|
121
57
|
* AND owner. Session ids are not unique across independent domains, so
|
|
122
58
|
* one root's completed recovery must never disable another root's. */
|
|
123
59
|
const recoveryKey = (root, owner) => `${root}\n${owner}`;
|
|
124
|
-
/** Eligible one-click recoveries from one inspection: each interrupted
|
|
125
|
-
* owner whose every unresolved operation is a still-blocking unknown.
|
|
126
|
-
* @param {string} root @param {any} snapshot */
|
|
127
|
-
function recoveryCandidates(root, snapshot) {
|
|
128
|
-
const interrupted = new Set((Array.isArray(snapshot?.sessions) ? snapshot.sessions : [])
|
|
129
|
-
.filter((/** @type {any} */ session) => session?.interrupted === true)
|
|
130
|
-
.map((/** @type {any} */ session) => session.sessionId));
|
|
131
|
-
/** @type {Map<string, any[]>} */
|
|
132
|
-
const byOwner = new Map();
|
|
133
|
-
for (const op of Array.isArray(snapshot?.unresolved) ? snapshot.unresolved : []) {
|
|
134
|
-
const owner = op?.key?.sessionId;
|
|
135
|
-
if (typeof owner !== "string") continue;
|
|
136
|
-
const list = byOwner.get(owner) ?? [];
|
|
137
|
-
list.push(op);
|
|
138
|
-
byOwner.set(owner, list);
|
|
139
|
-
}
|
|
140
|
-
/** @type {{owner: string, scope: {root: string, owner: string, expectedRevision: number, operationIds: string[]}, confirmation: string}[]} */
|
|
141
|
-
const candidates = [];
|
|
142
|
-
// An owner with a prepared operation can never be recovered online
|
|
143
|
-
// (the manager requires every unresolved operation to be unknown), so
|
|
144
|
-
// offering the action would present a click that always refuses.
|
|
145
|
-
const preparedOwners = new Set((Array.isArray(snapshot?.prepared) ? snapshot.prepared : [])
|
|
146
|
-
.map((op) => op?.key?.sessionId)
|
|
147
|
-
.filter((owner) => typeof owner === "string"));
|
|
148
|
-
for (const [owner, ops] of byOwner) {
|
|
149
|
-
if (!interrupted.has(owner)) continue;
|
|
150
|
-
if (preparedOwners.has(owner)) continue;
|
|
151
|
-
if (!(ops.length > 0 && ops.every((op) => op?.phase === "unknown" && op?.admissionBlocked === true))) continue;
|
|
152
|
-
const scope = { root, owner, expectedRevision: snapshot.revision, operationIds: ops.map((op) => op.key.operationId).sort() };
|
|
153
|
-
candidates.push({ owner, scope, confirmation: recoveryConfirmation(scope) });
|
|
154
|
-
}
|
|
155
|
-
return candidates;
|
|
156
|
-
}
|
|
157
60
|
/** Recovery-UX zone copy (D1/D2). The entry dictionaries (client.js) are
|
|
158
|
-
* owned by the entry chunk, so the
|
|
159
|
-
* template-layer English per the text discipline;
|
|
160
|
-
*
|
|
61
|
+
* owned by the entry chunk, so the zone copy lives HERE as
|
|
62
|
+
* template-layer English per the text discipline; the unmet reason prose
|
|
63
|
+
* lives in the dictionaries below (server reason CODES are localized),
|
|
64
|
+
* while file paths, session ids and statuses stay dynamic instance
|
|
65
|
+
* content. */
|
|
161
66
|
const RECOVERY_UX = Object.freeze({
|
|
162
67
|
blockedHeader: "Blocked files — one-click force release",
|
|
163
68
|
blockedHint: "Opening the panel ran the read-only inspection automatically. Each row settles the holder's unresolved publication fence (when present) and releases the lock in one operator-confirmed step.",
|
|
@@ -167,88 +72,7 @@ window.__ModuleLoader__.load({
|
|
|
167
72
|
stateFence: "publication fence",
|
|
168
73
|
stateInterrupted: "interrupted",
|
|
169
74
|
stateActive: "active",
|
|
170
|
-
reasonNotInterrupted: "The holding session is not durably interrupted; the online administrative recovery can only override an interrupted historical owner. Stop the session first, or ask it to release the file.",
|
|
171
|
-
reasonPrepared: "The owner still has a prepared operation; recover to stable unknown history before an administrative override.",
|
|
172
|
-
reasonPublishing: "The owner still has an operation in the publishing phase; only unknown outcomes can be overridden online.",
|
|
173
|
-
reasonDisposition: "An administrative recovery is already recorded for this owner; admission for its operations is no longer blocked.",
|
|
174
|
-
reasonNoUnresolved: "The owner has no unresolved operations; an ordinary unlock may be enough.",
|
|
175
|
-
reasonUnknownSession: "The owner session is not recorded in this authority image.",
|
|
176
75
|
});
|
|
177
|
-
/** The first failing precondition of an ineligible blocking owner, in the
|
|
178
|
-
* manager's refusal order (revoked/disposition → owner unknown → not
|
|
179
|
-
* interrupted → prepared → publishing → no unresolved operations).
|
|
180
|
-
* @param {string} owner @param {any} snapshot @returns {string} */
|
|
181
|
-
function unmetReason(owner, snapshot) {
|
|
182
|
-
const unresolved = Array.isArray(snapshot?.unresolved) ? snapshot.unresolved : [];
|
|
183
|
-
const ownerOps = unresolved.filter((op) => op?.key?.sessionId === owner);
|
|
184
|
-
const prepared = (Array.isArray(snapshot?.prepared) ? snapshot.prepared : []).filter((op) => op?.key?.sessionId === owner);
|
|
185
|
-
const session = (Array.isArray(snapshot?.sessions) ? snapshot.sessions : []).find((row) => row?.sessionId === owner);
|
|
186
|
-
if (ownerOps.some((op) => op?.admissionBlocked === false)) return RECOVERY_UX.reasonDisposition;
|
|
187
|
-
if (!session) return RECOVERY_UX.reasonUnknownSession;
|
|
188
|
-
if (session.interrupted !== true) return RECOVERY_UX.reasonNotInterrupted;
|
|
189
|
-
if (prepared.length > 0) return RECOVERY_UX.reasonPrepared;
|
|
190
|
-
if (ownerOps.some((op) => op?.phase === "publishing")) return RECOVERY_UX.reasonPublishing;
|
|
191
|
-
return RECOVERY_UX.reasonNoUnresolved;
|
|
192
|
-
}
|
|
193
|
-
/** Recovery-UX diagnosis (D1/D2): the three-zone classification the panel
|
|
194
|
-
* renders from one inspection. Eligibility is recoveryCandidates — the
|
|
195
|
-
* SAME scope and confirmation algorithm — and every ineligible blocking
|
|
196
|
-
* owner carries the named failing precondition. Blocking artifacts are
|
|
197
|
-
* retained locks plus still-blocking (admissionBlocked) unresolved
|
|
198
|
-
* publications; file-scoped fences become rows, wider fences name their
|
|
199
|
-
* scope path in the unmet zone. Pure: no fetch, no state, no clock.
|
|
200
|
-
* @param {string} root @param {any} snapshot */
|
|
201
|
-
function recoveryDiagnosis(root, snapshot) {
|
|
202
|
-
const candidates = recoveryCandidates(root, snapshot);
|
|
203
|
-
const candidateByOwner = new Map(candidates.map((candidate) => [candidate.owner, candidate]));
|
|
204
|
-
const sessions = Array.isArray(snapshot?.sessions) ? snapshot.sessions : [];
|
|
205
|
-
const interruptedByOwner = new Map(sessions.map((session) => [session.sessionId, session?.interrupted === true]));
|
|
206
|
-
const retained = Array.isArray(snapshot?.retainedLocks) ? snapshot.retainedLocks : [];
|
|
207
|
-
/** @type {Map<string, {resourceId: string, owner: string, lockStatus: string|null, fence: boolean, interrupted: boolean}>} */
|
|
208
|
-
const rowsByResource = new Map();
|
|
209
|
-
const blockedOwners = new Set();
|
|
210
|
-
for (const lock of retained) {
|
|
211
|
-
if (typeof lock?.resourceId !== "string" || typeof lock?.owner !== "string") continue;
|
|
212
|
-
blockedOwners.add(lock.owner);
|
|
213
|
-
const row = rowsByResource.get(lock.resourceId) ?? { resourceId: lock.resourceId, owner: lock.owner, lockStatus: null, fence: false, interrupted: interruptedByOwner.get(lock.owner) ?? false };
|
|
214
|
-
row.lockStatus = row.lockStatus ?? (typeof lock.status === "string" ? lock.status : null);
|
|
215
|
-
rowsByResource.set(lock.resourceId, row);
|
|
216
|
-
}
|
|
217
|
-
/** @type {Map<string, {owner: string, scopePaths: string[]}>} */
|
|
218
|
-
const fencedOwners = new Map();
|
|
219
|
-
for (const op of Array.isArray(snapshot?.unresolved) ? snapshot.unresolved : []) {
|
|
220
|
-
if (op?.admissionBlocked !== true || typeof op?.key?.sessionId !== "string") continue;
|
|
221
|
-
blockedOwners.add(op.key.sessionId);
|
|
222
|
-
const scope = op?.scope ?? null;
|
|
223
|
-
if (scope?.kind === "file" && typeof scope.path === "string") {
|
|
224
|
-
const existing = rowsByResource.get(scope.path);
|
|
225
|
-
if (existing) {
|
|
226
|
-
// Same owner locks AND fences the file (the incident shape): one row.
|
|
227
|
-
if (existing.owner === op.key.sessionId) existing.fence = true;
|
|
228
|
-
else {
|
|
229
|
-
const entry = fencedOwners.get(op.key.sessionId) ?? { owner: op.key.sessionId, scopePaths: /** @type {string[]} */ ([]) };
|
|
230
|
-
if (!entry.scopePaths.includes(scope.path)) entry.scopePaths.push(scope.path);
|
|
231
|
-
fencedOwners.set(op.key.sessionId, entry);
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
rowsByResource.set(scope.path, { resourceId: scope.path, owner: op.key.sessionId, lockStatus: null, fence: true, interrupted: interruptedByOwner.get(op.key.sessionId) ?? false });
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
const entry = fencedOwners.get(op.key.sessionId) ?? { owner: op.key.sessionId, scopePaths: /** @type {string[]} */ ([]) };
|
|
238
|
-
if (typeof scope?.path === "string" && !entry.scopePaths.includes(scope.path)) entry.scopePaths.push(scope.path);
|
|
239
|
-
fencedOwners.set(op.key.sessionId, entry);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const rows = [...rowsByResource.values()];
|
|
243
|
-
const blocked = rows.filter((row) => candidateByOwner.has(row.owner));
|
|
244
|
-
blocked.sort((a, b) => a.resourceId.localeCompare(b.resourceId));
|
|
245
|
-
const unmet = [];
|
|
246
|
-
for (const owner of [...blockedOwners].sort()) {
|
|
247
|
-
if (candidateByOwner.has(owner)) continue;
|
|
248
|
-
unmet.push({ owner, rows: rows.filter((row) => row.owner === owner), scopePaths: fencedOwners.get(owner)?.scopePaths ?? [], reason: unmetReason(owner, snapshot) });
|
|
249
|
-
}
|
|
250
|
-
return { candidates, blocked, unmet };
|
|
251
|
-
}
|
|
252
76
|
/** Error boundary isolating the maintenance panel from the settings page. */
|
|
253
77
|
class EditLockMaintenanceBoundary extends react.Component {
|
|
254
78
|
constructor(props) {
|
|
@@ -268,11 +92,6 @@ window.__ModuleLoader__.load({
|
|
|
268
92
|
/** Switch-state banner keys (state names contain hyphens). */
|
|
269
93
|
const STATE_KEYS = { enforced: "Enforced", "disable-requested": "DisableRequested", "enable-requested": "EnableRequested", disabled: "Disabled", unknown: "Unknown" };
|
|
270
94
|
const onlineRecoveryAvailable = (state) => state === "enforced" || state === "disable-requested";
|
|
271
|
-
const post = (url, body) => fetch(url, {
|
|
272
|
-
method: "POST",
|
|
273
|
-
credentials: "include",
|
|
274
|
-
...(body === undefined ? {} : { headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
|
|
275
|
-
}).then((response) => response.json());
|
|
276
95
|
// Invoke the render callback only during descendant traversal, never while
|
|
277
96
|
// the parent constructs the boundary element.
|
|
278
97
|
function MaintenanceBody({ render }) {
|
|
@@ -286,14 +105,14 @@ window.__ModuleLoader__.load({
|
|
|
286
105
|
const [expanded, setExpanded] = react.useState(/** @type {string | null} */ (null));
|
|
287
106
|
const [inspections, setInspections] = react.useState({});
|
|
288
107
|
const [recoveries, setRecoveries] = react.useState(/** @type {Record<string, {status: 'busy'} | {status: 'done', revision: number, idempotent: boolean} | {status: 'error', message: string}>} */ ({}));
|
|
289
|
-
/** The one explicit action: submit the
|
|
290
|
-
*
|
|
108
|
+
/** The one explicit action: submit the SERVER-computed confirmation
|
|
109
|
+
* for one displayed scope, unchanged. No hash typing, no restart.
|
|
291
110
|
* A refusal is shown next to a freshly reloaded scope.
|
|
292
|
-
* @param {string} root @param {ReturnType<typeof
|
|
111
|
+
* @param {string} root @param {ReturnType<typeof import('../src/edit-lock/inspect.js').recoveryProposals>['candidates'][number]} candidate */
|
|
293
112
|
const recover = (root, candidate) => {
|
|
294
113
|
if (view?.status !== "ready" || !onlineRecoveryAvailable(view.value.state)) return;
|
|
295
114
|
setRecoveries((previous) => ({ ...previous, [recoveryKey(root, candidate.owner)]: { status: "busy" } }));
|
|
296
|
-
|
|
115
|
+
props.channel("api/weir-edit-lock/maintenance/recover-online", {
|
|
297
116
|
root,
|
|
298
117
|
owner: candidate.owner,
|
|
299
118
|
expectedRevision: candidate.scope.expectedRevision,
|
|
@@ -302,10 +121,10 @@ window.__ModuleLoader__.load({
|
|
|
302
121
|
reason: "Confirmed in the Edit Lock maintenance panel: the operator reviewed the displayed scope and accepted the displayed late-writer risk.",
|
|
303
122
|
acceptLateWriterRisk: true,
|
|
304
123
|
confirmation: candidate.confirmation,
|
|
305
|
-
}).then((
|
|
306
|
-
setRecoveries((previous) => ({ ...previous, [recoveryKey(root, candidate.owner)]:
|
|
307
|
-
? { status: "done", revision:
|
|
308
|
-
: { status: "error", message:
|
|
124
|
+
}).then((result) => {
|
|
125
|
+
setRecoveries((previous) => ({ ...previous, [recoveryKey(root, candidate.owner)]: result.ok === true
|
|
126
|
+
? { status: "done", revision: result.value.revision, idempotent: result.value.idempotent === true }
|
|
127
|
+
: { status: "error", message: result.error?.detail ?? result.error?.message ?? "unknown" } }));
|
|
309
128
|
inspect(root);
|
|
310
129
|
}).catch((error) => {
|
|
311
130
|
setRecoveries((previous) => ({ ...previous, [recoveryKey(root, candidate.owner)]: { status: "error", message: String(error?.message ?? error) } }));
|
|
@@ -314,14 +133,14 @@ window.__ModuleLoader__.load({
|
|
|
314
133
|
};
|
|
315
134
|
const load = () => {
|
|
316
135
|
setView({ status: "loading" });
|
|
317
|
-
|
|
318
|
-
.then((
|
|
319
|
-
setView(
|
|
136
|
+
props.channel("api/weir-edit-lock/maintenance/status")
|
|
137
|
+
.then((result) => {
|
|
138
|
+
setView(result.ok === true ? { status: "ready", value: result.value } : { status: "error", message: result.error.message });
|
|
320
139
|
// Recovery UX D1: opening the panel auto-runs the read-only
|
|
321
140
|
// inspection for every server-derived domain root, so the
|
|
322
141
|
// blocked/unmet zones render without any manual step. The
|
|
323
142
|
// inspection itself stays read-only (the same endpoint).
|
|
324
|
-
if (
|
|
143
|
+
if (result.ok === true && Array.isArray(result.value?.domains)) result.value.domains.forEach((domain) => inspect(domain.root, false));
|
|
325
144
|
})
|
|
326
145
|
.catch((error) => setView({ status: "error", message: String(error?.message ?? error) }));
|
|
327
146
|
};
|
|
@@ -335,9 +154,9 @@ window.__ModuleLoader__.load({
|
|
|
335
154
|
const inspect = (root, expand = true) => {
|
|
336
155
|
if (expand) setExpanded(root);
|
|
337
156
|
setInspections((previous) => ({ ...previous, [root]: { status: "loading" } }));
|
|
338
|
-
|
|
339
|
-
.then((
|
|
340
|
-
setInspections((previous) => ({ ...previous, [root]:
|
|
157
|
+
props.channel("api/weir-edit-lock/maintenance/inspect", { root })
|
|
158
|
+
.then((result) => {
|
|
159
|
+
setInspections((previous) => ({ ...previous, [root]: result.ok === true ? { status: "ready", value: result.value } : { status: "error", message: result.error.message } }));
|
|
341
160
|
})
|
|
342
161
|
.catch((error) => setInspections((previous) => ({ ...previous, [root]: { status: "error", message: String(error?.message ?? error) } })));
|
|
343
162
|
};
|
|
@@ -347,14 +166,20 @@ window.__ModuleLoader__.load({
|
|
|
347
166
|
if (scope?.kind === "domain") return t("editLockMaintScopeDomain");
|
|
348
167
|
return t("editLockMaintScopeNone");
|
|
349
168
|
};
|
|
169
|
+
/** The localized explanation for one server reason CODE; an unknown
|
|
170
|
+
* code falls back to a generic message naming the code itself. */
|
|
171
|
+
const reasonText = (reason) => {
|
|
172
|
+
const key = RECOVERY_REASON_KEYS[reason];
|
|
173
|
+
return key ? t(key) : t("editLockMaintRecoverReasonUnknown").replace("{code}", String(reason ?? ""));
|
|
174
|
+
};
|
|
350
175
|
const presenceRow = (value) => {
|
|
351
176
|
if (value.presence === "valid") return null;
|
|
352
177
|
const key = { none: "editLockMaintPresenceNone", empty: "editLockMaintPresenceEmpty", "no-committed-snapshot": "editLockMaintPresenceJunk", "not-a-file": "editLockMaintPresenceNotAFile", corrupt: "editLockMaintPresenceCorrupt", unreadable: "editLockMaintPresenceUnreadable" }[value.presence];
|
|
353
178
|
return react_jsx_runtime.jsx("div", { style: value.presence === "corrupt" || value.presence === "not-a-file" || value.presence === "no-committed-snapshot" ? dangerStyle : hintStyle, children: `${t(key) ?? value.presence}${value.message ? ` ${value.message}` : ""}` });
|
|
354
179
|
};
|
|
355
|
-
/** Recovery card for one eligible owner: the FULL scope, the
|
|
356
|
-
*
|
|
357
|
-
* @param {string} root @param {ReturnType<typeof
|
|
180
|
+
/** Recovery card for one eligible owner: the FULL server scope, the
|
|
181
|
+
* risk text and the server digest, then the one explicit button.
|
|
182
|
+
* @param {string} root @param {ReturnType<typeof import('../src/edit-lock/inspect.js').recoveryProposals>['candidates'][number]} candidate */
|
|
358
183
|
const recoveryCard = (root, candidate) => {
|
|
359
184
|
const state = recoveries[recoveryKey(root, candidate.owner)];
|
|
360
185
|
return react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px", padding: "8px", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: "var(--dsw-radius-md)" }, children: [
|
|
@@ -365,7 +190,7 @@ window.__ModuleLoader__.load({
|
|
|
365
190
|
react_jsx_runtime.jsx("div", { children: `${t("editLockMaintRecoverOperations")}:` }),
|
|
366
191
|
...candidate.scope.operationIds.map((id, index) => react_jsx_runtime.jsx("div", { style: monoStyle, children: id }, index))
|
|
367
192
|
] }),
|
|
368
|
-
react_jsx_runtime.jsx("div", { style: dangerStyle, children: `${t("editLockMaintRecoverRisk")}: ${
|
|
193
|
+
react_jsx_runtime.jsx("div", { style: dangerStyle, children: `${t("editLockMaintRecoverRisk")}: ${candidate.risk}` }),
|
|
369
194
|
react_jsx_runtime.jsx("div", { style: monoStyle, children: `${t("editLockMaintRecoverDigest")}: ${candidate.confirmation}` }),
|
|
370
195
|
state?.status === "done"
|
|
371
196
|
? react_jsx_runtime.jsx("div", { style: hintStyle, children: t("editLockMaintRecoverDone").replace("{revision}", String(state.revision)) })
|
|
@@ -419,7 +244,7 @@ window.__ModuleLoader__.load({
|
|
|
419
244
|
react_jsx_runtime.jsx("div", { children: `${t("editLockMaintRecoverOperations")}:` }),
|
|
420
245
|
...candidate.scope.operationIds.map((id, index) => react_jsx_runtime.jsx("div", { style: monoStyle, children: id }, index))
|
|
421
246
|
] }),
|
|
422
|
-
react_jsx_runtime.jsx("div", { style: dangerStyle, children: `${t("editLockMaintRecoverRisk")}: ${
|
|
247
|
+
react_jsx_runtime.jsx("div", { style: dangerStyle, children: `${t("editLockMaintRecoverRisk")}: ${candidate.risk}` }),
|
|
423
248
|
react_jsx_runtime.jsx("div", { style: monoStyle, children: `${t("editLockMaintRecoverDigest")}: ${candidate.confirmation}` }),
|
|
424
249
|
state?.status === "error"
|
|
425
250
|
? react_jsx_runtime.jsx("div", { style: dangerStyle, children: `${t("editLockMaintRecoverFailed")} ${state.message}` })
|
|
@@ -432,9 +257,12 @@ window.__ModuleLoader__.load({
|
|
|
432
257
|
react_jsx_runtime.jsx("div", { style: chainDescStyle, children: `${t("editLockMaintRecoverOwner")}: ${entry.owner}` }),
|
|
433
258
|
...entry.rows.map((row, index) => react_jsx_runtime.jsx("div", { style: monoStyle, children: `${row.resourceId} · ${RECOVERY_UX.holder} ${row.owner} · ${holderStateText(row)}` }, index)),
|
|
434
259
|
...entry.scopePaths.map((path, index) => react_jsx_runtime.jsx("div", { style: monoStyle, children: path }, index)),
|
|
435
|
-
react_jsx_runtime.jsx("div", { style: dangerStyle, children: entry.reason })
|
|
260
|
+
react_jsx_runtime.jsx("div", { style: dangerStyle, children: reasonText(entry.reason) })
|
|
436
261
|
], key: `${root}\n${entry.owner}\nunmet` });
|
|
437
|
-
const snapshotSection = (/** @type {string} */ root, /** @type {any} */
|
|
262
|
+
const snapshotSection = (/** @type {string} */ root, /** @type {any} */ value) => {
|
|
263
|
+
const snapshot = value.snapshot;
|
|
264
|
+
const proposals = supportedProposals(value);
|
|
265
|
+
return react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [
|
|
438
266
|
react_jsx_runtime.jsx("div", { style: chainDescStyle, children: t("editLockMaintCounts")
|
|
439
267
|
.replace("{sessions}", String(snapshot.counts.sessions))
|
|
440
268
|
.replace("{locks}", String(snapshot.counts.locks))
|
|
@@ -453,13 +281,14 @@ window.__ModuleLoader__.load({
|
|
|
453
281
|
...snapshot.retainedLocks.map((lock, index) => react_jsx_runtime.jsx("div", { style: chainDescStyle, children: `${lock.resourceId} · ${lock.owner} · ${lock.status}${lock.reason ? `: ${lock.reason}` : ""} · gen ${lock.generation}` }, index))
|
|
454
282
|
] })
|
|
455
283
|
: null,
|
|
456
|
-
|
|
284
|
+
proposals !== null && onlineRecoveryAvailable(view?.status === "ready" ? view.value.state : null) && Array.isArray(proposals.candidates) && proposals.candidates.length > 0
|
|
457
285
|
? react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [
|
|
458
286
|
react_jsx_runtime.jsx("div", { style: chainCategoryStyle, children: t("editLockMaintRecoveries") }),
|
|
459
287
|
react_jsx_runtime.jsx("div", { style: chainDescStyle, children: t("editLockMaintRecoverHint") }),
|
|
460
|
-
...
|
|
288
|
+
...proposals.candidates.map((candidate) => recoveryCard(root, candidate))
|
|
461
289
|
] }) : null
|
|
462
|
-
|
|
290
|
+
] });
|
|
291
|
+
};
|
|
463
292
|
const inspectionBody = (root) => {
|
|
464
293
|
const inspection = inspections[root];
|
|
465
294
|
if (!inspection) return null;
|
|
@@ -474,7 +303,7 @@ window.__ModuleLoader__.load({
|
|
|
474
303
|
return react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px", paddingTop: "4px" }, children: [
|
|
475
304
|
value.reservation === true ? react_jsx_runtime.jsx("div", { style: hintStyle, children: t("editLockMaintReservation") }) : null,
|
|
476
305
|
presenceRow(value),
|
|
477
|
-
value.presence === "valid" ? snapshotSection(root, value
|
|
306
|
+
value.presence === "valid" ? snapshotSection(root, value) : null
|
|
478
307
|
] });
|
|
479
308
|
};
|
|
480
309
|
const domainRow = (domain) => react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
@@ -506,18 +335,23 @@ window.__ModuleLoader__.load({
|
|
|
506
335
|
const online = onlineRecoveryAvailable(value.state);
|
|
507
336
|
const zoneGroups = new Map();
|
|
508
337
|
const zoneUnmet = [];
|
|
338
|
+
// Fail closed: a missing or unsupported proposals payload disables
|
|
339
|
+
// every zone derived from it — the panel never re-derives the
|
|
340
|
+
// recovery surface by itself.
|
|
341
|
+
let proposalsUnsupported = false;
|
|
509
342
|
for (const root of Object.keys(inspections)) {
|
|
510
343
|
const inspection = inspections[root];
|
|
511
344
|
if (inspection?.status !== "ready" || inspection.value?.presence !== "valid") continue;
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
345
|
+
const proposals = supportedProposals(inspection.value);
|
|
346
|
+
if (!proposals) { proposalsUnsupported = true; continue; }
|
|
347
|
+
for (const candidate of Array.isArray(proposals.candidates) ? proposals.candidates : []) {
|
|
348
|
+
if (!candidate || typeof candidate.owner !== "string" || !Array.isArray(candidate.rows) || candidate.rows.length === 0) continue;
|
|
349
|
+
const key = recoveryKey(root, candidate.owner);
|
|
516
350
|
const group = zoneGroups.get(key) ?? { root, candidate, rows: [] };
|
|
517
|
-
group.rows.push(row);
|
|
351
|
+
for (const row of candidate.rows) group.rows.push(row);
|
|
518
352
|
zoneGroups.set(key, group);
|
|
519
353
|
}
|
|
520
|
-
for (const entry of
|
|
354
|
+
for (const entry of Array.isArray(proposals.unmet) ? proposals.unmet : []) zoneUnmet.push({ root, entry });
|
|
521
355
|
}
|
|
522
356
|
const blockedGroups = [...zoneGroups.values()];
|
|
523
357
|
zoneUnmet.sort((a, b) => a.entry.owner.localeCompare(b.entry.owner));
|
|
@@ -549,6 +383,9 @@ window.__ModuleLoader__.load({
|
|
|
549
383
|
react_jsx_runtime.jsx("span", { style: warnStyle, children: t("editLockMaintWarnKeepHistory") }),
|
|
550
384
|
react_jsx_runtime.jsx("span", { style: warnStyle, children: t("editLockMaintWarnUnlock") })
|
|
551
385
|
] }),
|
|
386
|
+
proposalsUnsupported
|
|
387
|
+
? react_jsx_runtime.jsx("div", { style: warnStyle, children: t("editLockMaintRecoverUpgrade") })
|
|
388
|
+
: null,
|
|
552
389
|
online && blockedGroups.length > 0
|
|
553
390
|
? react_jsx_runtime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [
|
|
554
391
|
react_jsx_runtime.jsx("span", { style: chainCategoryStyle, children: RECOVERY_UX.blockedHeader }),
|
|
@@ -590,10 +427,166 @@ window.__ModuleLoader__.load({
|
|
|
590
427
|
open ? react_jsx_runtime.jsx(EditLockMaintenanceBoundary, { t, children: react_jsx_runtime.jsx(MaintenanceBody, { render: panelBody }) }) : null
|
|
591
428
|
] });
|
|
592
429
|
}
|
|
430
|
+
// Locale data face (pure data, NO ctx calls — a chunk factory re-executes
|
|
431
|
+
// under HMR, so a chunk-local register would throw on the duplicate
|
|
432
|
+
// (namespace, locale) pair): the entry registers these under "settings.weir.page"
|
|
433
|
+
// inside a ctx.effect when this chunk arrives (lib/client.js
|
|
434
|
+
// registerChunkDictionaries). Pinned by test/client.test.js's dictionary
|
|
435
|
+
// union pin.
|
|
436
|
+
// (The entry merges every settings-batch slice into the single "settings.weir.page"
|
|
437
|
+
// registration — the whole settings card tree shares one t.)
|
|
438
|
+
const dictionaries = {
|
|
439
|
+
en: {
|
|
440
|
+
editLockMaint: "Edit Lock maintenance",
|
|
441
|
+
editLockMaintHint: "Profile-wide enforcement status and read-only diagnostics.",
|
|
442
|
+
editLockMaintFailed: "Edit Lock maintenance failed",
|
|
443
|
+
editLockMaintLoading: "Loading…",
|
|
444
|
+
editLockMaintUnavailable: "Maintenance data unavailable:",
|
|
445
|
+
editLockMaintRetry: "Retry",
|
|
446
|
+
editLockMaintRefresh: "Refresh",
|
|
447
|
+
editLockMaintInspect: "Inspect",
|
|
448
|
+
editLockMaintStateEnforced: "Enforced",
|
|
449
|
+
editLockMaintStateEnforcedHint: "The switch is on and the Edit Lock manager is mounted in this process.",
|
|
450
|
+
editLockMaintStateDisableRequested: "Disable requested — restart required",
|
|
451
|
+
editLockMaintStateDisableRequestedHint: "You turned the switch off, but the manager is still mounted and enforcing. The change applies after restarting DeepSeek Harness.",
|
|
452
|
+
editLockMaintStateEnableRequested: "Enable requested — restart required",
|
|
453
|
+
editLockMaintStateEnableRequestedHint: "You turned the switch on, but no manager is mounted in this process. The change applies after restarting DeepSeek Harness.",
|
|
454
|
+
editLockMaintStateDisabled: "Enforcement disabled",
|
|
455
|
+
editLockMaintStateDisabledHint: "The switch is off and every recorded, fully installed row reports enforcement disabled.",
|
|
456
|
+
editLockMaintStateUnknown: "Unknown",
|
|
457
|
+
editLockMaintStateUnknownHint: "Mount evidence is absent, inconsistent, installing, disposing or failed. The saved value alone never proves enforcement or disablement.",
|
|
458
|
+
editLockMaintRestart: "Applies after restarting DeepSeek Harness.",
|
|
459
|
+
editLockMaintPinned: "This composition pins the Edit Lock row configuration; the settings switch does not govern it here.",
|
|
460
|
+
editLockMaintScopeNote: "The switch is profile-wide: it covers every Weir session in this profile, not one workspace.",
|
|
461
|
+
editLockMaintWarnKeepHistory: "Turning enforcement off does not clear unresolved operations, fences or history — turning it back on can block the same files again.",
|
|
462
|
+
editLockMaintWarnUnlock: "An ordinary unlock frees one lock without validating content; it is not historical recovery and never settles an unknown publication.",
|
|
463
|
+
editLockMaintOffRisk: "Edit Lock is not mounted. Ordinary edits are UNPROTECTED and can overwrite another session's work. Retained locks and fences do not block off-period edits; they may block files again after you re-enable and restart.",
|
|
464
|
+
editLockMaintUnknownRisk: "Edit Lock mount status cannot be verified. If no manager is mounted, ordinary edits are UNPROTECTED and can overwrite another session's work. Do not treat retained fences as proof of current protection.",
|
|
465
|
+
editLockMaintHistory: "Retained history — not current blocks",
|
|
466
|
+
editLockMaintHistoryHint: "These old locks and publication fences remain on disk for inspection. Without a mounted manager they do not block ordinary edits; after re-enabling they may block files again. Online force release is unavailable here; separate offline administrator recovery remains available.",
|
|
467
|
+
editLockMaintUnknownHistory: "Retained authority — current effect unverified",
|
|
468
|
+
editLockMaintUnknownHistoryHint: "The saved value and old fences cannot establish whether a manager is mounted. Inspect this history read-only; online force release is unavailable until enforcement is verified. Separate offline administrator recovery remains available.",
|
|
469
|
+
editLockMaintBlocked: "Initialization blocked",
|
|
470
|
+
editLockMaintDomains: "Domains",
|
|
471
|
+
editLockMaintNoDomains: "No live session reports an Edit Lock domain in this profile.",
|
|
472
|
+
editLockMaintAuthorityYes: "authority present",
|
|
473
|
+
editLockMaintAuthorityNo: "authority currently absent",
|
|
474
|
+
editLockMaintReservation: "A publisher reservation is present: this or another Harness holds (or stranded) the publishing claim for this domain.",
|
|
475
|
+
editLockMaintPresenceNone: "No authority directory is currently present. Prior initialization cannot be determined.",
|
|
476
|
+
editLockMaintPresenceEmpty: "The authority directory is currently empty. Prior initialization cannot be determined.",
|
|
477
|
+
editLockMaintPresenceJunk: "The authority directory has contents but no committed snapshot; the store would refuse to open it. Nothing was changed.",
|
|
478
|
+
editLockMaintPresenceNotAFile: "snapshot.json is not a regular file; the store would refuse it. Nothing was changed.",
|
|
479
|
+
editLockMaintPresenceCorrupt: "The authority image failed integrity validation and was left untouched — no automatic repair exists. Refusal:",
|
|
480
|
+
editLockMaintPresenceUnreadable: "The authority could not be read:",
|
|
481
|
+
editLockMaintCounts: "{sessions} session(s) · {locks} lock(s) · {operations} recorded operation(s)",
|
|
482
|
+
editLockMaintUnresolved: "Unresolved operations",
|
|
483
|
+
editLockMaintUnresolvedNone: "No unresolved operations.",
|
|
484
|
+
editLockMaintRetained: "Retained locks (interrupted / abnormal / pending)",
|
|
485
|
+
editLockMaintScopeFile: "one file",
|
|
486
|
+
editLockMaintScopeSubtree: "a directory subtree",
|
|
487
|
+
editLockMaintScopeDomain: "the whole work directory",
|
|
488
|
+
editLockMaintScopeNone: "no recorded scope",
|
|
489
|
+
editLockMaintRecoveries: "Online administrative recovery",
|
|
490
|
+
editLockMaintRecoverHint: "Settle an interrupted owner's unknown publications in the running manager: one explicit click, no restart, no hash typing. The unknown outcome stays in history. If the scope changed since it was loaded, the manager refuses and the panel reloads it.",
|
|
491
|
+
editLockMaintRecoverRoot: "Root",
|
|
492
|
+
editLockMaintRecoverOwner: "Owner session",
|
|
493
|
+
editLockMaintRecoverRevision: "Authority revision",
|
|
494
|
+
editLockMaintRecoverOperations: "Unresolved operation IDs",
|
|
495
|
+
editLockMaintRecoverRisk: "Risk you accept",
|
|
496
|
+
editLockMaintRecoverDigest: "Confirmation digest",
|
|
497
|
+
editLockMaintRecoverConfirm: "Settle this owner online",
|
|
498
|
+
editLockMaintRecoverBusy: "Settling…",
|
|
499
|
+
editLockMaintRecoverDone: "Settled at revision {revision}; admission is unblocked and the unknown outcome stays in history.",
|
|
500
|
+
editLockMaintRecoverFailed: "Recovery not acknowledged:",
|
|
501
|
+
editLockMaintRecoverUpgrade: "Online administrative recovery is unavailable: the server did not return recovery proposals this panel understands (missing or unsupported version). Refresh this panel or upgrade DeepSeek Harness, then inspect again.",
|
|
502
|
+
editLockMaintRecoverReasonDispositionRecorded: "An administrative recovery is already recorded for this owner; admission for its operations is no longer blocked.",
|
|
503
|
+
editLockMaintRecoverReasonOwnerUnknown: "The owner session is not recorded in this authority image.",
|
|
504
|
+
editLockMaintRecoverReasonNotInterrupted: "The holding session is not durably interrupted; the online administrative recovery can only override an interrupted historical owner. Stop the session first, or ask it to release the file.",
|
|
505
|
+
editLockMaintRecoverReasonPreparedOutstanding: "The owner still has a prepared operation; recover to stable unknown history before an administrative override.",
|
|
506
|
+
editLockMaintRecoverReasonPublishingOutstanding: "The owner still has an operation in the publishing phase; only unknown outcomes can be overridden online.",
|
|
507
|
+
editLockMaintRecoverReasonNoUnresolved: "The owner has no unresolved operations; an ordinary unlock may be enough.",
|
|
508
|
+
editLockMaintRecoverReasonUnknown: "The server reported an unrecognized recovery precondition (code: {code}). Refresh or upgrade DeepSeek Harness for the localized explanation.",
|
|
509
|
+
},
|
|
510
|
+
zh: {
|
|
511
|
+
editLockMaint: "编辑锁维护",
|
|
512
|
+
editLockMaintHint: "查看全 profile 的强制执行状态与只读诊断。",
|
|
513
|
+
editLockMaintFailed: "编辑锁维护面板加载失败",
|
|
514
|
+
editLockMaintLoading: "加载中……",
|
|
515
|
+
editLockMaintUnavailable: "维护数据不可用:",
|
|
516
|
+
editLockMaintRetry: "重试",
|
|
517
|
+
editLockMaintRefresh: "刷新",
|
|
518
|
+
editLockMaintInspect: "检查",
|
|
519
|
+
editLockMaintStateEnforced: "强制执行中",
|
|
520
|
+
editLockMaintStateEnforcedHint: "开关已打开,且编辑锁管理器已在本进程中挂载。",
|
|
521
|
+
editLockMaintStateDisableRequested: "已请求停用——需要重启",
|
|
522
|
+
editLockMaintStateDisableRequestedHint: "你已关闭开关,但管理器仍处于挂载并强制执行状态。重启 DeepSeek Harness 后生效。",
|
|
523
|
+
editLockMaintStateEnableRequested: "已请求启用——需要重启",
|
|
524
|
+
editLockMaintStateEnableRequestedHint: "你已打开开关,但本进程尚未挂载管理器。重启 DeepSeek Harness 后生效。",
|
|
525
|
+
editLockMaintStateDisabled: "强制执行已停用",
|
|
526
|
+
editLockMaintStateDisabledHint: "开关关闭,所有已记录且完成安装的行均报告强制执行已停用。",
|
|
527
|
+
editLockMaintStateUnknown: "未知",
|
|
528
|
+
editLockMaintStateUnknownHint: "挂载证据缺失、不一致,或仍在安装、卸载、失败状态。仅凭已保存的值不能证明正在强制执行或已经停用。",
|
|
529
|
+
editLockMaintRestart: "重启 DeepSeek Harness 后生效。",
|
|
530
|
+
editLockMaintPinned: "当前组合在编辑锁行上钉死了配置;这里的设置开关对它不生效。",
|
|
531
|
+
editLockMaintScopeNote: "该开关对整个 profile 生效:覆盖此 profile 下的所有 Weir 会话,而非某一个工作区。",
|
|
532
|
+
editLockMaintWarnKeepHistory: "关闭强制执行不会清除未决操作、围栏或历史——重新打开后同一批文件可能再次被阻塞。",
|
|
533
|
+
editLockMaintWarnUnlock: "普通解锁只释放一把锁且不校验内容;它不是历史恢复,永远不会结清未知的发布。",
|
|
534
|
+
editLockMaintOffRisk: "编辑锁管理器未挂载。普通编辑不受保护,可能覆盖其他会话的工作。保留的锁和围栏不会阻止停用期间的编辑;重新启用并重启后可能再次阻塞文件。",
|
|
535
|
+
editLockMaintUnknownRisk: "无法核实编辑锁的挂载状态。如果管理器未挂载,普通编辑不受保护,可能覆盖其他会话的工作。不能把保留的围栏当成当前仍受保护的证据。",
|
|
536
|
+
editLockMaintHistory: "保留的历史——不是当前阻塞",
|
|
537
|
+
editLockMaintHistoryHint: "这些旧锁与发布围栏保留在磁盘供检查。没有挂载管理器时,它们不会阻止普通编辑;重新启用后可能再次阻塞文件。此处不可在线强制释放;独立的离线管理员恢复端点仍可使用。",
|
|
538
|
+
editLockMaintUnknownHistory: "保留的权威记录——当前作用未经核实",
|
|
539
|
+
editLockMaintUnknownHistoryHint: "仅凭已保存的值与旧围栏无法确认管理器是否挂载。可只读检查这些历史;核实强制执行前不可在线强制释放。独立的离线管理员恢复端点仍可使用。",
|
|
540
|
+
editLockMaintBlocked: "初始化受阻",
|
|
541
|
+
editLockMaintDomains: "管理域",
|
|
542
|
+
editLockMaintNoDomains: "此 profile 中没有活动会话报告编辑锁管理域。",
|
|
543
|
+
editLockMaintAuthorityYes: "权威已存在",
|
|
544
|
+
editLockMaintAuthorityNo: "当前无权威",
|
|
545
|
+
editLockMaintReservation: "存在发布者预留:本进程或另一个 Harness 持有(或遗留)了该域的发布权。",
|
|
546
|
+
editLockMaintPresenceNone: "当前没有权威目录。无法据此判断此前是否初始化过。",
|
|
547
|
+
editLockMaintPresenceEmpty: "权威目录当前为空。无法据此判断此前是否初始化过。",
|
|
548
|
+
editLockMaintPresenceJunk: "权威目录有内容但没有已提交的快照;存储层会拒绝打开它。未做任何改动。",
|
|
549
|
+
editLockMaintPresenceNotAFile: "snapshot.json 不是常规文件;存储层会拒绝它。未做任何改动。",
|
|
550
|
+
editLockMaintPresenceCorrupt: "权威镜像未通过完整性校验,已原样保留——不存在自动修复。拒绝原因:",
|
|
551
|
+
editLockMaintPresenceUnreadable: "无法读取权威:",
|
|
552
|
+
editLockMaintCounts: "{sessions} 个会话 · {locks} 把锁 · {operations} 条已记录操作",
|
|
553
|
+
editLockMaintUnresolved: "未决操作",
|
|
554
|
+
editLockMaintUnresolvedNone: "没有未决操作。",
|
|
555
|
+
editLockMaintRetained: "保留的锁(中断 / 异常 / 待确认)",
|
|
556
|
+
editLockMaintScopeFile: "单个文件",
|
|
557
|
+
editLockMaintScopeSubtree: "一个目录子树",
|
|
558
|
+
editLockMaintScopeDomain: "整个工作目录",
|
|
559
|
+
editLockMaintScopeNone: "无记录范围",
|
|
560
|
+
editLockMaintRecoveries: "在线管理员恢复",
|
|
561
|
+
editLockMaintRecoverHint: "在运行中的管理器里结清已中断 owner 的未知发布:一次明确点击,无需重启、无需手打哈希;历史中的未知结论原样保留。若范围自加载后发生变化,管理器会拒绝,面板会重新加载。",
|
|
562
|
+
editLockMaintRecoverRoot: "管理根",
|
|
563
|
+
editLockMaintRecoverOwner: "属主会话",
|
|
564
|
+
editLockMaintRecoverRevision: "权威 revision",
|
|
565
|
+
editLockMaintRecoverOperations: "未决操作 ID",
|
|
566
|
+
editLockMaintRecoverRisk: "你接受的风险",
|
|
567
|
+
editLockMaintRecoverDigest: "确认摘要",
|
|
568
|
+
editLockMaintRecoverConfirm: "在线结清该 owner",
|
|
569
|
+
editLockMaintRecoverBusy: "结清中……",
|
|
570
|
+
editLockMaintRecoverDone: "已在 revision {revision} 结清;准入阻塞解除,历史仍保持未知。",
|
|
571
|
+
editLockMaintRecoverFailed: "恢复未确认:",
|
|
572
|
+
editLockMaintRecoverUpgrade: "在线管理员恢复不可用:服务端未返回本面板可理解的恢复提案(缺失或版本不受支持)。请刷新本面板或升级 DeepSeek Harness,然后重新检查。",
|
|
573
|
+
editLockMaintRecoverReasonDispositionRecorded: "该 owner 已有记录在案的管理员恢复;其操作的准入已不再阻塞。",
|
|
574
|
+
editLockMaintRecoverReasonOwnerUnknown: "该 owner 会话未记录在此权威镜像中。",
|
|
575
|
+
editLockMaintRecoverReasonNotInterrupted: "持有会话尚未持久中断;在线管理员恢复只能撤权已中断的历史 owner。请先停止该会话,或请它释放文件。",
|
|
576
|
+
editLockMaintRecoverReasonPreparedOutstanding: "该 owner 仍持有 prepared 操作;请先恢复到稳定的 unknown 历史,再进行管理员撤权。",
|
|
577
|
+
editLockMaintRecoverReasonPublishingOutstanding: "该 owner 仍有处于 publishing 阶段的操作;只有 unknown 结论可以在线撤权。",
|
|
578
|
+
editLockMaintRecoverReasonNoUnresolved: "该 owner 没有未决操作;普通解锁或许就已足够。",
|
|
579
|
+
editLockMaintRecoverReasonUnknown: "服务端报告了本面板不认识的恢复前置条件(代码:{code})。请刷新或升级 DeepSeek Harness 以获取对应说明。",
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
exports.dictionaries = dictionaries;
|
|
593
584
|
exports.EditLockMaintenanceBoundary = EditLockMaintenanceBoundary;
|
|
594
585
|
exports.EditLockMaintenanceField = EditLockMaintenanceField;
|
|
595
|
-
// Exported for the chunk
|
|
596
|
-
|
|
586
|
+
// Exported for the chunk tests: the recovery progress key and the zone
|
|
587
|
+
// copy. The recovery derivation lives host-side (inspect.js); the chunk
|
|
588
|
+
// renders and forwards, never computes.
|
|
589
|
+
exports.editLockRecovery = Object.freeze({ recoveryKey, RECOVERY_UX });
|
|
597
590
|
return module.exports;
|
|
598
591
|
}
|
|
599
592
|
});
|