weir-harness 1.2.3 → 2.0.0
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/cordis.patch.yml +7 -18
- package/lib/client.file-lease-badge.js +307 -0
- package/lib/client.js +20 -30
- package/lib/client.settings-page.js +10 -34
- package/package.json +2 -2
- package/src/capabilities/store/checked-contract.d.ts +2 -2
- package/src/capabilities/store/fs-adapter.js +2 -3
- package/src/context-guard/index.js +2 -2
- package/src/delegate/supervision-mount.js +5 -5
- package/src/delegate/tool.js +2 -10
- package/src/file-lease/client.js +1066 -0
- package/src/file-lease/index.js +30 -0
- package/src/file-lease/lineage.js +207 -0
- package/src/file-lease/node-contracts.d.ts +42 -0
- package/src/file-lease/plugin.js +789 -0
- package/src/file-lease/singleton.js +155 -0
- package/src/file-lease/spawn.js +62 -0
- package/src/file-lease/tools.js +459 -0
- package/src/file-lease/view.js +144 -0
- package/src/hashline-edit/index.js +76 -42
- package/src/hashline-edit/write-tool.js +90 -0
- package/src/lease-broker/entry.js +98 -0
- package/src/lease-broker/node-contracts.d.ts +63 -0
- package/src/lease-broker/protocol.js +236 -0
- package/src/lease-broker/server.js +399 -0
- package/src/lease-broker/state.js +425 -0
- package/src/lsp/tools.js +137 -27
- package/src/settings/index.js +2 -44
- package/src/settings/sections.js +2 -24
- package/src/shared/audit.js +6 -1
- package/src/todo-driver/index.js +1 -6
- package/src/worktree/cards.js +6 -21
- package/src/worktree/lane-core.js +1 -19
- package/src/worktree/lane-workflows.js +11 -32
- package/lib/client.edit-lock-maintenance.js +0 -592
- package/lib/client.edit-lock-panel.js +0 -405
- package/src/edit-lock/adapter.js +0 -57
- package/src/edit-lock/admin-ledger.js +0 -149
- package/src/edit-lock/admin-recovery.js +0 -151
- package/src/edit-lock/admission.js +0 -82
- package/src/edit-lock/binding-attestation.js +0 -29
- package/src/edit-lock/binding-guidance.js +0 -72
- package/src/edit-lock/binding-machine.js +0 -312
- package/src/edit-lock/call-context.js +0 -65
- package/src/edit-lock/derived-facts.js +0 -67
- package/src/edit-lock/domain.js +0 -165
- package/src/edit-lock/domains.js +0 -170
- package/src/edit-lock/host.js +0 -149
- package/src/edit-lock/incarnations.js +0 -150
- package/src/edit-lock/index.js +0 -1303
- package/src/edit-lock/inspect.js +0 -219
- package/src/edit-lock/lifecycle.js +0 -579
- package/src/edit-lock/limits.js +0 -61
- package/src/edit-lock/maintenance.js +0 -310
- package/src/edit-lock/manager.js +0 -875
- package/src/edit-lock/negotiation.js +0 -132
- package/src/edit-lock/operation-history.js +0 -214
- package/src/edit-lock/peer-client.js +0 -86
- package/src/edit-lock/peer-transport.js +0 -76
- package/src/edit-lock/peer.js +0 -87
- package/src/edit-lock/publisher.js +0 -191
- package/src/edit-lock/read-authority.js +0 -101
- package/src/edit-lock/recovery.js +0 -139
- package/src/edit-lock/remote.js +0 -362
- package/src/edit-lock/request-binding.js +0 -42
- package/src/edit-lock/request-data.js +0 -40
- package/src/edit-lock/reservation.js +0 -301
- package/src/edit-lock/reserved-runtime.js +0 -45
- package/src/edit-lock/resource-identity.js +0 -130
- package/src/edit-lock/runtime.js +0 -145
- package/src/edit-lock/settle.js +0 -144
- package/src/edit-lock/snapshot.js +0 -175
- package/src/edit-lock/stale-sweep.js +0 -120
- package/src/edit-lock/state.js +0 -462
- package/src/edit-lock/store.js +0 -327
- package/src/edit-lock/tool-scope.js +0 -29
- package/src/edit-lock/view.js +0 -170
- package/src/edit-lock/write-scopes.js +0 -48
- package/src/edit-lock/write-tool.js +0 -45
- package/src/shared/delegate-edit-lifecycle.js +0 -36
- package/src/worktree/authority.js +0 -56
package/src/edit-lock/inspect.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
// Read-only maintenance inspection over a validated authority image, and the
|
|
2
|
-
// profile-wide switch-state decision. Pure: no IO, no clock, no ctx, and no
|
|
3
|
-
// cross-module type imports (jsconfig pure-module curation) — the caller
|
|
4
|
-
// supplies the parsed snapshot and the mount evidence. Nothing here opens a
|
|
5
|
-
// runtime, grants authority, or changes what admission would decide; an
|
|
6
|
-
// unresolved operation is reported, never settled.
|
|
7
|
-
import { dispositionFor } from './derived-facts.js'
|
|
8
|
-
import { recoveryConfirmation } from './admin-recovery.js'
|
|
9
|
-
import { LATE_WRITER_RISK } from './admin-ledger.js'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The switch state the maintenance panel renders. Detection is evidence-based:
|
|
13
|
-
* 'unknown' whenever the mount evidence this decision needs is unavailable —
|
|
14
|
-
* never a guess from the saved setting alone.
|
|
15
|
-
* @typedef {'enforced'|'disable-requested'|'enable-requested'|'disabled'|'unknown'} MaintenanceState
|
|
16
|
-
* @typedef {{ kind: 'file'|'subtree'|'domain'|'none', path: string|null }} FenceScope
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** The scope one recorded fence covers, in user terms.
|
|
20
|
-
* @param {any} fence @returns {FenceScope} */
|
|
21
|
-
export function fenceScope(fence) {
|
|
22
|
-
if (!fence || typeof fence !== 'object') return { kind: 'none', path: null }
|
|
23
|
-
if (fence.kind === 'resource') return { kind: 'file', path: typeof fence.resourceId === 'string' ? fence.resourceId : null }
|
|
24
|
-
if (fence.kind === 'subtree') return { kind: 'subtree', path: typeof fence.ancestor === 'string' ? fence.ancestor : null }
|
|
25
|
-
if (fence.kind === 'domain') return { kind: 'domain', path: null }
|
|
26
|
-
return { kind: 'none', path: null }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** One unresolved (publishing/unknown) operation's diagnostic row. The phase,
|
|
30
|
-
* outcome and fence are historical facts; `closeouts` counts inert recorded
|
|
31
|
-
* assertions (they discharge nothing). @param {any} op */
|
|
32
|
-
function unresolvedRow(op) {
|
|
33
|
-
return {
|
|
34
|
-
phase: op?.phase ?? null,
|
|
35
|
-
outcome: op?.outcome?.kind ?? null,
|
|
36
|
-
scope: fenceScope(op?.fence),
|
|
37
|
-
fence: op?.fence ? structuredClone(op.fence) : null,
|
|
38
|
-
target: { tool: op?.binding?.tool ?? null, filePath: op?.binding?.filePath ?? null },
|
|
39
|
-
origin: { executionEpoch: op?.origin?.executionEpoch ?? null, managerIncarnation: op?.origin?.managerIncarnation ?? null },
|
|
40
|
-
closeouts: Array.isArray(op?.closeouts) ? op.closeouts.length : 0,
|
|
41
|
-
key: { sessionId: op?.sessionId ?? null, operationId: op?.operationId ?? null },
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Summarize a validated snapshot for the maintenance inspector. The primary
|
|
47
|
-
* facts are the unresolved operations and the scope each one fences off;
|
|
48
|
-
* identifiers stay available because this is the administrator's surface.
|
|
49
|
-
* Reading changes nothing: this is a pure projection of the parsed image.
|
|
50
|
-
* @param {{ revision: number, state: any }} snapshot
|
|
51
|
-
*/
|
|
52
|
-
export function summarizeAuthorityImage(snapshot) {
|
|
53
|
-
const state = snapshot.state
|
|
54
|
-
const operations = Array.isArray(state?.operations) ? state.operations : []
|
|
55
|
-
const unresolved = operations
|
|
56
|
-
.filter(op => op?.phase === 'publishing' || op?.phase === 'unknown')
|
|
57
|
-
.map(op => {
|
|
58
|
-
const disposition = dispositionFor(state, op)
|
|
59
|
-
return { ...unresolvedRow(op), admissionBlocked: !disposition, administrativeRecoveryId: disposition?.recoveryId ?? null }
|
|
60
|
-
})
|
|
61
|
-
// Prepared operations never fence anything, but the online administrative
|
|
62
|
-
// recovery refuses an owner that still has one (manager requires every
|
|
63
|
-
// unresolved operation to be unknown). The panel needs them visible to keep
|
|
64
|
-
// impossible one-click actions off the screen. Read-only and additive.
|
|
65
|
-
const prepared = operations
|
|
66
|
-
.filter(op => op?.phase === 'prepared')
|
|
67
|
-
.map(op => ({ target: { tool: op?.binding?.tool ?? null, filePath: op?.binding?.filePath ?? null }, key: { sessionId: op?.sessionId ?? null, operationId: op?.operationId ?? null } }))
|
|
68
|
-
const retainedLocks = (Array.isArray(state?.locks) ? state.locks : [])
|
|
69
|
-
.filter(lock => lock?.status !== 'active')
|
|
70
|
-
.map(lock => ({ resourceId: lock.resourceId, owner: lock.owner, generation: lock.generation, status: lock.status, reason: lock.reason ?? null }))
|
|
71
|
-
const recovery = Array.isArray(state?.recovery) ? state.recovery : []
|
|
72
|
-
return {
|
|
73
|
-
version: state?.version ?? null,
|
|
74
|
-
revision: snapshot.revision,
|
|
75
|
-
counts: {
|
|
76
|
-
sessions: (Array.isArray(state?.sessions) ? state.sessions : []).length,
|
|
77
|
-
locks: (Array.isArray(state?.locks) ? state.locks : []).length,
|
|
78
|
-
operations: operations.length,
|
|
79
|
-
unresolved: unresolved.length,
|
|
80
|
-
retainedLocks: retainedLocks.length,
|
|
81
|
-
},
|
|
82
|
-
unresolved,
|
|
83
|
-
prepared,
|
|
84
|
-
adminRecoveries: structuredClone(state?.adminRecoveries ?? []),
|
|
85
|
-
retainedLocks,
|
|
86
|
-
sessions: (Array.isArray(state?.sessions) ? state.sessions : []).map(session => ({
|
|
87
|
-
sessionId: session.sessionId,
|
|
88
|
-
executionEpoch: session.executionEpoch,
|
|
89
|
-
interrupted: session.interrupted,
|
|
90
|
-
recovery: recovery.find(row => row?.sessionId === session?.sessionId) ?? null,
|
|
91
|
-
})),
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Server-computed online-recovery proposals over the SAME parsed image the
|
|
97
|
-
* summary above projects (change edit-lock-recovery-proposals, D1/D2). The
|
|
98
|
-
* client renders these verbatim and never re-derives eligibility or the
|
|
99
|
-
* confirmation: eligibility uses the host's own disposition rule, and the
|
|
100
|
-
* confirmation is produced by the very function the manager re-verifies at
|
|
101
|
-
* commit time — the proposal is a read-time suggestion, never an
|
|
102
|
-
* authorization. `unmet` entries carry stable reason CODES in the manager's
|
|
103
|
-
* refusal order (disposition recorded → owner unknown → not interrupted →
|
|
104
|
-
* prepared outstanding → publishing outstanding → no unresolved operations);
|
|
105
|
-
* the prose is the client's localization job. Pure: reading changes nothing.
|
|
106
|
-
* @param {string} root @param {{ revision: number, state: any }} snapshot
|
|
107
|
-
*/
|
|
108
|
-
export function recoveryProposals(root, snapshot) {
|
|
109
|
-
const state = snapshot.state
|
|
110
|
-
const revision = snapshot.revision
|
|
111
|
-
const operations = Array.isArray(state?.operations) ? state.operations : []
|
|
112
|
-
const sessionByOwner = new Map()
|
|
113
|
-
const interruptedByOwner = new Map()
|
|
114
|
-
for (const session of Array.isArray(state?.sessions) ? state.sessions : []) {
|
|
115
|
-
if (typeof session?.sessionId !== 'string') continue
|
|
116
|
-
sessionByOwner.set(session.sessionId, session)
|
|
117
|
-
interruptedByOwner.set(session.sessionId, session.interrupted === true)
|
|
118
|
-
}
|
|
119
|
-
/** Unresolved (publishing/unknown) operations by owner, in image order. */
|
|
120
|
-
const unresolvedByOwner = new Map()
|
|
121
|
-
const preparedOwners = new Set()
|
|
122
|
-
for (const op of operations) {
|
|
123
|
-
if (typeof op?.sessionId !== 'string') continue
|
|
124
|
-
if (op.phase === 'prepared') { preparedOwners.add(op.sessionId); continue }
|
|
125
|
-
if (op.phase !== 'publishing' && op.phase !== 'unknown') continue
|
|
126
|
-
const list = unresolvedByOwner.get(op.sessionId) ?? []
|
|
127
|
-
list.push(op)
|
|
128
|
-
unresolvedByOwner.set(op.sessionId, list)
|
|
129
|
-
}
|
|
130
|
-
// The panel's merged display rows: retained locks plus still-blocking file
|
|
131
|
-
// fences (one row per resource); wide fences and cross-owner file fences
|
|
132
|
-
// become named scope paths instead of rows.
|
|
133
|
-
const rowsByResource = new Map()
|
|
134
|
-
const scopePathsByOwner = new Map()
|
|
135
|
-
const blockedOwners = new Set()
|
|
136
|
-
/** @param {string} owner @param {unknown} path */
|
|
137
|
-
const addScopePath = (owner, path) => {
|
|
138
|
-
if (typeof path !== 'string') return
|
|
139
|
-
const paths = scopePathsByOwner.get(owner) ?? []
|
|
140
|
-
if (!paths.includes(path)) paths.push(path)
|
|
141
|
-
scopePathsByOwner.set(owner, paths)
|
|
142
|
-
}
|
|
143
|
-
for (const lock of Array.isArray(state?.locks) ? state.locks : []) {
|
|
144
|
-
if (lock?.status === 'active') continue
|
|
145
|
-
if (typeof lock?.resourceId !== 'string' || typeof lock?.owner !== 'string') continue
|
|
146
|
-
blockedOwners.add(lock.owner)
|
|
147
|
-
const row = rowsByResource.get(lock.resourceId) ?? { resourceId: lock.resourceId, owner: lock.owner, lockStatus: null, fence: false, interrupted: interruptedByOwner.get(lock.owner) ?? false }
|
|
148
|
-
row.lockStatus = row.lockStatus ?? (typeof lock.status === 'string' ? lock.status : null)
|
|
149
|
-
rowsByResource.set(lock.resourceId, row)
|
|
150
|
-
}
|
|
151
|
-
for (const [owner, ops] of unresolvedByOwner) {
|
|
152
|
-
for (const op of ops) {
|
|
153
|
-
if (dispositionFor(state, op)) continue // disposed: no longer blocks admission
|
|
154
|
-
blockedOwners.add(owner)
|
|
155
|
-
const scope = fenceScope(op?.fence)
|
|
156
|
-
if (scope.kind === 'file' && typeof scope.path === 'string') {
|
|
157
|
-
const existing = rowsByResource.get(scope.path)
|
|
158
|
-
if (existing) {
|
|
159
|
-
// Same owner locks AND fences the file (the incident shape): one row.
|
|
160
|
-
if (existing.owner === owner) existing.fence = true
|
|
161
|
-
else addScopePath(owner, scope.path)
|
|
162
|
-
} else {
|
|
163
|
-
rowsByResource.set(scope.path, { resourceId: scope.path, owner, lockStatus: null, fence: true, interrupted: interruptedByOwner.get(owner) ?? false })
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
addScopePath(owner, scope.path)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const rows = [...rowsByResource.values()]
|
|
171
|
-
// Eligibility: an interrupted owner whose every unresolved operation is a
|
|
172
|
-
// still-blocking unknown, with no prepared operation outstanding — exactly
|
|
173
|
-
// the preconditions the manager re-verifies at the FIFO execution point.
|
|
174
|
-
const eligible = []
|
|
175
|
-
for (const [owner, ops] of unresolvedByOwner) {
|
|
176
|
-
if (interruptedByOwner.get(owner) !== true) continue
|
|
177
|
-
if (preparedOwners.has(owner)) continue
|
|
178
|
-
if (!(ops.length > 0 && ops.every(op => op.phase === 'unknown' && !dispositionFor(state, op)))) continue
|
|
179
|
-
eligible.push({ owner, ops })
|
|
180
|
-
}
|
|
181
|
-
const candidateOwners = new Set(eligible.map(entry => entry.owner))
|
|
182
|
-
const blocked = rows.filter(row => candidateOwners.has(row.owner))
|
|
183
|
-
blocked.sort((a, b) => a.resourceId.localeCompare(b.resourceId))
|
|
184
|
-
const candidates = eligible.map(({ owner, ops }) => {
|
|
185
|
-
const scope = { root, owner, expectedRevision: revision, operationIds: ops.map(op => op.operationId).sort() }
|
|
186
|
-
return { owner, scope, confirmation: recoveryConfirmation(scope), risk: LATE_WRITER_RISK, rows: blocked.filter(row => row.owner === owner) }
|
|
187
|
-
})
|
|
188
|
-
const unmet = []
|
|
189
|
-
for (const owner of [...blockedOwners].sort()) {
|
|
190
|
-
if (candidateOwners.has(owner)) continue
|
|
191
|
-
const ops = unresolvedByOwner.get(owner) ?? []
|
|
192
|
-
const session = sessionByOwner.get(owner)
|
|
193
|
-
const reason = ops.some(op => dispositionFor(state, op)) ? 'disposition-recorded'
|
|
194
|
-
: !session ? 'owner-unknown'
|
|
195
|
-
: session.interrupted !== true ? 'not-interrupted'
|
|
196
|
-
: preparedOwners.has(owner) ? 'prepared-outstanding'
|
|
197
|
-
: ops.some(op => op.phase === 'publishing') ? 'publishing-outstanding'
|
|
198
|
-
: 'no-unresolved'
|
|
199
|
-
unmet.push({ owner, reason, rows: rows.filter(row => row.owner === owner), scopePaths: scopePathsByOwner.get(owner) ?? [] })
|
|
200
|
-
}
|
|
201
|
-
return { schemaVersion: 1, revision, candidates, blocked, unmet }
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* The profile-wide switch decision. `saved` is the value currently persisted
|
|
206
|
-
* in settings; `enabledAtMount` is what the edit-lock row actually did when it
|
|
207
|
-
* last mounted (undefined when no mount evidence exists in this process).
|
|
208
|
-
* A change applies after restart, so a saved value different from the mounted
|
|
209
|
-
* one is a pending request, never the current enforcement state.
|
|
210
|
-
* @param {{ saved: boolean, enabledAtMount: boolean|undefined }} input
|
|
211
|
-
* @returns {MaintenanceState}
|
|
212
|
-
*/
|
|
213
|
-
export function maintenanceState({ saved, enabledAtMount }) {
|
|
214
|
-
if (enabledAtMount === undefined) return 'unknown'
|
|
215
|
-
if (saved && enabledAtMount) return 'enforced'
|
|
216
|
-
if (saved && !enabledAtMount) return 'enable-requested'
|
|
217
|
-
if (!saved && enabledAtMount) return 'disable-requested'
|
|
218
|
-
return 'disabled'
|
|
219
|
-
}
|