weir-harness 1.2.2 → 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 (89) 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/intent-gate/matcher.js +27 -0
  23. package/src/lease-broker/entry.js +98 -0
  24. package/src/lease-broker/node-contracts.d.ts +63 -0
  25. package/src/lease-broker/protocol.js +236 -0
  26. package/src/lease-broker/server.js +399 -0
  27. package/src/lease-broker/state.js +425 -0
  28. package/src/lsp/admin.js +24 -13
  29. package/src/lsp/index.js +5 -2
  30. package/src/lsp/registry.js +83 -17
  31. package/src/lsp/tools.js +137 -27
  32. package/src/notify/commands.js +15 -2
  33. package/src/notify/index.js +48 -10
  34. package/src/notify/web-channel.js +26 -3
  35. package/src/process/index.js +11 -0
  36. package/src/settings/index.js +2 -44
  37. package/src/settings/sections.js +29 -28
  38. package/src/shared/audit.js +6 -1
  39. package/src/todo-driver/index.js +1 -6
  40. package/src/worktree/cards.js +6 -21
  41. package/src/worktree/lane-core.js +1 -19
  42. package/src/worktree/lane-workflows.js +11 -32
  43. package/lib/client.edit-lock-maintenance.js +0 -592
  44. package/lib/client.edit-lock-panel.js +0 -405
  45. package/src/edit-lock/adapter.js +0 -57
  46. package/src/edit-lock/admin-ledger.js +0 -149
  47. package/src/edit-lock/admin-recovery.js +0 -151
  48. package/src/edit-lock/admission.js +0 -82
  49. package/src/edit-lock/binding-attestation.js +0 -29
  50. package/src/edit-lock/binding-guidance.js +0 -72
  51. package/src/edit-lock/binding-machine.js +0 -312
  52. package/src/edit-lock/call-context.js +0 -65
  53. package/src/edit-lock/derived-facts.js +0 -67
  54. package/src/edit-lock/domain.js +0 -165
  55. package/src/edit-lock/domains.js +0 -170
  56. package/src/edit-lock/host.js +0 -149
  57. package/src/edit-lock/incarnations.js +0 -150
  58. package/src/edit-lock/index.js +0 -1303
  59. package/src/edit-lock/inspect.js +0 -219
  60. package/src/edit-lock/lifecycle.js +0 -579
  61. package/src/edit-lock/limits.js +0 -61
  62. package/src/edit-lock/maintenance.js +0 -310
  63. package/src/edit-lock/manager.js +0 -875
  64. package/src/edit-lock/negotiation.js +0 -132
  65. package/src/edit-lock/operation-history.js +0 -214
  66. package/src/edit-lock/peer-client.js +0 -86
  67. package/src/edit-lock/peer-transport.js +0 -76
  68. package/src/edit-lock/peer.js +0 -87
  69. package/src/edit-lock/publisher.js +0 -191
  70. package/src/edit-lock/read-authority.js +0 -101
  71. package/src/edit-lock/recovery.js +0 -139
  72. package/src/edit-lock/remote.js +0 -362
  73. package/src/edit-lock/request-binding.js +0 -42
  74. package/src/edit-lock/request-data.js +0 -40
  75. package/src/edit-lock/reservation.js +0 -301
  76. package/src/edit-lock/reserved-runtime.js +0 -45
  77. package/src/edit-lock/resource-identity.js +0 -130
  78. package/src/edit-lock/runtime.js +0 -145
  79. package/src/edit-lock/settle.js +0 -144
  80. package/src/edit-lock/snapshot.js +0 -175
  81. package/src/edit-lock/stale-sweep.js +0 -120
  82. package/src/edit-lock/state.js +0 -462
  83. package/src/edit-lock/store.js +0 -327
  84. package/src/edit-lock/tool-scope.js +0 -29
  85. package/src/edit-lock/view.js +0 -170
  86. package/src/edit-lock/write-scopes.js +0 -48
  87. package/src/edit-lock/write-tool.js +0 -45
  88. package/src/shared/delegate-edit-lifecycle.js +0 -36
  89. 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
- }