wabachi 0.2.0 → 0.3.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 (55) hide show
  1. package/.codex-plugin/plugin.json +6 -0
  2. package/LICENSE +1 -1
  3. package/README.md +17 -21
  4. package/dist/architecture/cli.js +51 -36
  5. package/dist/architecture/cli.js.map +1 -1
  6. package/dist/architecture/documentation/react-flow.d.ts +23 -0
  7. package/dist/architecture/documentation/react-flow.js +436 -0
  8. package/dist/architecture/documentation/react-flow.js.map +1 -0
  9. package/dist/architecture/documentation/site.d.ts +10 -13
  10. package/dist/architecture/documentation/site.js +58 -24
  11. package/dist/architecture/documentation/site.js.map +1 -1
  12. package/dist/architecture/projection/react-flow.d.ts +96 -0
  13. package/dist/architecture/projection/react-flow.js +388 -0
  14. package/dist/architecture/projection/react-flow.js.map +1 -0
  15. package/dist/cli.js +53 -14
  16. package/dist/cli.js.map +1 -1
  17. package/dist/command-contract.d.ts +78 -0
  18. package/dist/command-contract.js +194 -0
  19. package/dist/command-contract.js.map +1 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/runtime/repository.d.ts +3 -0
  24. package/dist/runtime/repository.js +140 -16
  25. package/dist/runtime/repository.js.map +1 -1
  26. package/dist/runtime/run.js +3 -1
  27. package/dist/runtime/run.js.map +1 -1
  28. package/dist/skill.d.ts +47 -0
  29. package/dist/skill.js +160 -0
  30. package/dist/skill.js.map +1 -0
  31. package/dist/working-set/codec.d.ts +7 -0
  32. package/dist/working-set/codec.js +53 -0
  33. package/dist/working-set/codec.js.map +1 -0
  34. package/dist/working-set/conflicts.d.ts +46 -0
  35. package/dist/working-set/conflicts.js +97 -0
  36. package/dist/working-set/conflicts.js.map +1 -0
  37. package/dist/working-set/derive.d.ts +34 -0
  38. package/dist/working-set/derive.js +634 -0
  39. package/dist/working-set/derive.js.map +1 -0
  40. package/dist/working-set/index.d.ts +3 -0
  41. package/dist/working-set/index.js +3 -0
  42. package/dist/working-set/index.js.map +1 -0
  43. package/dist/working-set/model.d.ts +93 -0
  44. package/dist/working-set/model.js +204 -0
  45. package/dist/working-set/model.js.map +1 -0
  46. package/dist/working-set/quality.d.ts +201 -0
  47. package/dist/working-set/quality.js +447 -0
  48. package/dist/working-set/quality.js.map +1 -0
  49. package/dist/working-set/seeds.d.ts +135 -0
  50. package/dist/working-set/seeds.js +433 -0
  51. package/dist/working-set/seeds.js.map +1 -0
  52. package/docs/USAGE.md +118 -0
  53. package/docs/examples/minimal-canon.json +24 -0
  54. package/package.json +14 -4
  55. package/skills/wabachi/SKILL.md +39 -0
