dsh-live-teams 0.1.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/LICENSE +176 -0
- package/NOTICE +11 -0
- package/README.md +85 -0
- package/cordis.patch.yml +25 -0
- package/lib/binding.d.ts +18 -0
- package/lib/binding.js +42 -0
- package/lib/changed-paths.d.ts +26 -0
- package/lib/changed-paths.js +69 -0
- package/lib/client.js +6753 -0
- package/lib/command-queue.d.ts +60 -0
- package/lib/command-queue.js +185 -0
- package/lib/compatibility.js +109 -0
- package/lib/context-provider.d.ts +110 -0
- package/lib/context-provider.js +249 -0
- package/lib/dispatch.d.ts +174 -0
- package/lib/dispatch.js +624 -0
- package/lib/errors.d.ts +36 -0
- package/lib/errors.js +103 -0
- package/lib/git-artifacts.d.ts +50 -0
- package/lib/git-artifacts.js +242 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +14 -0
- package/lib/mailbox.d.ts +274 -0
- package/lib/mailbox.js +721 -0
- package/lib/member-tools.d.ts +57 -0
- package/lib/member-tools.js +1265 -0
- package/lib/migrations.d.ts +17 -0
- package/lib/migrations.js +47 -0
- package/lib/plugin.d.ts +106 -0
- package/lib/plugin.js +1003 -0
- package/lib/roles.d.ts +35 -0
- package/lib/roles.js +284 -0
- package/lib/routes.d.ts +586 -0
- package/lib/routes.js +2816 -0
- package/lib/scope.d.ts +62 -0
- package/lib/scope.js +133 -0
- package/lib/session-bridge.d.ts +76 -0
- package/lib/session-bridge.js +147 -0
- package/lib/session-title.js +35 -0
- package/lib/storage.d.ts +9 -0
- package/lib/storage.js +65 -0
- package/lib/task-store.d.ts +729 -0
- package/lib/task-store.js +2205 -0
- package/lib/team-store.d.ts +216 -0
- package/lib/team-store.js +765 -0
- package/lib/tree-snapshot.d.ts +28 -0
- package/lib/tree-snapshot.js +80 -0
- package/lib/types/client/TeamView.d.ts +26 -0
- package/lib/types/client/TeamView.dom.test.d.ts +1 -0
- package/lib/types/client/api.d.ts +522 -0
- package/lib/types/client/api.test.d.ts +1 -0
- package/lib/types/client/attention.d.ts +65 -0
- package/lib/types/client/attention.test.d.ts +1 -0
- package/lib/types/client/index.d.ts +31 -0
- package/lib/types/client/locales.d.ts +577 -0
- package/lib/types/client/member-name.d.ts +14 -0
- package/lib/types/client/member-name.test.d.ts +1 -0
- package/lib/types/client/roster.d.ts +26 -0
- package/lib/types/client/roster.test.d.ts +1 -0
- package/lib/types/client/styles.d.ts +3 -0
- package/package.json +104 -0
- package/roles/builder.md +40 -0
- package/roles/delegate.md +36 -0
- package/roles/lead.md +46 -0
- package/roles/oracle.md +36 -0
- package/roles/researcher.md +37 -0
- package/roles/reviewer.md +45 -0
- package/roles/scout.md +36 -0
- package/roles/verifier.md +36 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
import { Migration } from "./migrations.js";
|
|
2
|
+
import { TeamState } from "./team-store.js";
|
|
3
|
+
//#region src/task-store.d.ts
|
|
4
|
+
export type TaskKind = 'work' | 'research' | 'requirements' | 'implementation' | 'verification' | 'review' | 'repair' | 'integration';
|
|
5
|
+
export type TaskStatus = 'draft' | 'ready' | 'claimed' | 'in_progress' | 'waiting' | 'needs_human' | 'completed' | 'failed' | 'cancelled';
|
|
6
|
+
export type TaskRoute = {
|
|
7
|
+
type: 'report';
|
|
8
|
+
to: string | 'human';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'review';
|
|
11
|
+
reviewer: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: 'interactive';
|
|
14
|
+
then: TaskRoute;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'custom';
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
/** The durable task shape deliberately leaves room for later Phase 4 fields. */
|
|
20
|
+
export type HumanContact = 'none' | 'expected' | 'required';
|
|
21
|
+
export interface TeamTask {
|
|
22
|
+
id: string;
|
|
23
|
+
teamId: string;
|
|
24
|
+
title: string;
|
|
25
|
+
kind: TaskKind;
|
|
26
|
+
status: TaskStatus;
|
|
27
|
+
assignee?: string;
|
|
28
|
+
route?: TaskRoute;
|
|
29
|
+
humanContact?: HumanContact;
|
|
30
|
+
activeAttemptId?: string;
|
|
31
|
+
latestSubmissionId?: string;
|
|
32
|
+
acceptedSubmissionId?: string;
|
|
33
|
+
acceptance: string[];
|
|
34
|
+
roundLimit: number;
|
|
35
|
+
createdBy: string;
|
|
36
|
+
createdAt: number;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
revision: number;
|
|
39
|
+
/** Set after lead acceptance; the authored document then lives under archive/. */
|
|
40
|
+
archivedPath?: string;
|
|
41
|
+
/** Latest review round, populated by list projections when reviews exist. */
|
|
42
|
+
round?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Tasks in this team that must be `completed` before this one may be claimed.
|
|
45
|
+
* Immutable in B26: changing them is contract editing (B27).
|
|
46
|
+
*/
|
|
47
|
+
dependsOn?: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Workspace-relative globs this task may touch (B30). The lead declares them; two
|
|
50
|
+
* writing tasks with intersecting declarations do not run at the same time, and a
|
|
51
|
+
* submission records what actually changed outside them.
|
|
52
|
+
*/
|
|
53
|
+
paths?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* Bumped only when the *work itself* is restated — creation, a contract update, an
|
|
56
|
+
* acceptance, a revision — never by claiming, acknowledging, submitting or revoking.
|
|
57
|
+
* A mark left by a human intervention is resolved by exactly that (B31), so a
|
|
58
|
+
* revocation cannot silently un-suppress a task whose contract nobody rewrote.
|
|
59
|
+
*/
|
|
60
|
+
contractRevision?: number;
|
|
61
|
+
/**
|
|
62
|
+
* The authored narrative of the contract. It used to live only in the rendered
|
|
63
|
+
* document, which made the record a partial copy of its own contract and made
|
|
64
|
+
* editing impossible without losing it (B27).
|
|
65
|
+
*/
|
|
66
|
+
why?: string;
|
|
67
|
+
body?: string;
|
|
68
|
+
checks?: string[];
|
|
69
|
+
/** True only in list projections when this task's authored document is absent. */
|
|
70
|
+
documentMissing?: boolean;
|
|
71
|
+
[key: string]: unknown;
|
|
72
|
+
/**
|
|
73
|
+
* Written just before an integration moves the branch, cleared by the write that finishes the
|
|
74
|
+
* acceptance. A process that dies in between leaves it behind, which is how the reconciler can tell
|
|
75
|
+
* "the branch moved but the task was never completed" from "nothing happened" (review finding 1).
|
|
76
|
+
*/
|
|
77
|
+
integrationPending?: {
|
|
78
|
+
baseRef: string;
|
|
79
|
+
headSha: string;
|
|
80
|
+
baseTipSha: string;
|
|
81
|
+
at: number;
|
|
82
|
+
reportedAt?: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export interface TaskAttempt {
|
|
86
|
+
id: string;
|
|
87
|
+
taskId: string;
|
|
88
|
+
memberId: string;
|
|
89
|
+
sessionId: string;
|
|
90
|
+
bindingGeneration: number;
|
|
91
|
+
generation: number;
|
|
92
|
+
/**
|
|
93
|
+
* `revoking` is durable on purpose: the capability is already invalid and the
|
|
94
|
+
* session has been asked to stop, but quiescence has not been observed yet. A
|
|
95
|
+
* crash mid-transaction then leaves an honest record, not an active-looking
|
|
96
|
+
* attempt (B28).
|
|
97
|
+
*/
|
|
98
|
+
status: 'active' | 'revoking' | 'revoked' | 'completed' | 'failed';
|
|
99
|
+
startedAt: number;
|
|
100
|
+
/**
|
|
101
|
+
* When a human wrote into this member's Session directly, mid-attempt (B31), and the
|
|
102
|
+
* task revision at that moment. The mark stops automatic dispatch until the contract
|
|
103
|
+
* is formally restated — an update, an acceptance or a revision bumps the task
|
|
104
|
+
* revision above this value, which is what resolves it.
|
|
105
|
+
*/
|
|
106
|
+
humanIntervenedAt?: number;
|
|
107
|
+
humanIntervenedContractRevision?: number;
|
|
108
|
+
/**
|
|
109
|
+
* When the reconciler first noticed that this attempt is not moving: the assignment was never
|
|
110
|
+
* acknowledged, or the member's Session went idle with the work unfinished (F3 of the live pilot).
|
|
111
|
+
* Recorded on the attempt so the notice survives restarts and is written once, not every pass.
|
|
112
|
+
* Nothing is revoked on the strength of it — silence is not death, it is something to show.
|
|
113
|
+
*/
|
|
114
|
+
stalled?: {
|
|
115
|
+
at: number;
|
|
116
|
+
reason: 'unacknowledged' | 'idle';
|
|
117
|
+
detail: string;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Phase 6: the branch and tip this attempt was based on, recorded when it claimed the task.
|
|
121
|
+
* It is the acceptance precondition — integration refuses if the base moved since.
|
|
122
|
+
*/
|
|
123
|
+
gitBase?: {
|
|
124
|
+
ref: string;
|
|
125
|
+
baseTipSha: string;
|
|
126
|
+
};
|
|
127
|
+
revision: number;
|
|
128
|
+
}
|
|
129
|
+
export interface AttemptsState {
|
|
130
|
+
schemaVersion: number;
|
|
131
|
+
revision: number;
|
|
132
|
+
teamId: string;
|
|
133
|
+
attempts: TaskAttempt[];
|
|
134
|
+
[key: string]: unknown;
|
|
135
|
+
}
|
|
136
|
+
export interface ClaimTaskRequest {
|
|
137
|
+
taskId: string;
|
|
138
|
+
memberId: string;
|
|
139
|
+
sessionId: string;
|
|
140
|
+
bindingGeneration: number;
|
|
141
|
+
}
|
|
142
|
+
export interface ClaimTaskResult {
|
|
143
|
+
task: TeamTask;
|
|
144
|
+
attempt: TaskAttempt;
|
|
145
|
+
}
|
|
146
|
+
export interface AcknowledgeTaskRequest {
|
|
147
|
+
taskId: string;
|
|
148
|
+
memberId: string;
|
|
149
|
+
/** Required: the fence of ADR 0005 is checked against the binding the caller presents. */
|
|
150
|
+
sessionId: string;
|
|
151
|
+
bindingGeneration: number;
|
|
152
|
+
}
|
|
153
|
+
export interface TasksState {
|
|
154
|
+
schemaVersion: number;
|
|
155
|
+
revision: number;
|
|
156
|
+
teamId: string;
|
|
157
|
+
tasks: TeamTask[];
|
|
158
|
+
[key: string]: unknown;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A contract edit. An absent key means "leave it"; `null` clears a field that is
|
|
162
|
+
* allowed to be empty. Only the lead may send one, and only while the task is
|
|
163
|
+
* `draft` or `ready` — see the B27 contract.
|
|
164
|
+
*/
|
|
165
|
+
export interface UpdateTaskRequest {
|
|
166
|
+
taskId: string;
|
|
167
|
+
actor: string;
|
|
168
|
+
expectedRevision?: number;
|
|
169
|
+
title?: string;
|
|
170
|
+
kind?: TaskKind;
|
|
171
|
+
assignee?: string | null;
|
|
172
|
+
route?: TaskRoute | null;
|
|
173
|
+
acceptance?: readonly string[];
|
|
174
|
+
checks?: readonly string[];
|
|
175
|
+
why?: string;
|
|
176
|
+
body?: string;
|
|
177
|
+
roundLimit?: number;
|
|
178
|
+
humanContact?: HumanContact;
|
|
179
|
+
dependsOn?: readonly string[] | null;
|
|
180
|
+
paths?: readonly string[] | null;
|
|
181
|
+
}
|
|
182
|
+
export type UpdateTaskResult = {
|
|
183
|
+
readonly task: TeamTask;
|
|
184
|
+
readonly document: string;
|
|
185
|
+
readonly changed: readonly string[];
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Take work away from its current attempt (B28).
|
|
189
|
+
*
|
|
190
|
+
* `nextAssignee` moves ownership inside the same transaction; the contract is not
|
|
191
|
+
* touched, and the new owner claims the task themselves — the only path that has
|
|
192
|
+
* ever created an attempt.
|
|
193
|
+
*/
|
|
194
|
+
export interface RevokeTaskRequest {
|
|
195
|
+
taskId: string;
|
|
196
|
+
actor: string;
|
|
197
|
+
reason: string;
|
|
198
|
+
nextAssignee?: string;
|
|
199
|
+
expectedRevision?: number;
|
|
200
|
+
}
|
|
201
|
+
export type RevokeTaskResult = {
|
|
202
|
+
readonly task: TeamTask;
|
|
203
|
+
readonly attempt: TaskAttempt;
|
|
204
|
+
readonly quiesced: boolean;
|
|
205
|
+
readonly reassigned: boolean;
|
|
206
|
+
};
|
|
207
|
+
export interface CreateTaskRequest {
|
|
208
|
+
title: string;
|
|
209
|
+
kind: TaskKind;
|
|
210
|
+
assignee?: string;
|
|
211
|
+
route?: TaskRoute;
|
|
212
|
+
acceptance: readonly string[];
|
|
213
|
+
humanContact?: HumanContact;
|
|
214
|
+
checks?: readonly string[];
|
|
215
|
+
why?: string;
|
|
216
|
+
body?: string;
|
|
217
|
+
roundLimit?: number;
|
|
218
|
+
dependsOn?: readonly string[];
|
|
219
|
+
paths?: readonly string[];
|
|
220
|
+
actor: string;
|
|
221
|
+
expectedRevision?: number;
|
|
222
|
+
}
|
|
223
|
+
export type SubmissionArtifact = {
|
|
224
|
+
type: 'report';
|
|
225
|
+
bodyRef: string;
|
|
226
|
+
bodyDigest: string;
|
|
227
|
+
/**
|
|
228
|
+
* Where the frozen bytes live, relative to the team directory. The digest proves *what* was
|
|
229
|
+
* submitted; this makes it possible to read it back after the workspace moved on — the author can
|
|
230
|
+
* rewrite the file at `bodyRef` the moment after submitting, and a reviewer that reads the path
|
|
231
|
+
* would see the new text while the digest still describes the old one.
|
|
232
|
+
*/
|
|
233
|
+
blobPath?: string;
|
|
234
|
+
} | {
|
|
235
|
+
type: 'files';
|
|
236
|
+
paths: string[];
|
|
237
|
+
manifestDigest: string;
|
|
238
|
+
/** One frozen copy per path, in the same order as `paths`. */
|
|
239
|
+
blobs?: {
|
|
240
|
+
path: string;
|
|
241
|
+
digest: string;
|
|
242
|
+
blobPath: string;
|
|
243
|
+
}[];
|
|
244
|
+
};
|
|
245
|
+
export type SubmissionSourceArtifact = {
|
|
246
|
+
type: 'report';
|
|
247
|
+
bodyRef: string;
|
|
248
|
+
} | {
|
|
249
|
+
type: 'files';
|
|
250
|
+
paths: string[];
|
|
251
|
+
};
|
|
252
|
+
export type SubmissionCheck = {
|
|
253
|
+
command: string;
|
|
254
|
+
status: 'passed' | 'failed';
|
|
255
|
+
output?: string;
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* What the declaration was worth at submission time (B30). `unchecked` is a
|
|
259
|
+
* first-class outcome: an unobservable workspace must not read as a clean one.
|
|
260
|
+
*/
|
|
261
|
+
export interface SubmissionScope {
|
|
262
|
+
declared: string[];
|
|
263
|
+
changed?: string[];
|
|
264
|
+
unexpected?: string[];
|
|
265
|
+
/**
|
|
266
|
+
* Paths outside this declaration that another *active* task was writing at the same
|
|
267
|
+
* time. The first parallel run showed that in one shared tree this is what most
|
|
268
|
+
* "unexpected" changes are: a colleague working on their own package, not an
|
|
269
|
+
* intrusion into this one. The name says what was observed.
|
|
270
|
+
*/
|
|
271
|
+
sharedTree?: string[];
|
|
272
|
+
state: 'clean' | 'violation' | 'shared-tree' | 'unchecked';
|
|
273
|
+
/**
|
|
274
|
+
* Where this record's evidence came from (B32): `git` in a work tree, `files` in a folder
|
|
275
|
+
* that is not a repository. The record keeps its shape either way; the mode says what the
|
|
276
|
+
* evidence is worth.
|
|
277
|
+
*/
|
|
278
|
+
mode?: 'git' | 'files';
|
|
279
|
+
/** Why the workspace could not be observed, when `state` is `unchecked`. */
|
|
280
|
+
reason?: 'no-repository' | 'too-many-files' | 'no-snapshot' | 'unreadable';
|
|
281
|
+
/**
|
|
282
|
+
* Phase 6: the paths that went into the submission's commit — exactly what the member
|
|
283
|
+
* submitted. `changed` stays the working tree's own story, so an edit outside the declaration
|
|
284
|
+
* is still visible even though the reviewed artifact is a commit.
|
|
285
|
+
*/
|
|
286
|
+
committed?: string[];
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The Git half of a submission (ADR 0006): the commit the plugin built from the submitted paths,
|
|
290
|
+
* the base it was built on, and the boundary of the reviewed diff.
|
|
291
|
+
*/
|
|
292
|
+
export interface SubmissionGit {
|
|
293
|
+
baseRef: string;
|
|
294
|
+
baseTipSha: string;
|
|
295
|
+
/**
|
|
296
|
+
* Set when the recorded claim-time base had already moved by the time the submission was frozen
|
|
297
|
+
* and the movement could not touch the declared paths: the commit is parented on the base as it
|
|
298
|
+
* was at freeze time, and this names the base the attempt actually started from.
|
|
299
|
+
*/
|
|
300
|
+
rebasedFrom?: string;
|
|
301
|
+
headSha: string;
|
|
302
|
+
mergeBaseSha?: string;
|
|
303
|
+
commitPaths: string[];
|
|
304
|
+
}
|
|
305
|
+
/** A path whose workspace content no longer matches what the submission froze. */
|
|
306
|
+
export interface SubmissionDrift {
|
|
307
|
+
path: string;
|
|
308
|
+
expected: string;
|
|
309
|
+
/** `null` when the file is gone. */
|
|
310
|
+
actual: string | null;
|
|
311
|
+
}
|
|
312
|
+
export interface Submission {
|
|
313
|
+
id: string;
|
|
314
|
+
taskId: string;
|
|
315
|
+
attemptId: string;
|
|
316
|
+
attemptGeneration: number;
|
|
317
|
+
artifact: SubmissionArtifact;
|
|
318
|
+
/**
|
|
319
|
+
* Phase 6: the commit this submission is reviewed as. It belongs to the submission, not to an
|
|
320
|
+
* artifact shape — a coding task that submits a report still has a diff to review.
|
|
321
|
+
*/
|
|
322
|
+
git?: SubmissionGit;
|
|
323
|
+
summary: string;
|
|
324
|
+
checks: SubmissionCheck[];
|
|
325
|
+
scope?: SubmissionScope;
|
|
326
|
+
createdAt: number;
|
|
327
|
+
revision: number;
|
|
328
|
+
}
|
|
329
|
+
export type ReviewFinding = {
|
|
330
|
+
severity: 'low' | 'medium' | 'high' | 'blocker';
|
|
331
|
+
problem: string;
|
|
332
|
+
requiredFix: string;
|
|
333
|
+
artifactLocation?: string;
|
|
334
|
+
};
|
|
335
|
+
export type ReviewVerdict = 'pass' | 'needs_revision' | 'escalate';
|
|
336
|
+
export interface ReviewRound {
|
|
337
|
+
id: string;
|
|
338
|
+
taskId: string;
|
|
339
|
+
submissionId: string;
|
|
340
|
+
reviewerMemberId: string;
|
|
341
|
+
round: number;
|
|
342
|
+
verdict: ReviewVerdict;
|
|
343
|
+
findings: ReviewFinding[];
|
|
344
|
+
evidence: SubmissionCheck[];
|
|
345
|
+
createdAt: number;
|
|
346
|
+
revision: number;
|
|
347
|
+
}
|
|
348
|
+
export interface SubmissionsState {
|
|
349
|
+
schemaVersion: number;
|
|
350
|
+
revision: number;
|
|
351
|
+
teamId: string;
|
|
352
|
+
submissions: Submission[];
|
|
353
|
+
[key: string]: unknown;
|
|
354
|
+
}
|
|
355
|
+
export interface ReviewsState {
|
|
356
|
+
schemaVersion: number;
|
|
357
|
+
revision: number;
|
|
358
|
+
teamId: string;
|
|
359
|
+
rounds: ReviewRound[];
|
|
360
|
+
[key: string]: unknown;
|
|
361
|
+
}
|
|
362
|
+
export interface SubmitTaskRequest {
|
|
363
|
+
taskId: string;
|
|
364
|
+
memberId: string;
|
|
365
|
+
sessionId: string;
|
|
366
|
+
bindingGeneration: number;
|
|
367
|
+
artifact: SubmissionSourceArtifact;
|
|
368
|
+
summary: string;
|
|
369
|
+
checks: readonly SubmissionCheck[];
|
|
370
|
+
}
|
|
371
|
+
export interface AcceptTaskRequest {
|
|
372
|
+
taskId: string;
|
|
373
|
+
actor: string;
|
|
374
|
+
note?: string;
|
|
375
|
+
}
|
|
376
|
+
export interface AcceptTaskResult {
|
|
377
|
+
task: TeamTask;
|
|
378
|
+
archivedPath: string;
|
|
379
|
+
}
|
|
380
|
+
export interface ReviseTaskRequest {
|
|
381
|
+
taskId: string;
|
|
382
|
+
actor: string;
|
|
383
|
+
instructions: string;
|
|
384
|
+
}
|
|
385
|
+
export interface ReviseTaskResult {
|
|
386
|
+
task: TeamTask;
|
|
387
|
+
}
|
|
388
|
+
export interface ReviewTaskRequest {
|
|
389
|
+
taskId: string;
|
|
390
|
+
submissionId: string;
|
|
391
|
+
reviewerMemberId: string;
|
|
392
|
+
verdict: ReviewVerdict;
|
|
393
|
+
findings?: readonly ReviewFinding[];
|
|
394
|
+
evidence?: readonly SubmissionCheck[];
|
|
395
|
+
}
|
|
396
|
+
export interface ReviewTaskResult {
|
|
397
|
+
round: ReviewRound;
|
|
398
|
+
task: TeamTask;
|
|
399
|
+
}
|
|
400
|
+
export interface ReadTaskResult {
|
|
401
|
+
task: TeamTask;
|
|
402
|
+
document: string;
|
|
403
|
+
/**
|
|
404
|
+
* Submissions as they were frozen, plus a drift note per submission: paths whose workspace content
|
|
405
|
+
* no longer matches the frozen bytes. A reviewer that reads the workspace path would otherwise
|
|
406
|
+
* judge text the digest does not describe.
|
|
407
|
+
*/
|
|
408
|
+
submissions: (Submission & {
|
|
409
|
+
drift?: SubmissionDrift[];
|
|
410
|
+
})[];
|
|
411
|
+
rounds: ReviewRound[];
|
|
412
|
+
/**
|
|
413
|
+
* Every attempt this task ever had, oldest first. A task sitting in `claimed` with an
|
|
414
|
+
* attempt nobody acknowledged is the state a human most needs to see, and reading the
|
|
415
|
+
* journal for it was the only way before B31.
|
|
416
|
+
*/
|
|
417
|
+
attempts: TaskAttempt[];
|
|
418
|
+
}
|
|
419
|
+
export interface CreateTaskResult {
|
|
420
|
+
task: TeamTask;
|
|
421
|
+
documentPath: string;
|
|
422
|
+
}
|
|
423
|
+
export interface ListTasksOptions {
|
|
424
|
+
includeArchived?: boolean;
|
|
425
|
+
}
|
|
426
|
+
type TaskStoreOptions = {
|
|
427
|
+
workspacePath: string;
|
|
428
|
+
teamId: string;
|
|
429
|
+
appendAudit?: (event: unknown) => Promise<void>;
|
|
430
|
+
deliverEscalation?: (request: {
|
|
431
|
+
leadMemberId: string;
|
|
432
|
+
taskId: string;
|
|
433
|
+
submissionId: string;
|
|
434
|
+
round: ReviewRound;
|
|
435
|
+
reviewerMemberId: string;
|
|
436
|
+
problem: string;
|
|
437
|
+
tried: string;
|
|
438
|
+
}) => Promise<void>;
|
|
439
|
+
deliverClosing?: (request: {
|
|
440
|
+
assignee: string;
|
|
441
|
+
taskId: string;
|
|
442
|
+
note?: string;
|
|
443
|
+
}) => Promise<void>;
|
|
444
|
+
deliverDirection?: (request: {
|
|
445
|
+
assignee: string;
|
|
446
|
+
taskId: string;
|
|
447
|
+
instructions: string;
|
|
448
|
+
}) => Promise<void>;
|
|
449
|
+
/**
|
|
450
|
+
* Ask a Session to stop. Absent means the store cannot interrupt, and a
|
|
451
|
+
* revocation then refuses rather than pretending the session was stopped.
|
|
452
|
+
*/
|
|
453
|
+
interruptSession?: (sessionId: string, reason: string) => Promise<void>;
|
|
454
|
+
/** Observe that a Session is no longer running. `false` means "not observed in time". */
|
|
455
|
+
awaitQuiescence?: (sessionId: string, timeoutMs: number) => Promise<boolean>;
|
|
456
|
+
quiescenceTimeoutMs?: number;
|
|
457
|
+
/**
|
|
458
|
+
* What changed in the workspace at submission time (B30). `undefined` means "not
|
|
459
|
+
* observable" — no git, or not a repository — and is recorded as `unchecked`
|
|
460
|
+
* rather than as a clean result. The store never shells out itself.
|
|
461
|
+
*/
|
|
462
|
+
changedPaths?: (workspacePath: string) => Promise<readonly string[] | undefined>;
|
|
463
|
+
/**
|
|
464
|
+
* B32: which observation this workspace supports. Absent means the composition cannot
|
|
465
|
+
* tell, and a scope record says `unchecked (unreadable)` rather than reading as clean.
|
|
466
|
+
*/
|
|
467
|
+
workspaceMode?: (workspacePath: string) => Promise<'git' | 'files' | undefined>;
|
|
468
|
+
/** Migration steps to use; the module's registry by default, injectable so a test can add one. */
|
|
469
|
+
migrations?: Readonly<Record<number, Migration>>;
|
|
470
|
+
/** B32: a cheap fingerprint of the tree, taken at claim and at submit in `files` mode. */
|
|
471
|
+
snapshotTree?: (workspacePath: string) => Promise<Readonly<Record<string, string>> | undefined>;
|
|
472
|
+
/**
|
|
473
|
+
* Phase 6 (ADR 0006): Git plumbing, injected the same way the changed-paths port is. The store
|
|
474
|
+
* never shells out itself. Absent means submissions carry no Git artifact — a workspace without
|
|
475
|
+
* a repository, or a composition that does not offer one.
|
|
476
|
+
*/
|
|
477
|
+
git?: {
|
|
478
|
+
base: (workspacePath: string) => Promise<{
|
|
479
|
+
ref: string;
|
|
480
|
+
sha: string;
|
|
481
|
+
} | undefined>;
|
|
482
|
+
tip: (workspacePath: string, ref: string) => Promise<string | undefined>;
|
|
483
|
+
commit: (workspacePath: string, base: {
|
|
484
|
+
ref: string;
|
|
485
|
+
sha: string;
|
|
486
|
+
} | undefined, request: {
|
|
487
|
+
paths: readonly string[];
|
|
488
|
+
message: string;
|
|
489
|
+
author: {
|
|
490
|
+
name: string;
|
|
491
|
+
email: string;
|
|
492
|
+
};
|
|
493
|
+
}) => Promise<{
|
|
494
|
+
headSha: string;
|
|
495
|
+
treeSha: string;
|
|
496
|
+
commitPaths: readonly string[];
|
|
497
|
+
mergeBaseSha?: string;
|
|
498
|
+
} | undefined>;
|
|
499
|
+
pin: (workspacePath: string, ref: string, sha: string) => Promise<boolean>;
|
|
500
|
+
integrate: (workspacePath: string, ref: string, headSha: string, expectedTip: string | undefined, commitPaths?: readonly string[]) => Promise<'integrated' | 'stale' | 'index-failed'>;
|
|
501
|
+
/** Paths changed between two commits, for deciding whether a moved base can be refreshed safely. */
|
|
502
|
+
changedBetween: (workspacePath: string, fromSha: string, toSha: string) => Promise<readonly string[] | undefined>;
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* The dependencies that still stand between a task and its claim, in the order
|
|
507
|
+
* the contract lists them. A dependency in a terminal non-completed state is
|
|
508
|
+
* included on purpose: it blocks forever and the human must see that.
|
|
509
|
+
*/
|
|
510
|
+
export declare function blockedBy(task: Pick<TeamTask, 'dependsOn'>, tasks: readonly TeamTask[]): Array<{
|
|
511
|
+
id: string;
|
|
512
|
+
title: string;
|
|
513
|
+
status: TaskStatus;
|
|
514
|
+
}>;
|
|
515
|
+
export declare function routeText(route: TaskRoute | undefined, team: TeamState): string;
|
|
516
|
+
export declare function assignmentRouteText(route: TaskRoute | undefined, team: TeamState): string;
|
|
517
|
+
export declare function deriveRouteContacts(task: TeamTask, team: TeamState): string[];
|
|
518
|
+
export declare function routeAllowsContact(task: TeamTask, from: string, to: string, team: TeamState): boolean;
|
|
519
|
+
export declare function taskDocumentPath(workspacePath: string, teamId: string, taskId: string): string;
|
|
520
|
+
declare function renderDocument(task: TeamTask, team: TeamState, fields?: {
|
|
521
|
+
why?: string;
|
|
522
|
+
checks?: readonly string[];
|
|
523
|
+
body?: string;
|
|
524
|
+
tasks?: readonly TeamTask[];
|
|
525
|
+
}): string;
|
|
526
|
+
export declare class TaskStore {
|
|
527
|
+
private readonly location;
|
|
528
|
+
private readonly workspacePath;
|
|
529
|
+
private readonly appendAudit;
|
|
530
|
+
private readonly deliverEscalation?;
|
|
531
|
+
private readonly deliverClosing?;
|
|
532
|
+
private readonly deliverDirection?;
|
|
533
|
+
private readonly interruptSession?;
|
|
534
|
+
private readonly awaitQuiescence?;
|
|
535
|
+
private readonly quiescenceTimeoutMs;
|
|
536
|
+
private readonly changedPaths;
|
|
537
|
+
private readonly git;
|
|
538
|
+
private readonly migrations;
|
|
539
|
+
private readonly workspaceMode;
|
|
540
|
+
private readonly snapshotTree;
|
|
541
|
+
/** `null` means "no port": the answer is cached because a store lives with one workspace. */
|
|
542
|
+
private resolvedMode;
|
|
543
|
+
constructor(options: TaskStoreOptions);
|
|
544
|
+
get filePath(): string;
|
|
545
|
+
get directoryPath(): string;
|
|
546
|
+
open(): Promise<TasksState>;
|
|
547
|
+
listTasks(options?: ListTasksOptions | boolean): Promise<TeamTask[]>;
|
|
548
|
+
readTask(taskId: string): Promise<ReadTaskResult>;
|
|
549
|
+
readAttempts(): Promise<AttemptsState>;
|
|
550
|
+
readSubmissions(): Promise<SubmissionsState>;
|
|
551
|
+
readReviews(): Promise<ReviewsState>;
|
|
552
|
+
activeTaskForMember(memberId: string): Promise<TeamTask | undefined>;
|
|
553
|
+
activeTaskForMemberSync(memberId: string): TeamTask | undefined;
|
|
554
|
+
/**
|
|
555
|
+
* The latest attempt of one task, read synchronously for context assembly: the member
|
|
556
|
+
* has to know that a human wrote into its Session mid-flight (B31).
|
|
557
|
+
*/
|
|
558
|
+
latestAttemptForTaskSync(taskId: string): TaskAttempt | undefined;
|
|
559
|
+
/**
|
|
560
|
+
* Every task, read synchronously for context assembly. The authorization gate
|
|
561
|
+
* scans all tasks — a reviewer holds a path to a task it is not the assignee of —
|
|
562
|
+
* so the line a member reads about where it may send has to see the same set.
|
|
563
|
+
*/
|
|
564
|
+
listTasksSync(): TeamTask[];
|
|
565
|
+
/**
|
|
566
|
+
* Close attempts whose task has already ended (F5 of the first live run).
|
|
567
|
+
*
|
|
568
|
+
* Both acceptance paths are supposed to close the attempt, but "supposed to" is how
|
|
569
|
+
* the member stayed busy forever: a stale active attempt blocks every later task for
|
|
570
|
+
* that member and keeps the intervention observer watching a finished Session. This
|
|
571
|
+
* heals the class rather than the instance — a task in a terminal status cannot have
|
|
572
|
+
* work in flight.
|
|
573
|
+
*/
|
|
574
|
+
closeTerminalAttempts(): Promise<{
|
|
575
|
+
closed: {
|
|
576
|
+
attemptId: string;
|
|
577
|
+
taskId: string;
|
|
578
|
+
memberId: string;
|
|
579
|
+
status: 'completed' | 'failed';
|
|
580
|
+
}[];
|
|
581
|
+
}>;
|
|
582
|
+
/**
|
|
583
|
+
* Record that a human wrote into this member's Session mid-attempt (B31). Idempotent:
|
|
584
|
+
* the first observation wins, and the task revision at that moment is stored so that a
|
|
585
|
+
* later formal restatement of the contract resolves the mark without a second write.
|
|
586
|
+
*/
|
|
587
|
+
markHumanIntervention(attemptId: string, at: number): Promise<{
|
|
588
|
+
attempt: TaskAttempt;
|
|
589
|
+
changed: boolean;
|
|
590
|
+
}>;
|
|
591
|
+
private contractRevisionLocked;
|
|
592
|
+
submitTask(request: SubmitTaskRequest): Promise<{
|
|
593
|
+
submission: Submission;
|
|
594
|
+
}>;
|
|
595
|
+
acceptTask(request: AcceptTaskRequest): Promise<AcceptTaskResult>;
|
|
596
|
+
reviseTask(request: ReviseTaskRequest): Promise<ReviseTaskResult>;
|
|
597
|
+
reviewTask(request: ReviewTaskRequest): Promise<ReviewTaskResult>;
|
|
598
|
+
/**
|
|
599
|
+
* What the declaration was worth at submission time. Only a writing task that
|
|
600
|
+
* declared paths is checked: with nothing declared there is nothing to violate, and
|
|
601
|
+
* an unobservable workspace is reported as `unchecked` rather than as clean (B30).
|
|
602
|
+
*/
|
|
603
|
+
/**
|
|
604
|
+
* Integrate a submission's commit into its base, refusing when the base moved.
|
|
605
|
+
*
|
|
606
|
+
* Nothing is checked out and nothing is merged: the working tree already holds the work, so
|
|
607
|
+
* integration is one compare-and-swap ref update. `STALE_BASE` is a refusal — nothing is written,
|
|
608
|
+
* and the decision has to be made again against the current base (ADR 0006).
|
|
609
|
+
*/
|
|
610
|
+
private integrateSubmission;
|
|
611
|
+
/**
|
|
612
|
+
* Record the intent to integrate, and return the task as it now stands.
|
|
613
|
+
*
|
|
614
|
+
* `Locked` on purpose: the callers (both acceptance paths) already hold the team lock, and taking it
|
|
615
|
+
* again deadlocks the process — which is exactly what the git suite reported, as a thirty-second
|
|
616
|
+
* hang rather than a failure.
|
|
617
|
+
*/
|
|
618
|
+
private writeIntegrationPendingLocked;
|
|
619
|
+
/** Forget the intent: either the integration did not happen, or the caller is about to finish it. */
|
|
620
|
+
private clearIntegrationPendingLocked;
|
|
621
|
+
/**
|
|
622
|
+
* Where an archived document lives. New records store the path relative to the team directory;
|
|
623
|
+
* records written before that change hold an absolute path and are still honoured.
|
|
624
|
+
*/
|
|
625
|
+
private archivedPathOf;
|
|
626
|
+
/**
|
|
627
|
+
* Record that an active attempt is not moving, once. Idempotent by design: a second call with the
|
|
628
|
+
* same reason is a no-op, so the reconciler may call it on every pass without filling the journal.
|
|
629
|
+
* Returns `true` when this call was the one that recorded it.
|
|
630
|
+
*/
|
|
631
|
+
markAttemptStalled(request: {
|
|
632
|
+
attemptId: string;
|
|
633
|
+
reason: 'unacknowledged' | 'idle';
|
|
634
|
+
detail: string;
|
|
635
|
+
actor: string;
|
|
636
|
+
}): Promise<boolean>;
|
|
637
|
+
/**
|
|
638
|
+
* Keep a copy of submitted bytes, addressed by their digest. Returns the path relative to the team
|
|
639
|
+
* directory, or `undefined` when the copy could not be written — a submission is never refused for
|
|
640
|
+
* this, but it is then read back from the workspace and the drift check says so.
|
|
641
|
+
*/
|
|
642
|
+
private storeBlob;
|
|
643
|
+
/** The frozen bytes of a submission artifact, or `undefined` when no copy was kept. */
|
|
644
|
+
readBlob(blobPath: string): Promise<Buffer | undefined>;
|
|
645
|
+
/**
|
|
646
|
+
* Reconcile an integration that was interrupted: the marker is on the task, and the branch tells
|
|
647
|
+
* the rest of the story. Reports once, and clears the marker only when nothing happened — a branch
|
|
648
|
+
* that is already at the reviewed commit means the acceptance still has to be finished, and that is
|
|
649
|
+
* a decision, not a cleanup.
|
|
650
|
+
*/
|
|
651
|
+
reconcilePendingIntegrations(request: {
|
|
652
|
+
tipOf: (ref: string) => Promise<string | undefined>;
|
|
653
|
+
actor: string;
|
|
654
|
+
}): Promise<{
|
|
655
|
+
taskId: string;
|
|
656
|
+
state: 'moved' | 'untouched' | 'elsewhere';
|
|
657
|
+
detail: string;
|
|
658
|
+
}[]>;
|
|
659
|
+
/**
|
|
660
|
+
* Bring every state file this store owns forward to the current schema version.
|
|
661
|
+
*
|
|
662
|
+
* Reading tolerates an older version, but nothing brought it forward: a field added later stayed
|
|
663
|
+
* missing until something happened to write the file again, and no record said the state had been
|
|
664
|
+
* read at an older version. A state from the future is refused with both versions named.
|
|
665
|
+
*
|
|
666
|
+
* The file is copied before it is rewritten, and the migration is recorded in the journal: a
|
|
667
|
+
* migration is the one operation that changes history, and it should be possible to say when it
|
|
668
|
+
* happened and to go back.
|
|
669
|
+
*/
|
|
670
|
+
migrate(): Promise<{
|
|
671
|
+
files: {
|
|
672
|
+
file: string;
|
|
673
|
+
from: number;
|
|
674
|
+
to: number;
|
|
675
|
+
backup: string;
|
|
676
|
+
}[];
|
|
677
|
+
}>;
|
|
678
|
+
/** B32: resolved once per store; the answer cannot change while a store lives. */
|
|
679
|
+
private mode;
|
|
680
|
+
private snapshotPathFor;
|
|
681
|
+
/** Take the fingerprint a `files`-mode submission will be compared against. */
|
|
682
|
+
private takeSnapshot;
|
|
683
|
+
/** Delete fingerprints whose attempt is no longer running: garbage, not evidence. */
|
|
684
|
+
pruneSnapshots(): Promise<number>;
|
|
685
|
+
private checkScope;
|
|
686
|
+
private freezeArtifact;
|
|
687
|
+
/**
|
|
688
|
+
* Edit the contract of one task (B27).
|
|
689
|
+
*
|
|
690
|
+
* Only while nobody is working on it: an attempt is bound to the contract its
|
|
691
|
+
* assignee is working from, and a human decision owns every later transition.
|
|
692
|
+
*/
|
|
693
|
+
updateTask(request: UpdateTaskRequest): Promise<UpdateTaskResult>;
|
|
694
|
+
claimTask(request: ClaimTaskRequest): Promise<ClaimTaskResult>;
|
|
695
|
+
/**
|
|
696
|
+
* Take work away from its current attempt (B28, ADR 0005).
|
|
697
|
+
*
|
|
698
|
+
* The order matters and is the contract: the capability is invalidated and made
|
|
699
|
+
* durable *before* the session is asked to stop, so a session that keeps running
|
|
700
|
+
* cannot write anything; and `revoked` is only ever reported once quiescence has
|
|
701
|
+
* actually been observed. When it has not, the attempt stays `revoking` and the
|
|
702
|
+
* task goes to the human rather than claiming a stop that did not happen.
|
|
703
|
+
*/
|
|
704
|
+
revokeTask(request: RevokeTaskRequest): Promise<RevokeTaskResult>;
|
|
705
|
+
/** Mark the settled outcome: revoked and re-owned, or escalated to the human. */
|
|
706
|
+
private settleRevocation;
|
|
707
|
+
acknowledgeTask(request: AcknowledgeTaskRequest): Promise<{
|
|
708
|
+
task: TeamTask;
|
|
709
|
+
}>;
|
|
710
|
+
createTask(request: CreateTaskRequest): Promise<CreateTaskResult>;
|
|
711
|
+
private get teamId();
|
|
712
|
+
private documentPath;
|
|
713
|
+
private readOrCreateLocked;
|
|
714
|
+
private readTasksIfPresentLocked;
|
|
715
|
+
private readAttemptsIfPresentLocked;
|
|
716
|
+
private readSubmissionsIfPresentLocked;
|
|
717
|
+
private readReviewsIfPresentLocked;
|
|
718
|
+
private readTeamLocked;
|
|
719
|
+
private validateCreate;
|
|
720
|
+
/**
|
|
721
|
+
* The one place the contract rules live, shared by creation (B19) and editing
|
|
722
|
+
* (B27): a second copy of them is how the writer and the reader start to
|
|
723
|
+
* disagree about what a valid contract is.
|
|
724
|
+
*/
|
|
725
|
+
private validateContract;
|
|
726
|
+
}
|
|
727
|
+
export declare function createTaskStore(options: TaskStoreOptions): TaskStore;
|
|
728
|
+
//#endregion
|
|
729
|
+
export { renderDocument };
|