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.
Files changed (81) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.file-lease-badge.js +307 -0
  3. package/lib/client.js +20 -30
  4. package/lib/client.settings-page.js +10 -34
  5. package/package.json +2 -2
  6. package/src/capabilities/store/checked-contract.d.ts +2 -2
  7. package/src/capabilities/store/fs-adapter.js +2 -3
  8. package/src/context-guard/index.js +2 -2
  9. package/src/delegate/supervision-mount.js +5 -5
  10. package/src/delegate/tool.js +2 -10
  11. package/src/file-lease/client.js +1066 -0
  12. package/src/file-lease/index.js +30 -0
  13. package/src/file-lease/lineage.js +207 -0
  14. package/src/file-lease/node-contracts.d.ts +42 -0
  15. package/src/file-lease/plugin.js +789 -0
  16. package/src/file-lease/singleton.js +155 -0
  17. package/src/file-lease/spawn.js +62 -0
  18. package/src/file-lease/tools.js +459 -0
  19. package/src/file-lease/view.js +144 -0
  20. package/src/hashline-edit/index.js +76 -42
  21. package/src/hashline-edit/write-tool.js +90 -0
  22. package/src/lease-broker/entry.js +98 -0
  23. package/src/lease-broker/node-contracts.d.ts +63 -0
  24. package/src/lease-broker/protocol.js +236 -0
  25. package/src/lease-broker/server.js +399 -0
  26. package/src/lease-broker/state.js +425 -0
  27. package/src/lsp/tools.js +137 -27
  28. package/src/settings/index.js +2 -44
  29. package/src/settings/sections.js +2 -24
  30. package/src/shared/audit.js +6 -1
  31. package/src/todo-driver/index.js +1 -6
  32. package/src/worktree/cards.js +6 -21
  33. package/src/worktree/lane-core.js +1 -19
  34. package/src/worktree/lane-workflows.js +11 -32
  35. package/lib/client.edit-lock-maintenance.js +0 -592
  36. package/lib/client.edit-lock-panel.js +0 -405
  37. package/src/edit-lock/adapter.js +0 -57
  38. package/src/edit-lock/admin-ledger.js +0 -149
  39. package/src/edit-lock/admin-recovery.js +0 -151
  40. package/src/edit-lock/admission.js +0 -82
  41. package/src/edit-lock/binding-attestation.js +0 -29
  42. package/src/edit-lock/binding-guidance.js +0 -72
  43. package/src/edit-lock/binding-machine.js +0 -312
  44. package/src/edit-lock/call-context.js +0 -65
  45. package/src/edit-lock/derived-facts.js +0 -67
  46. package/src/edit-lock/domain.js +0 -165
  47. package/src/edit-lock/domains.js +0 -170
  48. package/src/edit-lock/host.js +0 -149
  49. package/src/edit-lock/incarnations.js +0 -150
  50. package/src/edit-lock/index.js +0 -1303
  51. package/src/edit-lock/inspect.js +0 -219
  52. package/src/edit-lock/lifecycle.js +0 -579
  53. package/src/edit-lock/limits.js +0 -61
  54. package/src/edit-lock/maintenance.js +0 -310
  55. package/src/edit-lock/manager.js +0 -875
  56. package/src/edit-lock/negotiation.js +0 -132
  57. package/src/edit-lock/operation-history.js +0 -214
  58. package/src/edit-lock/peer-client.js +0 -86
  59. package/src/edit-lock/peer-transport.js +0 -76
  60. package/src/edit-lock/peer.js +0 -87
  61. package/src/edit-lock/publisher.js +0 -191
  62. package/src/edit-lock/read-authority.js +0 -101
  63. package/src/edit-lock/recovery.js +0 -139
  64. package/src/edit-lock/remote.js +0 -362
  65. package/src/edit-lock/request-binding.js +0 -42
  66. package/src/edit-lock/request-data.js +0 -40
  67. package/src/edit-lock/reservation.js +0 -301
  68. package/src/edit-lock/reserved-runtime.js +0 -45
  69. package/src/edit-lock/resource-identity.js +0 -130
  70. package/src/edit-lock/runtime.js +0 -145
  71. package/src/edit-lock/settle.js +0 -144
  72. package/src/edit-lock/snapshot.js +0 -175
  73. package/src/edit-lock/stale-sweep.js +0 -120
  74. package/src/edit-lock/state.js +0 -462
  75. package/src/edit-lock/store.js +0 -327
  76. package/src/edit-lock/tool-scope.js +0 -29
  77. package/src/edit-lock/view.js +0 -170
  78. package/src/edit-lock/write-scopes.js +0 -48
  79. package/src/edit-lock/write-tool.js +0 -45
  80. package/src/shared/delegate-edit-lifecycle.js +0 -36
  81. package/src/worktree/authority.js +0 -56
@@ -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
- }