@@ -0,0 +1,634 @@
1
+ import { compareFactSets, } from "../runtime/facts.js";
2
+ import { CANDIDATE_WORKING_SET_LIMITS, createCandidateWorkingSet, } from "./model.js";
3
+ import { authorizationEntries, conflictToWorkingSetEntry, createWorkingSetConflict, workingSetTargetKey, } from "./conflicts.js";
4
+ /** Canon relationship classes admitted by the V1 derivation boundary. */
5
+ export const CANON_V1_RELATIONSHIP_CLASSES = ["depends-on", "calls", "uses", "data", "control"];
6
+ /** Provider predicates admitted by the V1 derivation boundary. */
7
+ export const PROVIDER_V1_RELATIONSHIP_CLASSES = ["depends-on", "imports", "calls", "references"];
8
+ const CANON_STATE = Object.freeze({
9
+ "depends-on": "required",
10
+ calls: "supporting",
11
+ uses: "supporting",
12
+ data: "supporting",
13
+ control: "supporting",
14
+ });
15
+ const PROVIDER_STATE = Object.freeze({
16
+ "depends-on": "supporting",
17
+ imports: "supporting",
18
+ calls: "supporting",
19
+ references: "supporting",
20
+ });
21
+ const STATE_PRIORITY = Object.freeze({
22
+ unresolved: 0,
23
+ verification: 1,
24
+ supporting: 2,
25
+ required: 3,
26
+ });
27
+ function compareText(left, right) {
28
+ if (left < right)
29
+ return -1;
30
+ if (left > right)
31
+ return 1;
32
+ return 0;
33
+ }
34
+ function stableId(...parts) {
35
+ return parts.join(":").slice(0, 240);
36
+ }
37
+ function stableLocator(...parts) {
38
+ return parts.join(":").slice(0, 1024);
39
+ }
40
+ function evidenceKey(reference) {
41
+ return `${reference.artifact}\u0000${reference.reference}`;
42
+ }
43
+ function targetKey(target) {
44
+ return `${target.kind}\u0000${target.locator}`;
45
+ }
46
+ function seedKey(seed) {
47
+ if (seed.kind === "path")
48
+ return `path:${seed.path}`;
49
+ if (seed.kind === "symbol-export") {
50
+ return `symbol-export:${seed.path}#${seed.symbol}${seed.exportName === undefined ? "" : `@${seed.exportName}`}`;
51
+ }
52
+ return `architecture-component:${seed.componentId}`;
53
+ }
54
+ function symbolLocator(target) {
55
+ if (target.kind !== "symbol")
56
+ return undefined;
57
+ const hash = target.locator.indexOf("#");
58
+ if (hash < 1 || hash === target.locator.length - 1)
59
+ return undefined;
60
+ const path = target.locator.slice(0, hash);
61
+ const symbolPart = target.locator.slice(hash + 1);
62
+ const at = symbolPart.indexOf("@");
63
+ return at < 1
64
+ ? { path, symbol: symbolPart }
65
+ : { path, symbol: symbolPart.slice(0, at), exportName: symbolPart.slice(at + 1) };
66
+ }
67
+ function symbolTarget(path, symbol) {
68
+ return { kind: "symbol", locator: `${path}#${symbol}` };
69
+ }
70
+ function testTarget(path, selector) {
71
+ return { kind: "test", locator: `${path}#${selector}` };
72
+ }
73
+ function mappingTestTarget(path, selector) {
74
+ return testTarget(path, selector);
75
+ }
76
+ function mappingEvidence(mapping, kind, locator) {
77
+ return { artifact: "canon", reference: `${mapping.canonId}:${kind}:${locator}` };
78
+ }
79
+ function relationshipEvidence(source, kind, target, interfaceId) {
80
+ return {
81
+ artifact: "canon",
82
+ reference: `${source}->${target}:${kind}${interfaceId === undefined ? "" : `:${interfaceId}`}`,
83
+ };
84
+ }
85
+ function providerFactEvidence(fact) {
86
+ return { artifact: "provider-fact", reference: fact.factId };
87
+ }
88
+ function canonicalEntityEvidence(entity) {
89
+ return { artifact: "provider-correlation", reference: entity.canonicalId };
90
+ }
91
+ function isProviderRelationship(value) {
92
+ return PROVIDER_V1_RELATIONSHIP_CLASSES.includes(value);
93
+ }
94
+ function isCanonRelationship(value) {
95
+ return CANON_V1_RELATIONSHIP_CLASSES.includes(value);
96
+ }
97
+ function createMappingIndex(mappings) {
98
+ const grouped = new Map();
99
+ for (const mapping of mappings) {
100
+ const values = grouped.get(mapping.canonId) ?? [];
101
+ values.push(mapping);
102
+ grouped.set(mapping.canonId, values);
103
+ }
104
+ for (const values of grouped.values())
105
+ values.sort((left, right) => compareText(JSON.stringify(left), JSON.stringify(right)));
106
+ return { byId: grouped, mappings };
107
+ }
108
+ function createEntityIndex(entities, revision) {
109
+ const currentEntities = revision === undefined
110
+ ? entities
111
+ : entities.filter((entity) => entity.repository.commitSha.toLowerCase() === revision.toLowerCase());
112
+ const byId = new Map();
113
+ for (const entity of currentEntities)
114
+ byId.set(entity.canonicalId, entity);
115
+ return { byId, entities: currentEntities };
116
+ }
117
+ function pathMatches(path, candidate) {
118
+ return path.scope === "file"
119
+ ? path.path === candidate
120
+ : candidate === path.path || candidate.startsWith(`${path.path}/`);
121
+ }
122
+ function componentIdsForTarget(index, target) {
123
+ const components = new Set();
124
+ const symbol = symbolLocator(target);
125
+ for (const mapping of index.mappings) {
126
+ if (target.kind === "file" && mapping.paths.some((path) => pathMatches(path, target.locator))) {
127
+ components.add(mapping.canonId);
128
+ }
129
+ if (target.kind === "test") {
130
+ if (mapping.tests.some((test) => mappingTestTarget(test.path, test.selector).locator === target.locator)) {
131
+ components.add(mapping.canonId);
132
+ }
133
+ }
134
+ if (symbol !== undefined) {
135
+ if (mapping.symbols.some((candidate) => candidate.path === symbol.path &&
136
+ candidate.symbol === symbol.symbol &&
137
+ (candidate.exportName ?? "") === (symbol.exportName ?? "")) ||
138
+ mapping.paths.some((path) => pathMatches(path, symbol.path))) {
139
+ components.add(mapping.canonId);
140
+ }
141
+ }
142
+ }
143
+ return [...components].sort(compareText);
144
+ }
145
+ function memberMatchesSymbol(member, symbol) {
146
+ const values = [member.nativeId, member.name, member.qualifiedName, ...member.aliases].filter((value) => value !== undefined);
147
+ return member.path === symbol.path && values.includes(symbol.symbol);
148
+ }
149
+ function entityMatchesTarget(entity, target) {
150
+ const symbol = symbolLocator(target);
151
+ return entity.members.some((member) => {
152
+ if (target.kind === "file")
153
+ return member.path === target.locator;
154
+ if (symbol !== undefined)
155
+ return memberMatchesSymbol(member, symbol);
156
+ return false;
157
+ });
158
+ }
159
+ function entityTargetMembers(entity) {
160
+ const targets = entity.members
161
+ .filter((member) => member.path !== undefined)
162
+ .map((member) => symbolTarget(member.path, member.nativeId));
163
+ const unique = new Map();
164
+ for (const target of targets)
165
+ unique.set(targetKey(target), target);
166
+ return [...unique.values()].sort((left, right) => compareText(targetKey(left), targetKey(right)));
167
+ }
168
+ function boundaryMembership(canon) {
169
+ const memberships = new Map();
170
+ for (const boundary of canon.boundaries) {
171
+ for (const memberId of boundary.memberIds) {
172
+ const values = memberships.get(memberId) ?? [];
173
+ values.push(boundary.id);
174
+ memberships.set(memberId, values);
175
+ }
176
+ }
177
+ for (const values of memberships.values())
178
+ values.sort(compareText);
179
+ return memberships;
180
+ }
181
+ function sameDeclaredBoundary(memberships, source, target) {
182
+ const sourceBoundaries = memberships.get(source) ?? [];
183
+ const targetBoundaries = memberships.get(target) ?? [];
184
+ if (sourceBoundaries.length === 0 || sourceBoundaries.length !== targetBoundaries.length)
185
+ return false;
186
+ return sourceBoundaries.every((boundaryId, index) => boundaryId === targetBoundaries[index]);
187
+ }
188
+ function addEvidence(target, references) {
189
+ const values = new Map();
190
+ for (const reference of [...target.evidence, ...references])
191
+ values.set(evidenceKey(reference), reference);
192
+ target.evidence = [...values.values()]
193
+ .sort((left, right) => compareText(left.artifact, right.artifact) || compareText(left.reference, right.reference))
194
+ .slice(0, CANDIDATE_WORKING_SET_LIMITS.maxEvidenceReferencesPerEntry);
195
+ }
196
+ function addEntry(entries, state, target, reason, evidence) {
197
+ const key = targetKey(target);
198
+ const existing = entries.get(key);
199
+ if (existing === undefined) {
200
+ entries.set(key, { state, target, reason, evidence: [...evidence] });
201
+ return;
202
+ }
203
+ if (STATE_PRIORITY[state] > STATE_PRIORITY[existing.state] ||
204
+ (STATE_PRIORITY[state] === STATE_PRIORITY[existing.state] &&
205
+ (compareText(reason.id, existing.reason.id) < 0 ||
206
+ (reason.id === existing.reason.id && compareText(reason.summary, existing.reason.summary) < 0)))) {
207
+ existing.state = state;
208
+ existing.reason = reason;
209
+ }
210
+ addEvidence(existing, evidence);
211
+ }
212
+ function addConflict(entries, kind, locator, evidence) {
213
+ const conflict = createWorkingSetConflict({ kind, locator, evidence });
214
+ const entry = conflictToWorkingSetEntry(conflict);
215
+ addEntry(entries, entry.state, entry.target, entry.reason, entry.evidence);
216
+ }
217
+ function unresolvedProviderTarget(subject, predicate) {
218
+ return { kind: "unresolved", locator: stableLocator("provider", predicate, subject) };
219
+ }
220
+ function seedEvidence(resolution) {
221
+ return resolution.evidence;
222
+ }
223
+ function seedReason(seed) {
224
+ return { id: stableId("seed", seed.kind), summary: "validated working-set seed" };
225
+ }
226
+ function canonReason(kind, source, target) {
227
+ return {
228
+ id: stableId("canon", kind, source, target),
229
+ summary: `Architecture Canon ${kind} relationship`,
230
+ };
231
+ }
232
+ function providerReason(predicate, fact) {
233
+ return { id: stableId("provider", predicate, fact.factId), summary: "admitted provider relationship evidence" };
234
+ }
235
+ function isFactEntity(value) {
236
+ return "nativeId" in value && "provider" in value;
237
+ }
238
+ function isPinnedRevision(fact, revision) {
239
+ return fact.repository.commitSha.toLowerCase() === revision;
240
+ }
241
+ function factSubjectKey(fact) {
242
+ return fact.subject.canonicalId;
243
+ }
244
+ function comparisonIndex(providerEvidence, revision) {
245
+ const facts = providerEvidence.facts.filter((fact) => isPinnedRevision(fact, revision));
246
+ const unsupported = providerEvidence.unsupported.filter((evidence) => evidence.repository.commitSha.toLowerCase() === revision.toLowerCase());
247
+ const comparisons = compareFactSets(facts, { unsupported });
248
+ const byFactId = new Map();
249
+ for (const comparison of comparisons) {
250
+ for (const fact of comparison.facts)
251
+ byFactId.set(fact.factId, comparison);
252
+ }
253
+ return { facts, byFactId, comparisons };
254
+ }
255
+ function activeEntitiesForSeeds(resolutions, entityIndex) {
256
+ const active = new Set();
257
+ for (const resolution of resolutions) {
258
+ if (resolution.status !== "resolved")
259
+ continue;
260
+ for (const evidence of resolution.evidence) {
261
+ if (evidence.artifact !== "provider-correlation")
262
+ continue;
263
+ const entity = entityIndex.byId.get(evidence.reference);
264
+ if (entity !== undefined && (entity.status === "ambiguous" || entity.candidateCanonicalIds.length > 0))
265
+ continue;
266
+ active.add(evidence.reference);
267
+ }
268
+ for (const target of resolution.targets) {
269
+ for (const entity of entityIndex.entities) {
270
+ if (entityMatchesTarget(entity, target) &&
271
+ entity.status !== "ambiguous" &&
272
+ entity.candidateCanonicalIds.length === 0) {
273
+ active.add(entity.canonicalId);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ return active;
279
+ }
280
+ function addUnresolvedSeed(entries, resolution) {
281
+ const evidence = [
282
+ ...resolution.evidence,
283
+ { artifact: "working-set-seeds", reference: seedKey(resolution.seed) },
284
+ { artifact: "working-set-seeds", reference: `reason:${resolution.reason}` },
285
+ ...resolution.candidates.map((candidate) => ({ artifact: "working-set-seed-candidate", reference: candidate })),
286
+ ];
287
+ const kind = resolution.reason === "ambiguous"
288
+ ? "ambiguity"
289
+ : resolution.reason === "repository-mismatch"
290
+ ? "stale-evidence"
291
+ : "mapping-gap";
292
+ addConflict(entries, kind, stableLocator("seed", seedKey(resolution.seed)), evidence);
293
+ }
294
+ function addMappingTargets(entries, mapping, state, reason, evidence) {
295
+ for (const path of mapping.paths) {
296
+ addEntry(entries, state, { kind: "file", locator: path.path }, reason, [
297
+ ...evidence,
298
+ mappingEvidence(mapping, "path", path.path),
299
+ ]);
300
+ }
301
+ for (const symbol of mapping.symbols) {
302
+ const locator = `${symbol.path}#${symbol.symbol}${symbol.exportName === undefined ? "" : `@${symbol.exportName}`}`;
303
+ addEntry(entries, state, { kind: "symbol", locator }, reason, [
304
+ ...evidence,
305
+ mappingEvidence(mapping, "symbol", locator),
306
+ ]);
307
+ }
308
+ for (const test of mapping.tests) {
309
+ const target = mappingTestTarget(test.path, test.selector);
310
+ addEntry(entries, "verification", target, {
311
+ id: stableId("verification", mapping.canonId, target.locator),
312
+ summary: "Canon-mapped verification context",
313
+ }, [...evidence, mappingEvidence(mapping, "test", target.locator)]);
314
+ }
315
+ }
316
+ function addComponentMapping(entries, mappingIndex, componentId, state, reason, evidence) {
317
+ const mappings = mappingIndex.byId.get(componentId) ?? [];
318
+ if (mappings.length !== 1)
319
+ return false;
320
+ addMappingTargets(entries, mappings[0], state, reason, evidence);
321
+ return mappings[0].paths.length > 0 || mappings[0].symbols.length > 0 || mappings[0].tests.length > 0;
322
+ }
323
+ function componentIdsForEntity(index, entity) {
324
+ const components = new Set();
325
+ for (const member of entity.members) {
326
+ if (member.path === undefined)
327
+ continue;
328
+ for (const componentId of componentIdsForTarget(index, { kind: "file", locator: member.path })) {
329
+ components.add(componentId);
330
+ }
331
+ for (const componentId of componentIdsForTarget(index, symbolTarget(member.path, member.nativeId))) {
332
+ components.add(componentId);
333
+ }
334
+ }
335
+ return [...components].sort(compareText);
336
+ }
337
+ function providerEntityIsUnresolved(entity, reference) {
338
+ return (reference.correlationStatus === "ambiguous" ||
339
+ reference.candidateCanonicalIds.length > 0 ||
340
+ entity.status === "ambiguous" ||
341
+ entity.candidateCanonicalIds.length > 0);
342
+ }
343
+ function providerSourceIsActive(reference, activeEntities) {
344
+ if (reference.canonicalId !== undefined && activeEntities.has(reference.canonicalId))
345
+ return true;
346
+ return reference.candidateCanonicalIds.some((candidate) => activeEntities.has(candidate));
347
+ }
348
+ function addProviderUnresolved(entries, fact, kind, evidence) {
349
+ const subject = fact.subject.canonicalId ?? fact.subject.nativeId;
350
+ addConflict(entries, kind, unresolvedProviderTarget(subject, fact.predicate).locator, evidence);
351
+ }
352
+ function expandCanon(entries, canon, mappingIndex, resolutions) {
353
+ const activeComponents = new Set();
354
+ const queued = new Set();
355
+ const queue = [];
356
+ const memberships = boundaryMembership(canon);
357
+ for (const resolution of resolutions) {
358
+ if (resolution.status !== "resolved")
359
+ continue;
360
+ for (const target of resolution.targets) {
361
+ const componentIds = componentIdsForTarget(mappingIndex, target);
362
+ if (componentIds.length > 1) {
363
+ addConflict(entries, "ambiguity", stableLocator("canon-mapping", target.locator), resolution.evidence);
364
+ continue;
365
+ }
366
+ for (const componentId of componentIds) {
367
+ if (!activeComponents.has(componentId))
368
+ activeComponents.add(componentId);
369
+ if (!queued.has(componentId)) {
370
+ queued.add(componentId);
371
+ queue.push(componentId);
372
+ }
373
+ }
374
+ }
375
+ if (resolution.seed.kind === "architecture-component") {
376
+ const componentId = resolution.seed.componentId;
377
+ const mappings = mappingIndex.byId.get(componentId) ?? [];
378
+ if (mappings.length === 1) {
379
+ if (mappings[0].paths.length === 0 && mappings[0].symbols.length === 0 && mappings[0].tests.length === 0) {
380
+ addConflict(entries, "mapping-gap", stableLocator("canon-component", componentId), resolution.evidence);
381
+ continue;
382
+ }
383
+ activeComponents.add(componentId);
384
+ if (!queued.has(componentId)) {
385
+ queued.add(componentId);
386
+ queue.push(componentId);
387
+ }
388
+ }
389
+ }
390
+ }
391
+ while (queue.length > 0) {
392
+ const source = queue.shift();
393
+ const relationships = canon.relationships
394
+ .filter((relationship) => relationship.source === source && isCanonRelationship(relationship.kind))
395
+ .sort((left, right) => compareText(left.target, right.target) ||
396
+ compareText(left.kind, right.kind) ||
397
+ compareText(left.interfaceId ?? "", right.interfaceId ?? ""));
398
+ for (const relationship of relationships) {
399
+ const kind = relationship.kind;
400
+ const relationEvidence = relationshipEvidence(source, kind, relationship.target, relationship.interfaceId);
401
+ const reason = canonReason(kind, source, relationship.target);
402
+ const mappings = mappingIndex.byId.get(relationship.target) ?? [];
403
+ if (mappings.length === 0) {
404
+ addConflict(entries, "mapping-gap", stableLocator("canon-component", relationship.target), [relationEvidence]);
405
+ continue;
406
+ }
407
+ if (mappings.length > 1) {
408
+ addConflict(entries, "ambiguity", stableLocator("canon-component", relationship.target), [relationEvidence]);
409
+ continue;
410
+ }
411
+ const mapped = addComponentMapping(entries, mappingIndex, relationship.target, CANON_STATE[kind], reason, [
412
+ relationEvidence,
413
+ ]);
414
+ if (!mapped) {
415
+ addConflict(entries, "mapping-gap", stableLocator("canon-component", relationship.target), [relationEvidence]);
416
+ continue;
417
+ }
418
+ // Only Canon depends-on is a recursive V1 traversal class. The other
419
+ // classes provide bounded context but never become a graph walk.
420
+ if (kind === "depends-on" &&
421
+ sameDeclaredBoundary(memberships, source, relationship.target) &&
422
+ !queued.has(relationship.target)) {
423
+ queued.add(relationship.target);
424
+ activeComponents.add(relationship.target);
425
+ queue.push(relationship.target);
426
+ }
427
+ }
428
+ }
429
+ return activeComponents;
430
+ }
431
+ function deriveProviderEvidence(entries, providerEvidence, entityIndex, staleEntityIds, mappingIndex, activeEntities, activeComponents, revision) {
432
+ const { facts, byFactId } = comparisonIndex(providerEvidence, revision);
433
+ for (const entity of entityIndex.entities) {
434
+ if (componentIdsForEntity(mappingIndex, entity).some((componentId) => activeComponents.has(componentId))) {
435
+ activeEntities.add(entity.canonicalId);
436
+ }
437
+ }
438
+ const processedDisagreements = new Set();
439
+ for (const fact of facts) {
440
+ if (!isProviderRelationship(fact.predicate))
441
+ continue;
442
+ const staleObject = isFactEntity(fact.object) && fact.object.canonicalId !== undefined
443
+ ? staleEntityIds.has(fact.object.canonicalId)
444
+ : false;
445
+ if ((fact.subject.canonicalId !== undefined && staleEntityIds.has(fact.subject.canonicalId)) || staleObject) {
446
+ addConflict(entries, "stale-evidence", stableLocator("provider-fact", fact.factId), [providerFactEvidence(fact)]);
447
+ continue;
448
+ }
449
+ const subjectKey = factSubjectKey(fact);
450
+ if (!providerSourceIsActive(fact.subject, activeEntities))
451
+ continue;
452
+ const comparison = byFactId.get(fact.factId);
453
+ if (comparison?.state === "conflict") {
454
+ if (!processedDisagreements.has(comparison.key)) {
455
+ processedDisagreements.add(comparison.key);
456
+ addProviderUnresolved(entries, fact, "disagreement", comparison.facts.flatMap((candidate) => [
457
+ providerFactEvidence(candidate),
458
+ ...(isFactEntity(candidate.object) && candidate.object.canonicalId !== undefined
459
+ ? [{ artifact: "provider-correlation", reference: candidate.object.canonicalId }]
460
+ : []),
461
+ ...candidate.subject.candidateCanonicalIds.map((id) => ({
462
+ artifact: "provider-correlation",
463
+ reference: id,
464
+ })),
465
+ ]));
466
+ }
467
+ continue;
468
+ }
469
+ const subjectEntity = subjectKey === undefined ? undefined : entityIndex.byId.get(subjectKey);
470
+ if (!isFactEntity(fact.object)) {
471
+ addProviderUnresolved(entries, fact, "ambiguity", [providerFactEvidence(fact)]);
472
+ continue;
473
+ }
474
+ const targetEntity = fact.object.canonicalId === undefined ? undefined : entityIndex.byId.get(fact.object.canonicalId);
475
+ if (subjectEntity === undefined ||
476
+ providerEntityIsUnresolved(subjectEntity, fact.subject) ||
477
+ targetEntity === undefined ||
478
+ providerEntityIsUnresolved(targetEntity, fact.object) ||
479
+ fact.subject.correlationStatus === "ambiguous" ||
480
+ fact.subject.candidateCanonicalIds.length > 0) {
481
+ addProviderUnresolved(entries, fact, "ambiguity", [
482
+ providerFactEvidence(fact),
483
+ ...fact.object.candidateCanonicalIds.map((id) => ({ artifact: "provider-correlation", reference: id })),
484
+ ]);
485
+ continue;
486
+ }
487
+ const predicate = fact.predicate;
488
+ const reason = providerReason(predicate, fact);
489
+ const evidence = [providerFactEvidence(fact), canonicalEntityEvidence(targetEntity)];
490
+ const targets = entityTargetMembers(targetEntity);
491
+ if (targets.length === 0) {
492
+ addProviderUnresolved(entries, fact, "insufficient-evidence", evidence);
493
+ continue;
494
+ }
495
+ for (const target of targets)
496
+ addEntry(entries, PROVIDER_STATE[predicate], target, reason, evidence);
497
+ // Repository tests are verification context, never required execution
498
+ // targets, even when their component is reached through provider evidence.
499
+ const componentIds = componentIdsForEntity(mappingIndex, targetEntity);
500
+ if (componentIds.length === 0) {
501
+ addConflict(entries, "mapping-gap", stableLocator("provider-entity", targetEntity.canonicalId), evidence);
502
+ }
503
+ if (componentIds.length > 1) {
504
+ addConflict(entries, "ambiguity", stableLocator("provider-entity", targetEntity.canonicalId), evidence);
505
+ }
506
+ if (componentIds.length !== 1)
507
+ continue;
508
+ for (const componentId of componentIds) {
509
+ const mapping = mappingIndex.byId.get(componentId)?.[0];
510
+ if (mapping === undefined)
511
+ continue;
512
+ for (const test of mapping.tests) {
513
+ const target = mappingTestTarget(test.path, test.selector);
514
+ addEntry(entries, "verification", target, { id: stableId("verification", componentId, target.locator), summary: "Canon-mapped verification context" }, [...evidence, mappingEvidence(mapping, "test", target.locator)]);
515
+ }
516
+ }
517
+ }
518
+ }
519
+ function addStaleEvidenceConflicts(entries, providerEvidence, revision) {
520
+ const staleEntityIds = new Set();
521
+ for (const entity of providerEvidence.correlation.canonicalEntities) {
522
+ if (entity.repository.commitSha.toLowerCase() !== revision.toLowerCase()) {
523
+ staleEntityIds.add(entity.canonicalId);
524
+ addConflict(entries, "stale-evidence", stableLocator("provider-correlation", entity.canonicalId), [
525
+ canonicalEntityEvidence(entity),
526
+ ]);
527
+ }
528
+ }
529
+ for (const fact of providerEvidence.facts) {
530
+ if (!isPinnedRevision(fact, revision)) {
531
+ addConflict(entries, "stale-evidence", stableLocator("provider-fact", fact.factId), [providerFactEvidence(fact)]);
532
+ }
533
+ }
534
+ for (const evidence of providerEvidence.unsupported) {
535
+ if (evidence.repository.commitSha.toLowerCase() !== revision.toLowerCase()) {
536
+ addConflict(entries, "stale-evidence", stableLocator("provider-evidence", evidence.nativeEvidence.id), [
537
+ { artifact: "provider-evidence", reference: evidence.nativeEvidence.id },
538
+ ]);
539
+ }
540
+ }
541
+ return staleEntityIds;
542
+ }
543
+ function sameRepository(left, right) {
544
+ return (left.repositoryHost === right.repositoryHost &&
545
+ left.repositoryId === right.repositoryId &&
546
+ left.repository === right.repository);
547
+ }
548
+ function targetReference(target) {
549
+ return `${target.kind}:${target.locator}`;
550
+ }
551
+ function addAuthorizationConflicts(entries, authorization, repository, revision) {
552
+ if (authorization === undefined)
553
+ return;
554
+ const comparisonEvidence = [
555
+ {
556
+ artifact: "authorization-comparison",
557
+ reference: `revision:${authorization.revision ?? revision}`,
558
+ },
559
+ ];
560
+ if (authorization.revision !== undefined && authorization.revision.toLowerCase() !== revision.toLowerCase()) {
561
+ addConflict(entries, "stale-evidence", "authorization-revision", comparisonEvidence);
562
+ return;
563
+ }
564
+ if (authorization.repository !== undefined && !sameRepository(authorization.repository, repository)) {
565
+ addConflict(entries, "stale-evidence", "authorization-repository", comparisonEvidence);
566
+ return;
567
+ }
568
+ const hasBoundary = authorization.targets !== undefined ||
569
+ authorization.entries !== undefined ||
570
+ authorization.authorizedTargets !== undefined;
571
+ if (!hasBoundary) {
572
+ addConflict(entries, "insufficient-evidence", "authorization-boundary", comparisonEvidence);
573
+ return;
574
+ }
575
+ const authorized = new Set(authorizationEntries(authorization).map(({ target }) => workingSetTargetKey(target)));
576
+ const requiredEntries = [...entries.values()]
577
+ .filter((entry) => entry.state === "required" && entry.target.kind !== "unresolved")
578
+ .sort((left, right) => workingSetTargetKey(left.target).localeCompare(workingSetTargetKey(right.target)));
579
+ for (const entry of requiredEntries) {
580
+ if (authorized.has(workingSetTargetKey(entry.target)))
581
+ continue;
582
+ addConflict(entries, "required-but-unauthorized", entry.target.locator, [
583
+ ...entry.evidence,
584
+ ...comparisonEvidence,
585
+ { artifact: "authorization-comparison", reference: `not-listed:${targetReference(entry.target)}` },
586
+ ]);
587
+ }
588
+ }
589
+ /**
590
+ * Derives one authorization-neutral Candidate Working Set from bounded seed
591
+ * resolutions, normalized provider evidence, and the read-only Canon.
592
+ *
593
+ * The algorithm has two deliberate traversal boundaries: Canon `depends-on`
594
+ * may recurse only while the source and target retain the same non-empty
595
+ * boundary membership, and provider relationships are consumed only as a
596
+ * single evidence hop. Other admitted relationship classes add context but
597
+ * never create a transitive provider/repository graph.
598
+ */
599
+ export function deriveCandidateWorkingSet(input) {
600
+ const mappingIndex = createMappingIndex(input.canon.repositoryMappings);
601
+ const entityIndex = createEntityIndex(input.providerEvidence.correlation.canonicalEntities, input.seeds.revision);
602
+ const entries = new Map();
603
+ const staleEntityIds = addStaleEvidenceConflicts(entries, input.providerEvidence, input.seeds.revision);
604
+ for (const resolution of input.seeds.resolutions) {
605
+ if (resolution.status === "unresolved") {
606
+ addUnresolvedSeed(entries, resolution);
607
+ continue;
608
+ }
609
+ for (const target of resolution.targets) {
610
+ const state = target.kind === "test" ? "verification" : "required";
611
+ addEntry(entries, state, target, seedReason(resolution.seed), seedEvidence(resolution));
612
+ }
613
+ }
614
+ if (input.seeds.binding !== "matched") {
615
+ addAuthorizationConflicts(entries, input.authorization, input.seeds.repository, input.seeds.revision);
616
+ return createCandidateWorkingSet({
617
+ workingSetId: input.workingSetId ?? input.seeds.task.taskId,
618
+ repository: input.seeds.repository,
619
+ revision: input.seeds.revision,
620
+ entries: [...entries.values()],
621
+ });
622
+ }
623
+ const activeComponents = expandCanon(entries, input.canon, mappingIndex, input.seeds.resolutions);
624
+ const activeEntities = activeEntitiesForSeeds(input.seeds.resolutions, entityIndex);
625
+ deriveProviderEvidence(entries, input.providerEvidence, entityIndex, staleEntityIds, mappingIndex, activeEntities, activeComponents, input.seeds.revision);
626
+ addAuthorizationConflicts(entries, input.authorization, input.seeds.repository, input.seeds.revision);
627
+ return createCandidateWorkingSet({
628
+ workingSetId: input.workingSetId ?? input.seeds.task.taskId,
629
+ repository: input.seeds.repository,
630
+ revision: input.seeds.revision,
631
+ entries: [...entries.values()],
632
+ });
633
+ }
634
+ //# sourceMappingURL=derive.js.map