@nestos/kanban 0.0.0-stage → 0.5.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/README.md +121 -2
- package/dist/activity-C6xDM-cg.d.cts +232 -0
- package/dist/activity-Dw8DOw0L.d.ts +232 -0
- package/dist/activity.cjs +288 -0
- package/dist/activity.cjs.map +1 -0
- package/dist/activity.d.cts +34 -0
- package/dist/activity.d.ts +34 -0
- package/dist/activity.js +288 -0
- package/dist/activity.js.map +1 -0
- package/dist/chunk-2YFPV5GF.cjs +16 -0
- package/dist/chunk-2YFPV5GF.cjs.map +1 -0
- package/dist/chunk-4KF7KUAI.cjs +221 -0
- package/dist/chunk-4KF7KUAI.cjs.map +1 -0
- package/dist/chunk-4XF6LQSU.cjs +9 -0
- package/dist/chunk-4XF6LQSU.cjs.map +1 -0
- package/dist/chunk-5DLZWW5Y.js +148 -0
- package/dist/chunk-5DLZWW5Y.js.map +1 -0
- package/dist/chunk-5XKOCKY2.js +58 -0
- package/dist/chunk-5XKOCKY2.js.map +1 -0
- package/dist/chunk-6MN3H3YF.cjs +183 -0
- package/dist/chunk-6MN3H3YF.cjs.map +1 -0
- package/dist/chunk-ATUFFNLV.js +221 -0
- package/dist/chunk-ATUFFNLV.js.map +1 -0
- package/dist/chunk-DFTOTBGF.js +439 -0
- package/dist/chunk-DFTOTBGF.js.map +1 -0
- package/dist/chunk-G6FCDLDF.js +16 -0
- package/dist/chunk-G6FCDLDF.js.map +1 -0
- package/dist/chunk-IUI5CI2S.cjs +439 -0
- package/dist/chunk-IUI5CI2S.cjs.map +1 -0
- package/dist/chunk-KYNYPKKH.cjs +71 -0
- package/dist/chunk-KYNYPKKH.cjs.map +1 -0
- package/dist/chunk-L44TM4EE.cjs +1498 -0
- package/dist/chunk-L44TM4EE.cjs.map +1 -0
- package/dist/chunk-LSE6JG5I.cjs +20 -0
- package/dist/chunk-LSE6JG5I.cjs.map +1 -0
- package/dist/chunk-O4OGAAV4.js +71 -0
- package/dist/chunk-O4OGAAV4.js.map +1 -0
- package/dist/chunk-OHQTKGQN.js +1498 -0
- package/dist/chunk-OHQTKGQN.js.map +1 -0
- package/dist/chunk-PSKQAFDF.cjs +58 -0
- package/dist/chunk-PSKQAFDF.cjs.map +1 -0
- package/dist/chunk-QTMTBEBA.js +20 -0
- package/dist/chunk-QTMTBEBA.js.map +1 -0
- package/dist/chunk-RQP77ZTY.js +9 -0
- package/dist/chunk-RQP77ZTY.js.map +1 -0
- package/dist/chunk-T7P5EIC4.cjs +148 -0
- package/dist/chunk-T7P5EIC4.cjs.map +1 -0
- package/dist/chunk-TB2RZGDV.js +183 -0
- package/dist/chunk-TB2RZGDV.js.map +1 -0
- package/dist/content-CAIp1njn.d.cts +35 -0
- package/dist/content-CGMJdyH_.d.ts +35 -0
- package/dist/contracts/index.cjs +21 -0
- package/dist/contracts/index.cjs.map +1 -0
- package/dist/contracts/index.d.cts +4 -0
- package/dist/contracts/index.d.ts +4 -0
- package/dist/contracts/index.js +21 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/discovery-DxpirOz3.d.cts +61 -0
- package/dist/discovery-gSqWpuRm.d.ts +61 -0
- package/dist/discovery.cjs +379 -0
- package/dist/discovery.cjs.map +1 -0
- package/dist/discovery.d.cts +22 -0
- package/dist/discovery.d.ts +22 -0
- package/dist/discovery.js +379 -0
- package/dist/discovery.js.map +1 -0
- package/dist/execution.cjs +368 -0
- package/dist/execution.cjs.map +1 -0
- package/dist/execution.d.cts +89 -0
- package/dist/execution.d.ts +89 -0
- package/dist/execution.js +368 -0
- package/dist/execution.js.map +1 -0
- package/dist/index.cjs +76 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/module-BbvwdCDF.d.cts +11 -0
- package/dist/module-CWNK2BMj.d.ts +11 -0
- package/dist/planning-CcFZYdIL.d.cts +844 -0
- package/dist/planning-CcFZYdIL.d.ts +844 -0
- package/dist/ports-Bn6ro2FB.d.ts +142 -0
- package/dist/ports-CaL6geqR.d.cts +142 -0
- package/dist/storage.cjs +289 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +71 -0
- package/dist/storage.d.ts +71 -0
- package/dist/storage.js +289 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-Ct0gOAAg.d.ts +28 -0
- package/dist/store-q63eSMoT.d.cts +28 -0
- package/dist/ui/index.cjs +1961 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +209 -0
- package/dist/ui/index.d.ts +209 -0
- package/dist/ui/index.js +1961 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/kanban.css +194 -0
- package/package.json +113 -4
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
discoveryState,
|
|
3
|
+
identity,
|
|
4
|
+
positive,
|
|
5
|
+
requireValue,
|
|
6
|
+
text
|
|
7
|
+
} from "./chunk-ATUFFNLV.js";
|
|
8
|
+
import {
|
|
9
|
+
KanbanError,
|
|
10
|
+
KanbanScanNotStartedError
|
|
11
|
+
} from "./chunk-G6FCDLDF.js";
|
|
12
|
+
import "./chunk-5XKOCKY2.js";
|
|
13
|
+
import {
|
|
14
|
+
randomUUID
|
|
15
|
+
} from "./chunk-TB2RZGDV.js";
|
|
16
|
+
import "./chunk-QTMTBEBA.js";
|
|
17
|
+
|
|
18
|
+
// src/scan-context.ts
|
|
19
|
+
function scanContext(board, state, sessions, options, runId) {
|
|
20
|
+
requireValue(state.board.revision === board.revision, "SCAN_BOARD_CHANGED");
|
|
21
|
+
const archived = state.archiveIndex ?? [];
|
|
22
|
+
const input = {
|
|
23
|
+
runId,
|
|
24
|
+
executorId: options.executor.identity,
|
|
25
|
+
board: { ...board, items: [] },
|
|
26
|
+
sessions,
|
|
27
|
+
connection: structuredClone(options.policy.connection),
|
|
28
|
+
projectContext: { totalRecords: state.records?.filter((record) => record.status === "confirmed").length ?? 0, omittedRecords: state.records?.filter((record) => record.status === "confirmed").length ?? 0, excludedRecords: state.records?.filter((record) => record.status !== "confirmed").length ?? 0, records: [] },
|
|
29
|
+
context: {
|
|
30
|
+
totalItems: board.items.length,
|
|
31
|
+
omittedItems: board.items.length,
|
|
32
|
+
archivedTotal: archived.length,
|
|
33
|
+
archivedItems: []
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const bytes = () => new TextEncoder().encode(JSON.stringify(input)).length;
|
|
37
|
+
requireValue(bytes() <= options.policy.maxInputBytes, "SCAN_INPUT_LIMIT");
|
|
38
|
+
const sessionIds = new Set(sessions.map((s) => s.session.id));
|
|
39
|
+
addRecords(input, state, sessionIds, bytes, options.policy.maxInputBytes);
|
|
40
|
+
const content = sessions.flatMap((s) => s.messages.map((m) => m.text.toLowerCase())).join("\n");
|
|
41
|
+
const ordered = [...board.items].sort((a, b) => score(a, sessionIds, content) - score(b, sessionIds, content) || (a.planning?.priority ?? 2) - (b.planning?.priority ?? 2) || a.id.localeCompare(b.id));
|
|
42
|
+
for (const item of ordered) {
|
|
43
|
+
if (input.board.items.length >= 50) break;
|
|
44
|
+
input.board.items.push(item);
|
|
45
|
+
input.context.omittedItems--;
|
|
46
|
+
if (bytes() > options.policy.maxInputBytes) {
|
|
47
|
+
input.board.items.pop();
|
|
48
|
+
input.context.omittedItems++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const history = [...archived].sort((a, b) => Number(b.sessionIds.some((id) => sessionIds.has(id))) - Number(a.sessionIds.some((id) => sessionIds.has(id))) || (b.closedAt ?? b.updatedAt) - (a.closedAt ?? a.updatedAt));
|
|
52
|
+
for (const item of history.slice(0, 20)) {
|
|
53
|
+
input.context.archivedItems.push(item);
|
|
54
|
+
if (bytes() > options.policy.maxInputBytes) input.context.archivedItems.pop();
|
|
55
|
+
}
|
|
56
|
+
return structuredClone(input);
|
|
57
|
+
}
|
|
58
|
+
function addRecords(input, state, sessionIds, bytes, maxBytes) {
|
|
59
|
+
const records = (state.records ?? []).filter((record) => record.status === "confirmed").sort((a, b) => Number(b.kind === "decision") - Number(a.kind === "decision") || Number(b.sources.some((source) => sessionIds.has(source.sessionId))) - Number(a.sources.some((source) => sessionIds.has(source.sessionId))) || b.createdAt - a.createdAt);
|
|
60
|
+
for (const record of records.slice(0, 10)) {
|
|
61
|
+
input.projectContext.records.push(record);
|
|
62
|
+
input.projectContext.omittedRecords--;
|
|
63
|
+
if (bytes() > maxBytes) {
|
|
64
|
+
input.projectContext.records.pop();
|
|
65
|
+
input.projectContext.omittedRecords++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function score(item, sessions, content) {
|
|
70
|
+
if (item.sources?.some((source) => sessions.has(source.sessionId)) || content.includes(item.title.toLowerCase())) return 0;
|
|
71
|
+
if (item.status === "claimed" || item.status === "review") return 1;
|
|
72
|
+
if (item.status === "open") return 2;
|
|
73
|
+
return item.status === "completed" || item.status === "cancelled" ? 4 : 3;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/discovery.ts
|
|
77
|
+
function createKanbanDiscovery(options) {
|
|
78
|
+
return new Discovery(options);
|
|
79
|
+
}
|
|
80
|
+
var Discovery = class {
|
|
81
|
+
constructor(options) {
|
|
82
|
+
this.options = options;
|
|
83
|
+
this.now = options.now ?? Date.now;
|
|
84
|
+
this.id = options.id ?? randomUUID;
|
|
85
|
+
this.token = this.id();
|
|
86
|
+
for (const value of [options.policy.idleMs, options.policy.maxSessions, options.policy.maxInputBytes, options.policy.leaseMs]) positive(value);
|
|
87
|
+
identity(options.policy.connection.connectionId);
|
|
88
|
+
}
|
|
89
|
+
now;
|
|
90
|
+
id;
|
|
91
|
+
token;
|
|
92
|
+
active;
|
|
93
|
+
stopping = false;
|
|
94
|
+
resets = /* @__PURE__ */ new Set();
|
|
95
|
+
stage = "prepare";
|
|
96
|
+
scan = () => {
|
|
97
|
+
if (this.stopping) return Promise.reject(new KanbanError("SERVICE_STOPPING"));
|
|
98
|
+
if (this.active) return this.active;
|
|
99
|
+
const operationId = this.id(), started = this.now();
|
|
100
|
+
this.active = this.run().catch(async (error) => {
|
|
101
|
+
if (error instanceof KanbanError && ["SOURCE_CHANGED", "SCAN_BOARD_CHANGED", "REVISION_CONFLICT"].includes(error.code)) {
|
|
102
|
+
await this.settleInvalidatedRun();
|
|
103
|
+
await this.update("invalidate", (state) => {
|
|
104
|
+
if (state.pending?.token === this.token) {
|
|
105
|
+
state.lastRun = { runId: state.pending.runId, outcome: "invalidated", at: this.now(), code: error.code };
|
|
106
|
+
delete state.pending;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}).then(() => this.log(operationId, started, "success"), (error) => {
|
|
112
|
+
this.log(operationId, started, "failure", errorCode(error));
|
|
113
|
+
throw error;
|
|
114
|
+
}).finally(() => {
|
|
115
|
+
this.active = void 0;
|
|
116
|
+
});
|
|
117
|
+
return this.active;
|
|
118
|
+
};
|
|
119
|
+
tick = () => this.scan();
|
|
120
|
+
async quiesce() {
|
|
121
|
+
this.stopping = true;
|
|
122
|
+
await Promise.allSettled([this.active, ...this.resets]);
|
|
123
|
+
}
|
|
124
|
+
dispose = () => this.quiesce();
|
|
125
|
+
reset = (runId) => {
|
|
126
|
+
if (this.stopping) return Promise.reject(new KanbanError("SERVICE_STOPPING"));
|
|
127
|
+
const work = this.resetRun(runId).finally(() => {
|
|
128
|
+
this.resets.delete(work);
|
|
129
|
+
});
|
|
130
|
+
this.resets.add(work);
|
|
131
|
+
return work;
|
|
132
|
+
};
|
|
133
|
+
async resetRun(runId) {
|
|
134
|
+
const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;
|
|
135
|
+
requireValue(pending?.runId === runId, "SCAN_CHANGED");
|
|
136
|
+
await this.options.authorize("commit", pending.input);
|
|
137
|
+
requireValue(pending.token === this.token || pending.leaseUntil <= this.now(), "SCAN_BUSY");
|
|
138
|
+
if (pending.executionStarted && !pending.executionSettled) requireValue(await this.options.executor.settled?.(pending.input), "SCAN_EXECUTION_UNSETTLED");
|
|
139
|
+
await this.update("reset", (state) => {
|
|
140
|
+
requireValue(state.pending?.runId === runId && state.pending.token === pending.token, "SCAN_CHANGED");
|
|
141
|
+
state.lastRun = { runId, outcome: "invalidated", at: this.now(), code: "SCAN_MANUAL_RESET" };
|
|
142
|
+
delete state.pending;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async run() {
|
|
146
|
+
await this.options.authorize("read");
|
|
147
|
+
const board = await this.options.service.read(this.options.boardId);
|
|
148
|
+
requireValue(matchesScope(board.scope, this.options.source), "SOURCE_PROJECT_MISMATCH");
|
|
149
|
+
requireValue(this.options.source.kind === this.options.policy.connection.kind || this.options.policy.connection.allowContentTransfer, "CONTENT_TRANSFER_NOT_ALLOWED");
|
|
150
|
+
const stored = await this.options.store.read(board.id);
|
|
151
|
+
let pending = discoveryState(stored)?.pending;
|
|
152
|
+
if (pending && pending.token !== this.token && pending.leaseUntil > this.now()) return;
|
|
153
|
+
if (!pending) {
|
|
154
|
+
const sessions = await this.collect();
|
|
155
|
+
if (!sessions.length) return;
|
|
156
|
+
const current = await this.options.store.read(board.id);
|
|
157
|
+
requireValue(current, "BOARD_NOT_FOUND");
|
|
158
|
+
const input = scanContext(board, current, sessions, this.options, this.id());
|
|
159
|
+
requireValue(new TextEncoder().encode(JSON.stringify(input)).length <= this.options.policy.maxInputBytes, "SCAN_INPUT_LIMIT");
|
|
160
|
+
pending = { runId: input.runId, autoCreate: this.options.policy.autoCreate && (input.projectContext?.omittedRecords ?? 0) === 0 && input.context.omittedItems === 0 && input.context.archivedItems.length === input.context.archivedTotal, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs, input };
|
|
161
|
+
}
|
|
162
|
+
const selected = pending;
|
|
163
|
+
await this.update("claim", (state) => {
|
|
164
|
+
requireValue(!state.pending || state.pending.token === this.token || state.pending.leaseUntil <= this.now(), "SCAN_BUSY");
|
|
165
|
+
if (state.pending) requireValue(state.pending.runId === selected.runId, "SCAN_CHANGED");
|
|
166
|
+
const restoring = selected.token !== this.token;
|
|
167
|
+
state.pending = { ...selected, ...restoring ? { request: void 0 } : {}, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs };
|
|
168
|
+
});
|
|
169
|
+
await this.process();
|
|
170
|
+
}
|
|
171
|
+
async collect() {
|
|
172
|
+
this.stage = "source.list";
|
|
173
|
+
const sessions = await this.options.source.list(this.options.policy.includeArchived);
|
|
174
|
+
for (const session of sessions) {
|
|
175
|
+
identity(session.id);
|
|
176
|
+
requireValue(typeof session.revision === "string" && session.revision.length > 0 && session.revision.length <= 16e3, "INVALID_SOURCE_REVISION");
|
|
177
|
+
}
|
|
178
|
+
requireValue(new Set(sessions.map((s) => s.id)).size === sessions.length, "DUPLICATE_SOURCE_SESSION");
|
|
179
|
+
await this.observe(sessions);
|
|
180
|
+
const state = discoveryState(await this.options.store.read(this.options.boardId));
|
|
181
|
+
const result = [];
|
|
182
|
+
let bytes = 0;
|
|
183
|
+
for (const session of sessions.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0) || a.id.localeCompare(b.id))) {
|
|
184
|
+
if (!this.eligible(session, state)) continue;
|
|
185
|
+
const current = await this.options.source.inspect(session.id);
|
|
186
|
+
if (!this.eligible(current, state) || current.revision !== session.revision) continue;
|
|
187
|
+
this.stage = "source.read";
|
|
188
|
+
const text2 = await this.readCandidate(current, Math.floor(this.options.policy.maxInputBytes * 0.7) - bytes);
|
|
189
|
+
if (!text2) continue;
|
|
190
|
+
requireValue(text2.session.id === current.id && text2.session.revision === current.revision, "SOURCE_CHANGED");
|
|
191
|
+
if (!text2.complete) continue;
|
|
192
|
+
const size = new TextEncoder().encode(JSON.stringify(text2)).length;
|
|
193
|
+
if (bytes + size > this.options.policy.maxInputBytes * 0.7) break;
|
|
194
|
+
bytes += size;
|
|
195
|
+
result.push(text2);
|
|
196
|
+
if (result.length >= this.options.policy.maxSessions) break;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
async readCandidate(session, maxBytes) {
|
|
201
|
+
const started = this.now();
|
|
202
|
+
try {
|
|
203
|
+
const result = await this.options.source.read(session, maxBytes);
|
|
204
|
+
if (!result.complete) this.log(this.id(), started, "failure", "SOURCE_PROVENANCE_UNKNOWN", session.id);
|
|
205
|
+
return result;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const code = errorCode(error);
|
|
208
|
+
if (!["SCAN_INPUT_LIMIT", "SOURCE_CHANGED", "HARNESS_DIGEST_CHANGED"].includes(code)) throw error;
|
|
209
|
+
this.log(this.id(), started, "failure", code, session.id);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
eligible(session, state) {
|
|
214
|
+
const quiet = state.observations?.[session.id];
|
|
215
|
+
return !session.scannerOwned && (this.options.policy.includeArchived || !session.archived) && session.activity === "idle" && session.updatedAt !== null && session.updatedAt <= this.now() && quiet?.revision === session.revision && this.now() - quiet.idleSince >= this.options.policy.idleMs && state.checkpoints[session.id] !== session.revision;
|
|
216
|
+
}
|
|
217
|
+
async observe(sessions) {
|
|
218
|
+
await this.update("observe", (state) => {
|
|
219
|
+
state.observations ??= {};
|
|
220
|
+
for (const session of sessions) {
|
|
221
|
+
if (session.activity !== "idle" || session.scannerOwned) {
|
|
222
|
+
delete state.observations[session.id];
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const old = state.observations[session.id];
|
|
226
|
+
if (old?.revision !== session.revision) state.observations[session.id] = { revision: session.revision, idleSince: this.now() };
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async process() {
|
|
231
|
+
let pending = discoveryState(await this.options.store.read(this.options.boardId)).pending;
|
|
232
|
+
requireValue(pending.input.executorId === this.options.executor.identity, "SCANNER_EXECUTOR_CHANGED");
|
|
233
|
+
await this.checkSources(pending.input);
|
|
234
|
+
if (!pending.changes) {
|
|
235
|
+
await this.options.authorize("execute", pending.input);
|
|
236
|
+
this.stage = "analyzer.execute";
|
|
237
|
+
await this.updateOwned("execute", (state) => {
|
|
238
|
+
state.pending.executionStarted = true;
|
|
239
|
+
});
|
|
240
|
+
const output = await this.execute(pending.input), result = Array.isArray(output) ? { changes: output } : output;
|
|
241
|
+
await this.updateOwned("settled", (state) => {
|
|
242
|
+
state.pending.executionSettled = true;
|
|
243
|
+
});
|
|
244
|
+
validateChanges(result.changes, pending.input);
|
|
245
|
+
validateRecords(result.records ?? [], pending.input, result.changes);
|
|
246
|
+
await this.updateOwned("result", (state) => {
|
|
247
|
+
state.pending.changes = result.changes;
|
|
248
|
+
state.pending.records = result.records;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
pending = discoveryState(await this.options.store.read(this.options.boardId)).pending;
|
|
252
|
+
await this.options.authorize("commit", pending.input);
|
|
253
|
+
await this.checkSources(pending.input);
|
|
254
|
+
if (!pending.request) {
|
|
255
|
+
const board = await this.options.service.read(this.options.boardId);
|
|
256
|
+
requireValue(board.revision === pending.input.board.revision, "SCAN_BOARD_CHANGED");
|
|
257
|
+
const request = {
|
|
258
|
+
operationId: `discovery:${pending.runId}`,
|
|
259
|
+
expectedRevision: board.revision,
|
|
260
|
+
command: { type: "apply-discovery", batch: {
|
|
261
|
+
runId: pending.runId,
|
|
262
|
+
leaseToken: this.token,
|
|
263
|
+
changes: pending.changes,
|
|
264
|
+
...pending.records ? { records: pending.records } : {},
|
|
265
|
+
autoCreate: pending.autoCreate,
|
|
266
|
+
checkpoints: pending.input.sessions.map((s) => ({ sessionId: s.session.id, revision: s.session.revision }))
|
|
267
|
+
} }
|
|
268
|
+
};
|
|
269
|
+
await this.updateOwned("prepare", (state) => {
|
|
270
|
+
state.pending.request = request;
|
|
271
|
+
});
|
|
272
|
+
pending.request = request;
|
|
273
|
+
}
|
|
274
|
+
this.stage = "board.commit";
|
|
275
|
+
await this.options.service.execute(this.options.boardId, pending.request);
|
|
276
|
+
}
|
|
277
|
+
async execute(input) {
|
|
278
|
+
try {
|
|
279
|
+
return await this.options.executor.run(structuredClone(input));
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error instanceof KanbanScanNotStartedError) await this.updateOwned("not-started", (state) => {
|
|
282
|
+
state.pending.executionSettled = true;
|
|
283
|
+
});
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async settleInvalidatedRun() {
|
|
288
|
+
const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;
|
|
289
|
+
if (!pending || pending.token !== this.token || !pending.executionStarted || pending.executionSettled) return;
|
|
290
|
+
requireValue(await this.options.executor.settled?.(pending.input), "SCAN_EXECUTION_UNSETTLED");
|
|
291
|
+
}
|
|
292
|
+
async checkSources(input) {
|
|
293
|
+
for (const source of input.sessions) {
|
|
294
|
+
const current = await this.options.source.inspect(source.session.id);
|
|
295
|
+
requireValue(current.revision === source.session.revision && current.activity === "idle" && !current.scannerOwned, "SOURCE_CHANGED");
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async updateOwned(stage, mutate) {
|
|
299
|
+
await this.update(stage, (state) => {
|
|
300
|
+
requireValue(state.pending?.token === this.token && state.pending.leaseUntil > this.now(), "SCAN_LEASE_EXPIRED");
|
|
301
|
+
mutate(state);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
async update(stage, mutate) {
|
|
305
|
+
const operation = this.id();
|
|
306
|
+
await this.options.store.update(this.options.boardId, operation, `discovery:${stage}:${operation}`, (state) => {
|
|
307
|
+
requireValue(state, "BOARD_NOT_FOUND");
|
|
308
|
+
state.discovery ??= { checkpoints: {} };
|
|
309
|
+
mutate(state.discovery);
|
|
310
|
+
return state;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
log(operationId, started, result, code, sessionId) {
|
|
314
|
+
try {
|
|
315
|
+
this.options.log({ operationId, boardId: this.options.boardId, sessionId, stage: `discovery.${this.stage}`, result, code, durationMs: this.now() - started });
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
function validateChanges(changes, input) {
|
|
321
|
+
requireValue(Array.isArray(changes) && changes.length <= 50, "SCAN_CHANGE_LIMIT");
|
|
322
|
+
const ids = /* @__PURE__ */ new Set();
|
|
323
|
+
for (const change of changes) {
|
|
324
|
+
requireValue(change && ["create", "update", "link"].includes(change.action) && !ids.has(change.itemId), "INVALID_CHANGE");
|
|
325
|
+
ids.add(change.itemId);
|
|
326
|
+
identity(change.itemId);
|
|
327
|
+
if (change.action !== "create") requireValue(input.board.items.some((i) => i.id === change.itemId), "UNKNOWN_CONTEXT_ITEM");
|
|
328
|
+
text(change.title);
|
|
329
|
+
text(change.responsibility);
|
|
330
|
+
text(change.acceptance);
|
|
331
|
+
requireValue(change.eligibleAgentIds === void 0 || Array.isArray(change.eligibleAgentIds), "INVALID_ELIGIBILITY");
|
|
332
|
+
change.eligibleAgentIds?.forEach(identity);
|
|
333
|
+
requireValue(change.eligiblePrincipals === void 0 || Array.isArray(change.eligiblePrincipals), "INVALID_ELIGIBILITY");
|
|
334
|
+
requireValue(Array.isArray(change.sources) && change.sources.length > 0 && change.sources.length <= 100 && change.sources.some((s) => s.role === "user"), "USER_EVIDENCE_REQUIRED");
|
|
335
|
+
for (const ref of change.sources) {
|
|
336
|
+
const source = input.sessions.find((s) => s.session.id === ref.sessionId && s.session.revision === ref.revision);
|
|
337
|
+
requireValue(source?.messages.some((m) => m.id === ref.messageId && m.role === ref.role), "INVALID_SOURCE_REFERENCE");
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function validateRecords(records, input, changes) {
|
|
342
|
+
requireValue(Array.isArray(records) && records.length <= 20, "SCAN_RECORD_LIMIT");
|
|
343
|
+
const ids = /* @__PURE__ */ new Set();
|
|
344
|
+
for (const record of records) {
|
|
345
|
+
identity(record.id);
|
|
346
|
+
requireValue(!ids.has(record.id), "INVALID_RECORD_ID");
|
|
347
|
+
ids.add(record.id);
|
|
348
|
+
text(record.title);
|
|
349
|
+
text(record.content);
|
|
350
|
+
requireValue(["log", "decision"].includes(record.kind), "INVALID_RECORD_KIND");
|
|
351
|
+
if (record.kind === "decision") text(record.rationale ?? "");
|
|
352
|
+
requireValue(Array.isArray(record.itemIds) && record.itemIds.every((id) => input.board.items.some((item) => item.id === id) || changes.some((change) => change.action === "create" && change.itemId === id)), "UNKNOWN_CONTEXT_ITEM");
|
|
353
|
+
if (record.supersedes) requireValue(input.projectContext?.records.some((old) => old.id === record.supersedes && old.kind === "decision"), "UNKNOWN_CONTEXT_RECORD");
|
|
354
|
+
requireValue(Array.isArray(record.sources) && record.sources.length > 0 && record.sources.length <= 100 && record.sources.some((source) => source.role === "user"), "USER_EVIDENCE_REQUIRED");
|
|
355
|
+
for (const ref of record.sources) {
|
|
356
|
+
const source = input.sessions.find((s) => s.session.id === ref.sessionId && s.session.revision === ref.revision);
|
|
357
|
+
requireValue(source?.messages.some((message) => message.id === ref.messageId && message.role === ref.role), "INVALID_SOURCE_REFERENCE");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function errorCode(error) {
|
|
362
|
+
const value = error;
|
|
363
|
+
const code = value?.remoteCode ?? value?.code;
|
|
364
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : "SCAN_PORT_FAILURE";
|
|
365
|
+
}
|
|
366
|
+
function matchesScope(scope, source) {
|
|
367
|
+
const expected = source.scope ?? (source.projectId ? { kind: "project", projectId: source.projectId } : void 0);
|
|
368
|
+
if (!expected || scope.kind !== expected.kind) return false;
|
|
369
|
+
if (scope.kind === "system") return true;
|
|
370
|
+
if (scope.kind === "project" && expected.kind === "project") return scope.projectId === expected.projectId;
|
|
371
|
+
return scope.kind === "collection" && expected.kind === "collection" && scope.namespace === expected.namespace && scope.id === expected.id;
|
|
372
|
+
}
|
|
373
|
+
export {
|
|
374
|
+
KanbanScanNotStartedError,
|
|
375
|
+
scanContext as buildDiscoveryContext,
|
|
376
|
+
createKanbanDiscovery,
|
|
377
|
+
discoveryState
|
|
378
|
+
};
|
|
379
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scan-context.ts","../src/discovery.ts"],"sourcesContent":["import type {KanbanBoard, KanbanItem, KanbanScanInput, KanbanScanText} from './contracts';\nimport type {KanbanDiscoveryOptions} from './scan-ports';\nimport type {KanbanState} from './ports';\nimport {requireValue} from './rules';\n\n/** A bounded working set, with explicit omitted counts instead of pretending full coverage. */\nexport function scanContext(board: KanbanBoard, state: KanbanState, sessions: KanbanScanText[], options: KanbanDiscoveryOptions, runId: string): KanbanScanInput {\n requireValue(state.board.revision === board.revision, 'SCAN_BOARD_CHANGED');\n const archived = state.archiveIndex ?? [];\n const input: KanbanScanInput = {runId, executorId: options.executor.identity, board: {...board, items: []}, sessions,\n connection: structuredClone(options.policy.connection), projectContext: {totalRecords: state.records?.filter(record => record.status === 'confirmed').length ?? 0, omittedRecords: state.records?.filter(record => record.status === 'confirmed').length ?? 0, excludedRecords: state.records?.filter(record => record.status !== 'confirmed').length ?? 0, records: []}, context: {totalItems: board.items.length, omittedItems: board.items.length,\n archivedTotal: archived.length, archivedItems: []}};\n const bytes = () => new TextEncoder().encode(JSON.stringify(input)).length;\n requireValue(bytes() <= options.policy.maxInputBytes, 'SCAN_INPUT_LIMIT');\n const sessionIds = new Set(sessions.map(s => s.session.id));\n addRecords(input, state, sessionIds, bytes, options.policy.maxInputBytes);\n const content = sessions.flatMap(s => s.messages.map(m => m.text.toLowerCase())).join('\\n');\n const ordered = [...board.items].sort((a, b) => score(a, sessionIds, content) - score(b, sessionIds, content)\n || (a.planning?.priority ?? 2) - (b.planning?.priority ?? 2) || a.id.localeCompare(b.id));\n for (const item of ordered) {\n if (input.board.items.length >= 50) break;\n input.board.items.push(item); input.context!.omittedItems--;\n if (bytes() > options.policy.maxInputBytes) {input.board.items.pop(); input.context!.omittedItems++;}\n }\n const history = [...archived].sort((a, b) => Number(b.sessionIds.some(id => sessionIds.has(id))) - Number(a.sessionIds.some(id => sessionIds.has(id)))\n || (b.closedAt ?? b.updatedAt) - (a.closedAt ?? a.updatedAt));\n for (const item of history.slice(0, 20)) {\n input.context!.archivedItems.push(item);\n if (bytes() > options.policy.maxInputBytes) input.context!.archivedItems.pop();\n }\n return structuredClone(input);\n}\nfunction addRecords(input: KanbanScanInput, state: KanbanState, sessionIds: Set<string>, bytes: () => number, maxBytes: number): void {\n const records = (state.records ?? []).filter(record => record.status === 'confirmed').sort((a, b) => Number(b.kind === 'decision') - Number(a.kind === 'decision')\n || Number(b.sources.some(source => sessionIds.has(source.sessionId))) - Number(a.sources.some(source => sessionIds.has(source.sessionId))) || b.createdAt - a.createdAt);\n for (const record of records.slice(0, 10)) {\n input.projectContext!.records.push(record); input.projectContext!.omittedRecords--;\n if (bytes() > maxBytes) {input.projectContext!.records.pop(); input.projectContext!.omittedRecords++;}\n }\n}\n\nfunction score(item: KanbanItem, sessions: Set<string>, content: string): number {\n if (item.sources?.some(source => sessions.has(source.sessionId)) || content.includes(item.title.toLowerCase())) return 0;\n if (item.status === 'claimed' || item.status === 'review') return 1;\n if (item.status === 'open') return 2;\n return item.status === 'completed' || item.status === 'cancelled' ? 4 : 3;\n}\n","import {discoveryState} from './discovery-state';\nimport {randomUUID} from './utils';\nimport type {KanbanDiscoveryChange, KanbanRequest, KanbanScanInput, KanbanScanSession, KanbanScanText} from './contracts';\nimport {KanbanError, KanbanScanNotStartedError} from './ports';\nimport type {KanbanDiscoveryOptions, KanbanDiscoveryState} from './scan-ports';\nimport {scanContext} from './scan-context';\nimport {identity, positive, requireValue, text as validateText} from './rules';\n\nexport interface KanbanDiscoveryHandle {scan(): Promise<void>; tick(): Promise<void>; reset(runId: string): Promise<void>; quiesce(): Promise<void>; dispose(): Promise<void>}\nexport function createKanbanDiscovery(options: KanbanDiscoveryOptions): KanbanDiscoveryHandle {return new Discovery(options);}\nclass Discovery {\n private readonly now;\n private readonly id;\n private readonly token;\n private active?: Promise<void>;\n private stopping = false;\n private readonly resets = new Set<Promise<void>>();\n private stage = 'prepare';\n constructor(private readonly options: KanbanDiscoveryOptions) {\n this.now = options.now ?? Date.now; this.id = options.id ?? randomUUID; this.token = this.id();\n for (const value of [options.policy.idleMs, options.policy.maxSessions, options.policy.maxInputBytes, options.policy.leaseMs]) positive(value);\n identity(options.policy.connection.connectionId);\n }\n scan = (): Promise<void> => {\n if (this.stopping) return Promise.reject(new KanbanError('SERVICE_STOPPING'));\n if (this.active) return this.active;\n const operationId = this.id(), started = this.now();\n this.active = this.run().catch(async error => {\n if (error instanceof KanbanError && ['SOURCE_CHANGED', 'SCAN_BOARD_CHANGED', 'REVISION_CONFLICT'].includes(error.code)) {\n await this.settleInvalidatedRun();\n await this.update('invalidate', state => {if (state.pending?.token === this.token) {\n state.lastRun = {runId: state.pending.runId, outcome: 'invalidated', at: this.now(), code: error.code}; delete state.pending;\n }});\n }\n throw error;\n }).then(() => this.log(operationId, started, 'success'), error => {\n this.log(operationId, started, 'failure', errorCode(error)); throw error;\n }).finally(() => {this.active = undefined;});\n return this.active;\n };\n tick = (): Promise<void> => this.scan();\n async quiesce(): Promise<void> {\n this.stopping = true;\n await Promise.allSettled([this.active, ...this.resets]);\n }\n dispose = (): Promise<void> => this.quiesce();\n reset = (runId: string): Promise<void> => {\n if (this.stopping) return Promise.reject(new KanbanError('SERVICE_STOPPING'));\n const work = this.resetRun(runId).finally(() => {this.resets.delete(work);});\n this.resets.add(work); return work;\n };\n private async resetRun(runId: string): Promise<void> {\n const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;\n requireValue(pending?.runId === runId, 'SCAN_CHANGED');\n await this.options.authorize('commit', pending.input);\n requireValue(pending.token === this.token || pending.leaseUntil <= this.now(), 'SCAN_BUSY');\n if (pending.executionStarted && !pending.executionSettled) requireValue(await this.options.executor.settled?.(pending.input), 'SCAN_EXECUTION_UNSETTLED');\n await this.update('reset', state => {\n requireValue(state.pending?.runId === runId && state.pending.token === pending.token, 'SCAN_CHANGED');\n state.lastRun = {runId, outcome: 'invalidated', at: this.now(), code: 'SCAN_MANUAL_RESET'}; delete state.pending;\n });\n }\n private async run(): Promise<void> {\n await this.options.authorize('read');\n const board = await this.options.service.read(this.options.boardId);\n requireValue(matchesScope(board.scope, this.options.source), 'SOURCE_PROJECT_MISMATCH');\n requireValue(this.options.source.kind === this.options.policy.connection.kind || this.options.policy.connection.allowContentTransfer, 'CONTENT_TRANSFER_NOT_ALLOWED');\n const stored = await this.options.store.read(board.id);\n let pending = discoveryState(stored)?.pending;\n if (pending && pending.token !== this.token && pending.leaseUntil > this.now()) return;\n if (!pending) {\n const sessions = await this.collect(); if (!sessions.length) return;\n const current = await this.options.store.read(board.id); requireValue(current, 'BOARD_NOT_FOUND');\n const input = scanContext(board, current, sessions, this.options, this.id());\n requireValue(new TextEncoder().encode(JSON.stringify(input)).length <= this.options.policy.maxInputBytes, 'SCAN_INPUT_LIMIT');\n pending = {runId: input.runId, autoCreate: this.options.policy.autoCreate && (input.projectContext?.omittedRecords ?? 0) === 0 && input.context!.omittedItems === 0 && input.context!.archivedItems.length === input.context!.archivedTotal, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs, input};\n }\n const selected = pending;\n await this.update('claim', state => {\n requireValue(!state.pending || state.pending.token === this.token || state.pending.leaseUntil <= this.now(), 'SCAN_BUSY');\n if (state.pending) requireValue(state.pending.runId === selected.runId, 'SCAN_CHANGED');\n const restoring = selected.token !== this.token;\n state.pending = {...selected, ...(restoring ? {request: undefined} : {}), token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs};\n });\n await this.process();\n }\n private async collect(): Promise<KanbanScanText[]> {\n this.stage = 'source.list';\n const sessions = await this.options.source.list(this.options.policy.includeArchived);\n for (const session of sessions) {\n identity(session.id); requireValue(typeof session.revision === 'string' && session.revision.length > 0 && session.revision.length <= 16000, 'INVALID_SOURCE_REVISION');\n }\n requireValue(new Set(sessions.map(s => s.id)).size === sessions.length, 'DUPLICATE_SOURCE_SESSION');\n await this.observe(sessions);\n const state = discoveryState(await this.options.store.read(this.options.boardId))!;\n const result: KanbanScanText[] = []; let bytes = 0;\n for (const session of sessions.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0) || a.id.localeCompare(b.id))) {\n if (!this.eligible(session, state)) continue;\n const current = await this.options.source.inspect(session.id);\n if (!this.eligible(current, state) || current.revision !== session.revision) continue;\n this.stage = 'source.read';\n const text = await this.readCandidate(current, Math.floor(this.options.policy.maxInputBytes * 0.7) - bytes);\n if (!text) continue;\n requireValue(text.session.id === current.id && text.session.revision === current.revision, 'SOURCE_CHANGED');\n if (!text.complete) continue;\n const size = new TextEncoder().encode(JSON.stringify(text)).length;\n if (bytes + size > this.options.policy.maxInputBytes * 0.7) break;\n bytes += size; result.push(text); if (result.length >= this.options.policy.maxSessions) break;\n }\n return result;\n }\n private async readCandidate(session: KanbanScanSession, maxBytes: number): Promise<KanbanScanText | null> {\n const started = this.now();\n try {\n const result = await this.options.source.read(session, maxBytes);\n if (!result.complete) this.log(this.id(), started, 'failure', 'SOURCE_PROVENANCE_UNKNOWN', session.id);\n return result;\n } catch (error) {\n const code = errorCode(error);\n if (!['SCAN_INPUT_LIMIT', 'SOURCE_CHANGED', 'HARNESS_DIGEST_CHANGED'].includes(code)) throw error;\n this.log(this.id(), started, 'failure', code, session.id); return null;\n }\n }\n private eligible(session: KanbanScanSession, state: KanbanDiscoveryState): boolean {\n const quiet = state.observations?.[session.id];\n return !session.scannerOwned && (this.options.policy.includeArchived || !session.archived)\n && session.activity === 'idle' && session.updatedAt !== null && session.updatedAt <= this.now()\n && quiet?.revision === session.revision && this.now() - quiet.idleSince >= this.options.policy.idleMs\n && state.checkpoints[session.id] !== session.revision;\n }\n private async observe(sessions: KanbanScanSession[]): Promise<void> {\n await this.update('observe', state => {\n state.observations ??= {};\n for (const session of sessions) {\n if (session.activity !== 'idle' || session.scannerOwned) {delete state.observations[session.id]; continue;}\n const old = state.observations[session.id];\n if (old?.revision !== session.revision) state.observations[session.id] = {revision: session.revision, idleSince: this.now()};\n }\n });\n }\n private async process(): Promise<void> {\n let pending = discoveryState(await this.options.store.read(this.options.boardId))!.pending!;\n requireValue(pending.input.executorId === this.options.executor.identity, 'SCANNER_EXECUTOR_CHANGED');\n await this.checkSources(pending.input);\n if (!pending.changes) {\n await this.options.authorize('execute', pending.input);\n this.stage = 'analyzer.execute';\n await this.updateOwned('execute', state => {state.pending!.executionStarted = true;});\n const output = await this.execute(pending.input), result = Array.isArray(output) ? {changes: output} : output;\n await this.updateOwned('settled', state => {state.pending!.executionSettled = true;});\n validateChanges(result.changes, pending.input); validateRecords(result.records ?? [], pending.input, result.changes);\n await this.updateOwned('result', state => {state.pending!.changes = result.changes; state.pending!.records = result.records;});\n }\n pending = discoveryState(await this.options.store.read(this.options.boardId))!.pending!;\n await this.options.authorize('commit', pending.input); await this.checkSources(pending.input);\n if (!pending.request) {\n const board = await this.options.service.read(this.options.boardId);\n requireValue(board.revision === pending.input.board.revision, 'SCAN_BOARD_CHANGED');\n const request: KanbanRequest = {operationId: `discovery:${pending.runId}`, expectedRevision: board.revision,\n command: {type: 'apply-discovery', batch: {runId: pending.runId, leaseToken: this.token, changes: pending.changes!, ...(pending.records ? {records: pending.records} : {}), autoCreate: pending.autoCreate,\n checkpoints: pending.input.sessions.map(s => ({sessionId: s.session.id, revision: s.session.revision}))}}};\n await this.updateOwned('prepare', state => {state.pending!.request = request;});\n pending.request = request;\n }\n this.stage = 'board.commit';\n await this.options.service.execute(this.options.boardId, pending.request);\n }\n private async execute(input: KanbanScanInput): Promise<KanbanDiscoveryChange[] | import('./contracts').KanbanDiscoveryResult> {\n try {return await this.options.executor.run(structuredClone(input));}\n catch (error) {\n if (error instanceof KanbanScanNotStartedError) await this.updateOwned('not-started', state => {state.pending!.executionSettled = true;});\n throw error;\n }\n }\n private async settleInvalidatedRun(): Promise<void> {\n const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;\n if (!pending || pending.token !== this.token || !pending.executionStarted || pending.executionSettled) return;\n requireValue(await this.options.executor.settled?.(pending.input), 'SCAN_EXECUTION_UNSETTLED');\n }\n private async checkSources(input: KanbanScanInput): Promise<void> {\n for (const source of input.sessions) {\n const current = await this.options.source.inspect(source.session.id);\n requireValue(current.revision === source.session.revision && current.activity === 'idle' && !current.scannerOwned, 'SOURCE_CHANGED');\n }\n }\n private async updateOwned(stage: string, mutate: (state: KanbanDiscoveryState) => void): Promise<void> {\n await this.update(stage, state => {\n requireValue(state.pending?.token === this.token && state.pending.leaseUntil > this.now(), 'SCAN_LEASE_EXPIRED'); mutate(state);\n });\n }\n private async update(stage: string, mutate: (state: KanbanDiscoveryState) => void): Promise<void> {\n const operation = this.id();\n await this.options.store.update(this.options.boardId, operation, `discovery:${stage}:${operation}`, state => {\n requireValue(state, 'BOARD_NOT_FOUND'); state.discovery ??= {checkpoints: {}}; mutate(state.discovery as KanbanDiscoveryState); return state;\n });\n }\n private log(operationId: string, started: number, result: 'success' | 'failure', code?: string, sessionId?: string): void {\n try {this.options.log({operationId, boardId: this.options.boardId, sessionId, stage: `discovery.${this.stage}`, result, code, durationMs: this.now() - started});}\n catch { /* Diagnostics cannot affect durable execution. */ }\n }\n}\nfunction validateChanges(changes: KanbanDiscoveryChange[], input: KanbanScanInput): void {\n requireValue(Array.isArray(changes) && changes.length <= 50, 'SCAN_CHANGE_LIMIT');\n const ids = new Set<string>();\n for (const change of changes) {\n requireValue(change && ['create', 'update', 'link'].includes(change.action) && !ids.has(change.itemId), 'INVALID_CHANGE'); ids.add(change.itemId); identity(change.itemId);\n if (change.action !== 'create') requireValue(input.board.items.some(i => i.id === change.itemId), 'UNKNOWN_CONTEXT_ITEM');\n validateText(change.title); validateText(change.responsibility); validateText(change.acceptance);\n requireValue(change.eligibleAgentIds === undefined || Array.isArray(change.eligibleAgentIds), 'INVALID_ELIGIBILITY');\n change.eligibleAgentIds?.forEach(identity);\n requireValue(change.eligiblePrincipals === undefined || Array.isArray(change.eligiblePrincipals), 'INVALID_ELIGIBILITY');\n requireValue(Array.isArray(change.sources) && change.sources.length > 0 && change.sources.length <= 100 && change.sources.some(s => s.role === 'user'), 'USER_EVIDENCE_REQUIRED');\n for (const ref of change.sources) {\n const source = input.sessions.find(s => s.session.id === ref.sessionId && s.session.revision === ref.revision);\n requireValue(source?.messages.some(m => m.id === ref.messageId && m.role === ref.role), 'INVALID_SOURCE_REFERENCE');\n }\n }\n}\n\nfunction validateRecords(records: import('./contracts').KanbanRecordInput[], input: KanbanScanInput, changes: KanbanDiscoveryChange[]): void {\n requireValue(Array.isArray(records) && records.length <= 20, 'SCAN_RECORD_LIMIT');\n const ids = new Set<string>();\n for (const record of records) {\n identity(record.id); requireValue(!ids.has(record.id), 'INVALID_RECORD_ID'); ids.add(record.id);\n validateText(record.title); validateText(record.content);\n requireValue(['log', 'decision'].includes(record.kind), 'INVALID_RECORD_KIND');\n if (record.kind === 'decision') validateText(record.rationale ?? '');\n requireValue(Array.isArray(record.itemIds) && record.itemIds.every(id => input.board.items.some(item => item.id === id) || changes.some(change => change.action === 'create' && change.itemId === id)), 'UNKNOWN_CONTEXT_ITEM');\n if (record.supersedes) requireValue(input.projectContext?.records.some(old => old.id === record.supersedes && old.kind === 'decision'), 'UNKNOWN_CONTEXT_RECORD');\n requireValue(Array.isArray(record.sources) && record.sources.length > 0 && record.sources.length <= 100\n && record.sources.some(source => source.role === 'user'), 'USER_EVIDENCE_REQUIRED');\n for (const ref of record.sources) {\n const source = input.sessions.find(s => s.session.id === ref.sessionId && s.session.revision === ref.revision);\n requireValue(source?.messages.some(message => message.id === ref.messageId && message.role === ref.role), 'INVALID_SOURCE_REFERENCE');\n }\n }\n}\n\nfunction errorCode(error: unknown): string {\n const value = error as {code?: unknown; remoteCode?: unknown};\n const code = value?.remoteCode ?? value?.code;\n return typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : 'SCAN_PORT_FAILURE';\n}\n\nfunction matchesScope(scope: import('./contracts').KanbanBoard['scope'], source: import('./scan-ports').KanbanScanSource): boolean {\n const expected = source.scope ?? (source.projectId ? {kind: 'project' as const, projectId: source.projectId} : undefined);\n if (!expected || scope.kind !== expected.kind) return false;\n if (scope.kind === 'system') return true;\n if (scope.kind === 'project' && expected.kind === 'project') return scope.projectId === expected.projectId;\n return scope.kind === 'collection' && expected.kind === 'collection' && scope.namespace === expected.namespace && scope.id === expected.id;\n}\n"],"mappings":"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","names":["text"]}
|