@agentxm/extension-materialization 0.28.14-preview.1789139351.5cd4595aa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +16 -0
  3. package/dist/src/desired-state/errors.d.ts +22 -0
  4. package/dist/src/desired-state/errors.js +22 -0
  5. package/dist/src/desired-state/retained-materialization.d.ts +58 -0
  6. package/dist/src/desired-state/retained-materialization.js +119 -0
  7. package/dist/src/errors.d.ts +42 -0
  8. package/dist/src/errors.js +14 -0
  9. package/dist/src/extensions/canonical-directory.d.ts +106 -0
  10. package/dist/src/extensions/canonical-directory.js +210 -0
  11. package/dist/src/extensions/canonical-reuse.d.ts +43 -0
  12. package/dist/src/extensions/canonical-reuse.js +38 -0
  13. package/dist/src/extensions/copy-directory.d.ts +50 -0
  14. package/dist/src/extensions/copy-directory.js +78 -0
  15. package/dist/src/extensions/errors.d.ts +97 -0
  16. package/dist/src/extensions/errors.js +42 -0
  17. package/dist/src/extensions/materializable-from-disk.d.ts +37 -0
  18. package/dist/src/extensions/materializable-from-disk.js +89 -0
  19. package/dist/src/extensions/operations.d.ts +252 -0
  20. package/dist/src/extensions/operations.js +631 -0
  21. package/dist/src/hooks/errors.d.ts +33 -0
  22. package/dist/src/hooks/errors.js +18 -0
  23. package/dist/src/hooks/manager.d.ts +13 -0
  24. package/dist/src/hooks/manager.js +778 -0
  25. package/dist/src/index.d.ts +44 -0
  26. package/dist/src/index.js +56 -0
  27. package/dist/src/knowledge/errors.d.ts +75 -0
  28. package/dist/src/knowledge/errors.js +36 -0
  29. package/dist/src/knowledge/manager.d.ts +9 -0
  30. package/dist/src/knowledge/manager.js +709 -0
  31. package/dist/src/live.d.ts +20 -0
  32. package/dist/src/live.js +20 -0
  33. package/dist/src/manager-contract.d.ts +136 -0
  34. package/dist/src/manager-contract.js +19 -0
  35. package/dist/src/manager-registry-live.d.ts +11 -0
  36. package/dist/src/manager-registry-live.js +23 -0
  37. package/dist/src/manager-registry.d.ts +37 -0
  38. package/dist/src/manager-registry.js +22 -0
  39. package/dist/src/managers.d.ts +147 -0
  40. package/dist/src/managers.js +32 -0
  41. package/dist/src/mcps/artifact.d.ts +24 -0
  42. package/dist/src/mcps/artifact.js +74 -0
  43. package/dist/src/mcps/authored-materialization.d.ts +28 -0
  44. package/dist/src/mcps/authored-materialization.js +34 -0
  45. package/dist/src/mcps/errors.d.ts +105 -0
  46. package/dist/src/mcps/errors.js +40 -0
  47. package/dist/src/mcps/install-operation.d.ts +92 -0
  48. package/dist/src/mcps/install-operation.js +557 -0
  49. package/dist/src/mcps/manager.d.ts +15 -0
  50. package/dist/src/mcps/manager.js +253 -0
  51. package/dist/src/mcps/native-entry.d.ts +51 -0
  52. package/dist/src/mcps/native-entry.js +100 -0
  53. package/dist/src/mcps/secret-store-live.d.ts +14 -0
  54. package/dist/src/mcps/secret-store-live.js +58 -0
  55. package/dist/src/mcps/secret-store.d.ts +55 -0
  56. package/dist/src/mcps/secret-store.js +33 -0
  57. package/dist/src/packs/errors.d.ts +50 -0
  58. package/dist/src/packs/errors.js +24 -0
  59. package/dist/src/packs/manager.d.ts +16 -0
  60. package/dist/src/packs/manager.js +189 -0
  61. package/dist/src/projection-participants-live.d.ts +17 -0
  62. package/dist/src/projection-participants-live.js +66 -0
  63. package/dist/src/projection-step-failure.d.ts +19 -0
  64. package/dist/src/projection-step-failure.js +76 -0
  65. package/dist/src/registry-materialization.d.ts +52 -0
  66. package/dist/src/registry-materialization.js +67 -0
  67. package/dist/src/rules/errors.d.ts +31 -0
  68. package/dist/src/rules/errors.js +17 -0
  69. package/dist/src/rules/manager.d.ts +18 -0
  70. package/dist/src/rules/manager.js +492 -0
  71. package/dist/src/skills/errors.d.ts +42 -0
  72. package/dist/src/skills/errors.js +21 -0
  73. package/dist/src/skills/manager.d.ts +17 -0
  74. package/dist/src/skills/manager.js +339 -0
  75. package/dist/src/skills/materialization.d.ts +52 -0
  76. package/dist/src/skills/materialization.js +168 -0
  77. package/dist/src/skills/skill-artifact.d.ts +38 -0
  78. package/dist/src/skills/skill-artifact.js +70 -0
  79. package/dist/src/skills/source-hash.d.ts +2 -0
  80. package/dist/src/skills/source-hash.js +6 -0
  81. package/dist/src/subagents/errors.d.ts +43 -0
  82. package/dist/src/subagents/errors.js +20 -0
  83. package/dist/src/subagents/lock-entry-builder.d.ts +20 -0
  84. package/dist/src/subagents/lock-entry-builder.js +63 -0
  85. package/dist/src/subagents/manager.d.ts +17 -0
  86. package/dist/src/subagents/manager.js +736 -0
  87. package/dist/src/testing.d.ts +87 -0
  88. package/dist/src/testing.js +116 -0
  89. package/package.json +70 -0
@@ -0,0 +1,709 @@
1
+ // @effect-diagnostics anyUnknownInErrorContext:off — schema and filesystem errors are swept into KnowledgeIoFailed inside this manager
2
+ /** Lifecycle manager for isolated Open Knowledge Format bundles. */
3
+ import * as Effect from "effect/Effect";
4
+ import { stripFileProtocol } from "@agentxm/registry-client";
5
+ import * as FileSystem from "effect/FileSystem";
6
+ import * as Layer from "effect/Layer";
7
+ import * as Option from "effect/Option";
8
+ import * as Path from "effect/Path";
9
+ import * as Ref from "effect/Ref";
10
+ import * as Schema from "effect/Schema";
11
+ import * as Scope from "effect/Scope";
12
+ import * as Result from "effect/Result";
13
+ import { PlatformError } from "effect/PlatformError";
14
+ import { KnowledgeDefinitionInvalid, KnowledgeDesiredStateUnreconcilable, KnowledgeInstallStateMissing, KnowledgeIoFailed, KnowledgeObservableContractViolated, KnowledgeResolutionMissing, KnowledgeUnavailable, } from "./errors.js";
15
+ import { applyProjectionPlans, formatProjectionExclusions, planAggregateProjection, requireCompleteGraph, KNOWLEDGE_REGION_OWNER, reconcileKnowledgeDiscovery, resolveInstructionsConfig, } from "@agentxm/workspace-projection";
16
+ import { canReuseInstalledPackage, materializeExternalPackage, } from "../extensions/canonical-directory.js";
17
+ import { materializeRegistryPackage } from "../registry-materialization.js";
18
+ import { computeExtensionPathsForLayout, extensionPathSourceFromLockEntry, } from "@agentxm/workspace-state";
19
+ import { computePackageContentHash } from "@agentxm/workspace-state";
20
+ import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
21
+ import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
22
+ import { gitSourceLockFields } from "@agentxm/workspace-state";
23
+ import { SourceHostProviders, WorkspaceCatalog } from "@agentxm/extension-sources";
24
+ import { knowledgeLockEntryToRef } from "@agentxm/workspace-state";
25
+ import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
26
+ import { recordFootprint } from "@agentxm/workspace-transactions";
27
+ import { makeWorkspaceRelativePath } from "@agentxm/extension-model/unstable/path-types";
28
+ import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
29
+ import { usableAcceptedCanonicalRef } from "@agentxm/workspace-state";
30
+ import { getKnowledgeLockEntries } from "@agentxm/workspace-state";
31
+ import { NO_MATERIALIZATION_OBSERVATION } from "../manager-contract.js";
32
+ import { KnowledgeManager, } from "../managers.js";
33
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
34
+ import { runWorkspaceTransaction } from "@agentxm/workspace-transactions";
35
+ import { isObservedInstalled } from "@agentxm/workspace-state";
36
+ import { acceptedCanonicalObservation, prepareAcceptedCanonicalTransition, removableAcceptedCanonicalPath, } from "@agentxm/workspace-state";
37
+ import { protectWorkspacePath } from "@agentxm/workspace-transactions";
38
+ import { isSourcedDesiredExtension } from "@agentxm/workspace-state";
39
+ import { KNOWLEDGE_EXTENSION_DIR, KNOWLEDGE_MANIFEST_FILENAME, KNOWLEDGE_SOURCE_DIR, KnowledgeManifestSchema, } from "@agentxm/extension-model/unstable/knowledge/manifest-schema";
40
+ import { inspectKnowledgeBundle, } from "@agentxm/extension-content/knowledge";
41
+ import { resolveKnowledgeInstructionEntry } from "@agentxm/workspace-projection";
42
+ const decodeManifest = Schema.decodeUnknownEffect(KnowledgeManifestSchema);
43
+ const registryLockEntry = (ref, treeIntegrity) => ({
44
+ type: "registry",
45
+ sourceType: "registry",
46
+ packageFormat: "agentxm",
47
+ endpoint: ref.source.location,
48
+ extensionType: "knowledge",
49
+ workspaceName: ref.knowledge.name,
50
+ owner: ref.owner,
51
+ name: ref.name,
52
+ resolvedVersion: decodeVersionSync(ref.version),
53
+ integrity: Option.getOrElse(ref.integrity, () => ""),
54
+ sourceName: ref.source.name,
55
+ publisherBindingId: ref.publisherBindingId,
56
+ treeIntegrity,
57
+ });
58
+ const gitLockEntry = (ref, contentIdentity, treeIntegrity) => ({
59
+ ...gitSourceLockFields(ref.source, "knowledge", ref.knowledge.name, Option.fromUndefinedOr(ref.sourcePath), ref.gitCommitSha, ref.gitTreeSha, contentIdentity, ref.owner, ref.name, treeIntegrity),
60
+ });
61
+ const localLockEntry = (ref, relativePath, contentIdentity, treeIntegrity) => ({
62
+ type: "local",
63
+ sourceType: "local",
64
+ sourceName: "local",
65
+ extensionType: "knowledge",
66
+ workspaceName: ref.knowledge.name,
67
+ packageFormat: "agentxm",
68
+ packageOwner: ref.owner,
69
+ packageName: ref.name,
70
+ path: Option.getOrElse(relativePath, () => ref.source.path),
71
+ contentIdentity,
72
+ treeIntegrity,
73
+ });
74
+ /**
75
+ * Name the failure that stopped a restore in the operator's sentence: the
76
+ * producing family carries its own detail, so no application envelope is
77
+ * needed to describe it.
78
+ */
79
+ const describeKnowledgeFailure = (failure) => "detail" in failure && typeof failure.detail === "string" ? failure.detail : failure._tag;
80
+ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(function* () {
81
+ const ws = yield* WorkspaceMutations;
82
+ const fs = yield* FileSystem.FileSystem;
83
+ const path = yield* Path.Path;
84
+ const sources = yield* SourceHostProviders;
85
+ const catalog = yield* WorkspaceCatalog;
86
+ const baseDir = ws.baseDir;
87
+ // The workspace facade and the source integration are this layer's own
88
+ // dependencies; the platform stays in `R` for every member.
89
+ const provide = (effect) => effect.pipe(Effect.provideService(WorkspaceMutations, ws), Effect.provideService(SourceHostProviders, sources), Effect.provideService(WorkspaceCatalog, catalog));
90
+ const acquiredFacts = (prepared, relativeLocalSource) => ({
91
+ observation: NO_MATERIALIZATION_OBSERVATION,
92
+ acquired: Option.some({
93
+ relativeLocalSource,
94
+ sourceHash: prepared.sourceHash,
95
+ ...(prepared.treeIntegrity === undefined ? {} : { treeIntegrity: prepared.treeIntegrity }),
96
+ }),
97
+ });
98
+ const withdrawn = {
99
+ observation: NO_MATERIALIZATION_OBSERVATION,
100
+ acquired: Option.none(),
101
+ };
102
+ const canonicalPathForRef = (ref) => computeExtensionPathsForLayout(path.join, ws.layout, ref, KNOWLEDGE_EXTENSION_DIR, ref.name)
103
+ .canonicalPath;
104
+ const materializePackage = (ref, options) => {
105
+ const canonicalPath = options?.destinationPath ?? canonicalPathForRef(ref);
106
+ const materializationBaseDir = options?.baseDir ?? baseDir;
107
+ switch (ref.refType) {
108
+ case "registry":
109
+ return Effect.gen(function* () {
110
+ return yield* provide(materializeRegistryPackage({
111
+ baseDir: materializationBaseDir,
112
+ destinationPath: canonicalPath,
113
+ sourceLocation: ref.source.location,
114
+ owner: ref.owner,
115
+ type: "knowledge",
116
+ name: ref.name,
117
+ version: ref.version,
118
+ integrity: ref.integrity,
119
+ messages: {
120
+ integrityMismatchDetail: `Integrity mismatch for knowledge:${ref.name}@${ref.version}`,
121
+ },
122
+ }));
123
+ });
124
+ case "git-hosted":
125
+ case "local":
126
+ return provide(materializeExternalPackage({
127
+ baseDir: materializationBaseDir,
128
+ canonicalPath,
129
+ sourceLocation: ref.location,
130
+ copyFailureCode: "validation",
131
+ copyFailureDetail: (target) => `Failed to copy knowledge package to ${target}`,
132
+ }));
133
+ case "workspace":
134
+ if (ref.scope !== ws.scope ||
135
+ path.resolve(ref.location) !== path.resolve(canonicalPath)) {
136
+ return Effect.fail(new KnowledgeDefinitionInvalid({
137
+ detail: `Invalid workspace knowledge source location: ${ref.location}`,
138
+ }));
139
+ }
140
+ return Effect.succeed(ref.location);
141
+ }
142
+ };
143
+ const inspectPackage = (packageRoot) => Effect.gen(function* () {
144
+ const raw = yield* fs
145
+ .readFileString(path.join(packageRoot, KNOWLEDGE_MANIFEST_FILENAME))
146
+ .pipe(Effect.mapError((cause) => new KnowledgeDefinitionInvalid({
147
+ detail: `Failed to read ${KNOWLEDGE_MANIFEST_FILENAME}`,
148
+ cause,
149
+ })));
150
+ const manifest = yield* Effect.try({
151
+ try: () => JSON.parse(raw),
152
+ catch: (cause) => new KnowledgeDefinitionInvalid({
153
+ detail: `Failed to parse ${KNOWLEDGE_MANIFEST_FILENAME}`,
154
+ cause,
155
+ }),
156
+ }).pipe(Effect.flatMap(decodeManifest), Effect.mapError((cause) => new KnowledgeDefinitionInvalid({
157
+ detail: `Invalid ${KNOWLEDGE_MANIFEST_FILENAME}`,
158
+ cause,
159
+ })));
160
+ const inspection = yield* provide(inspectKnowledgeBundle(path.join(packageRoot, KNOWLEDGE_SOURCE_DIR))).pipe(Effect.mapError((cause) => new KnowledgeDefinitionInvalid({
161
+ detail: "Failed to inspect Open Knowledge Format bundle",
162
+ cause,
163
+ })));
164
+ const errors = inspection.diagnostics.filter((item) => item.severity === "error");
165
+ if (errors.length > 0) {
166
+ return yield* new KnowledgeDefinitionInvalid({
167
+ detail: errors
168
+ .map((item) => item.details?.kind === "frontmatter-parse"
169
+ ? `${item.relativePath}: ${item.message}`
170
+ : item.message)
171
+ .join(" "),
172
+ });
173
+ }
174
+ return { manifest, inspection };
175
+ });
176
+ const preparePackage = (ref, force = false) => Effect.gen(function* () {
177
+ const canonicalPath = canonicalPathForRef(ref);
178
+ if (ref.refType === "workspace") {
179
+ const root = yield* materializePackage(ref);
180
+ yield* inspectPackage(root);
181
+ return {
182
+ root,
183
+ sourceHash: ref.sourceHash,
184
+ commit: Effect.void,
185
+ rollback: Effect.void,
186
+ };
187
+ }
188
+ // Decide against the canonical tree before staging: the staged path
189
+ // never exists, so a decision made there would re-extract every time
190
+ // and revert workspace-owned content on a no-op install.
191
+ if (ref.refType === "registry") {
192
+ const lockedEntry = yield* ws.getLockedKnowledgeEntry(ref.knowledge.name);
193
+ const lockedVersion = acceptedRegistryVersionForRef(lockedEntry, ref);
194
+ const reuse = yield* provide(canReuseInstalledPackage({
195
+ installedPath: canonicalPath,
196
+ force,
197
+ refVersion: ref.version,
198
+ hasIntegrity: Option.isSome(ref.integrity),
199
+ ...(lockedVersion === undefined ? {} : { lockedVersion }),
200
+ existsFailureDetail: (target) => `Failed to inspect knowledge path: ${target}`,
201
+ }));
202
+ if (reuse && Option.isSome(lockedEntry)) {
203
+ const observedTree = yield* provide(computeMaterializedTreeIntegrity(canonicalPath));
204
+ if (observedTree !== lockedEntry.value.treeIntegrity) {
205
+ yield* Effect.logWarning(`Knowledge package ${ref.knowledge.name} differs from its accepted tree; rematerializing`);
206
+ }
207
+ else {
208
+ return {
209
+ root: canonicalPath,
210
+ sourceHash: yield* provide(computePackageContentHash(canonicalPath)),
211
+ treeIntegrity: lockedEntry.value.treeIntegrity,
212
+ commit: Effect.void,
213
+ rollback: Effect.void,
214
+ };
215
+ }
216
+ }
217
+ }
218
+ const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "axm-knowledge-package-" });
219
+ const stagedPath = path.join(tempDir, "staged");
220
+ const backupPath = path.join(tempDir, "previous");
221
+ const stageState = yield* Ref.make({ phase: "preparing" });
222
+ const restoreStagedPackage = Ref.get(stageState).pipe(Effect.flatMap((state) => {
223
+ if (state.phase !== "staged")
224
+ return Effect.void;
225
+ return fs.remove(canonicalPath, { recursive: true, force: true }).pipe(Effect.mapError((cause) => new KnowledgeIoFailed({
226
+ detail: `Failed to remove staged Knowledge package during rollback: ${canonicalPath}`,
227
+ cause,
228
+ })), Effect.andThen(state.hadCanonical
229
+ ? fs.rename(backupPath, canonicalPath).pipe(Effect.mapError((cause) => new KnowledgeIoFailed({
230
+ detail: `Failed to restore Knowledge package during rollback: ${canonicalPath}`,
231
+ cause,
232
+ })))
233
+ : Effect.void), Effect.andThen(Ref.set(stageState, { phase: "settled" })));
234
+ }));
235
+ yield* Effect.addFinalizer(() => restoreStagedPackage.pipe(Effect.catchCause((cause) => Effect.logError("Failed to restore staged Knowledge package during finalization", cause))));
236
+ const stagedRoot = yield* materializePackage(ref, {
237
+ baseDir: tempDir,
238
+ destinationPath: stagedPath,
239
+ });
240
+ yield* inspectPackage(stagedRoot);
241
+ const sourceHash = yield* provide(computePackageContentHash(stagedRoot));
242
+ const treeIntegrity = yield* provide(computeMaterializedTreeIntegrity(stagedRoot));
243
+ yield* protectWorkspacePath(canonicalPath);
244
+ const hadCanonical = yield* fs.exists(canonicalPath);
245
+ yield* Effect.uninterruptible(Effect.gen(function* () {
246
+ if (hadCanonical)
247
+ yield* fs.rename(canonicalPath, backupPath);
248
+ yield* fs.makeDirectory(path.dirname(canonicalPath), { recursive: true });
249
+ yield* fs.rename(stagedPath, canonicalPath).pipe(Effect.tapError(() => hadCanonical
250
+ ? fs.rename(backupPath, canonicalPath).pipe(Effect.mapError((cause) => new KnowledgeIoFailed({
251
+ detail: `Failed to restore Knowledge package after staging failed: ${canonicalPath}`,
252
+ cause,
253
+ })))
254
+ : Effect.void));
255
+ yield* Ref.set(stageState, { phase: "staged", hadCanonical });
256
+ }));
257
+ yield* recordFootprint({
258
+ path: canonicalPath,
259
+ change: hadCanonical ? "modified" : "created",
260
+ });
261
+ return {
262
+ root: canonicalPath,
263
+ sourceHash,
264
+ treeIntegrity,
265
+ commit: Ref.set(stageState, { phase: "settled" }),
266
+ rollback: restoreStagedPackage,
267
+ };
268
+ }).pipe(Effect.mapError((cause) => cause instanceof PlatformError
269
+ ? new KnowledgeIoFailed({
270
+ detail: `Failed to stage Knowledge bundle ${ref.knowledge.name}`,
271
+ cause,
272
+ })
273
+ : cause));
274
+ const getInstructionsTarget = () => Effect.gen(function* () {
275
+ const config = yield* ws.getInstructionsConfig();
276
+ const enabled = Option.isSome(config) && config.value !== false;
277
+ const resolved = resolveInstructionsConfig(enabled ? config.value : undefined);
278
+ const relative = makeWorkspaceRelativePath(path, baseDir, resolved.fileName);
279
+ if (Option.isNone(relative)) {
280
+ return yield* new KnowledgeDefinitionInvalid({
281
+ detail: `Knowledge discovery instruction target escapes workspace: ${resolved.fileName}`,
282
+ });
283
+ }
284
+ return {
285
+ path: path.resolve(baseDir, relative.value),
286
+ enabled,
287
+ preserveSource: enabled,
288
+ };
289
+ });
290
+ const canonicalRoot = (_name, locked) => computeExtensionPathsForLayout(path.join, ws.layout, extensionPathSourceFromLockEntry(locked), KNOWLEDGE_EXTENSION_DIR, locked.workspaceName).canonicalPath;
291
+ const desiredCanonicalRoot = (node, locked) => {
292
+ if (node.identity.startsWith("workspace:")) {
293
+ return ws.layout.scope === "project"
294
+ ? Effect.succeed(path.join(ws.layout.authoredRoot("knowledge"), node.name))
295
+ : Effect.fail(new KnowledgeDefinitionInvalid({
296
+ detail: "User workspaces do not support workspace-authored Knowledge bundles",
297
+ }));
298
+ }
299
+ return locked === undefined
300
+ ? Effect.fail(new KnowledgeResolutionMissing({ name: node.name }))
301
+ : Effect.succeed(canonicalRoot(node.name, locked));
302
+ };
303
+ const toProjectionBundle = (root, inspected) => ({
304
+ owner: inspected.manifest.owner,
305
+ name: inspected.manifest.name,
306
+ sourceDir: path.join(root, KNOWLEDGE_SOURCE_DIR),
307
+ ...(inspected.manifest.description === undefined
308
+ ? {}
309
+ : { description: inspected.manifest.description }),
310
+ });
311
+ const activeKnowledgeNodes = () => ws
312
+ .getDesiredStateGraph()
313
+ .pipe(Effect.flatMap((graph) => graph.complete
314
+ ? Effect.succeed(graph.nodes
315
+ .filter(isSourcedDesiredExtension)
316
+ .filter((node) => node.type === "knowledge" && node.enabled))
317
+ : new KnowledgeDesiredStateUnreconcilable()));
318
+ /**
319
+ * Classify a bundle whose package could not be inspected. Only the
320
+ * bundle's own content is in question here: resolution facts have already
321
+ * succeeded, so the two truthful answers are that the package is gone or
322
+ * that its content is invalid.
323
+ */
324
+ const excludedContributor = (name, root, failure) => Effect.gen(function* () {
325
+ const probe = yield* Effect.result(fs.exists(path.join(root, KNOWLEDGE_MANIFEST_FILENAME)));
326
+ const manifestPresent = Result.isSuccess(probe) && probe.success;
327
+ if (!manifestPresent)
328
+ return { contributor: name, reason: "package-missing" };
329
+ return {
330
+ contributor: name,
331
+ reason: "package-invalid",
332
+ detail: failure._tag === "KnowledgeDefinitionInvalid"
333
+ ? failure.detail
334
+ : describeKnowledgeFailure(failure),
335
+ };
336
+ });
337
+ /**
338
+ * Resolve the contributor set for the discovery region. A bundle that
339
+ * fails inspection cannot supply a row, so it is excluded and reported
340
+ * rather than failing every other bundle's command with it. Resolution
341
+ * failures — a missing lock entry, an unsupported workspace-authored
342
+ * bundle — remain fail-closed: they are AXM state problems, not bundle
343
+ * content problems.
344
+ */
345
+ const selectKnowledgeBundles = (graph, locked, configured, config, instructionFilesEnabled) => Effect.forEach(graph.nodes.filter((node) => node.type === "knowledge" && node.enabled), (node) => Effect.gen(function* () {
346
+ const root = yield* desiredCanonicalRoot(node, locked[node.name]);
347
+ const workspaceInstructionEntry = configured[node.name]?.instructionEntry;
348
+ const resolveInclusion = (manifestInstructionEntry) => resolveKnowledgeInstructionEntry({
349
+ bundleEnabled: node.enabled,
350
+ instructionFilesEnabled,
351
+ knowledgeInstructionsEnabled: config.instructions,
352
+ ...(workspaceInstructionEntry === undefined ? {} : { workspaceInstructionEntry }),
353
+ ...(manifestInstructionEntry === undefined ? {} : { manifestInstructionEntry }),
354
+ });
355
+ const inspection = yield* Effect.result(inspectPackage(root));
356
+ if (Result.isFailure(inspection)) {
357
+ // A bundle the workspace would not publish anyway is simply
358
+ // absent from the region; reporting it would be noise.
359
+ return resolveInclusion().included
360
+ ? {
361
+ contributors: [],
362
+ exclusions: [yield* excludedContributor(node.name, root, inspection.failure)],
363
+ }
364
+ : { contributors: [], exclusions: [] };
365
+ }
366
+ const inspected = inspection.success;
367
+ const resolution = resolveInclusion(inspected.manifest.instructionEntry);
368
+ return {
369
+ contributors: resolution.included ? [toProjectionBundle(root, inspected)] : [],
370
+ exclusions: [],
371
+ };
372
+ })).pipe(Effect.map((selections) => ({
373
+ contributors: selections.flatMap(({ contributors }) => contributors),
374
+ exclusions: selections.flatMap(({ exclusions }) => exclusions),
375
+ })));
376
+ const resolveKnowledgeProjection = () => Effect.gen(function* () {
377
+ const graph = yield* ws.getDesiredStateGraph();
378
+ const locked = yield* getKnowledgeLockEntries(ws);
379
+ const configured = yield* ws.getConfiguredKnowledgeEntries();
380
+ const config = yield* ws.getKnowledgeDiscoveryConfig();
381
+ const instructionsTarget = yield* getInstructionsTarget();
382
+ return { graph, locked, configured, config, instructionsTarget };
383
+ });
384
+ const runKnowledgeProjectionAdapter = (args) => provide(reconcileKnowledgeDiscovery({
385
+ scopeRoot: baseDir,
386
+ config: args.config,
387
+ bundles: args.bundles,
388
+ instructionsPath: args.instructionsTarget.path,
389
+ instructionManagementEnabled: args.instructionsTarget.enabled,
390
+ preserveInstructionsSource: args.instructionsTarget.preserveSource,
391
+ ...(args.dryRun === undefined ? {} : { dryRun: args.dryRun }),
392
+ }));
393
+ const makeKnowledgeProjectionPlan = () => Effect.gen(function* () {
394
+ const { graph, locked, configured, config, instructionsTarget } = yield* resolveKnowledgeProjection();
395
+ return yield* planAggregateProjection({
396
+ unitId: "knowledge:discovery-region",
397
+ targetFile: instructionsTarget.path,
398
+ graph,
399
+ select: (completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled),
400
+ adapter: {
401
+ observe: (input) => runKnowledgeProjectionAdapter({
402
+ bundles: input.contributors,
403
+ config,
404
+ instructionsTarget,
405
+ dryRun: true,
406
+ }).pipe(Effect.map((result) => ({
407
+ unitId: "knowledge:discovery-region",
408
+ path: `${instructionsTarget.path}#knowledge`,
409
+ owner: KNOWLEDGE_REGION_OWNER,
410
+ present: Option.isSome(result.observedRegion),
411
+ current: !result.changed,
412
+ expectedContributors: input.contributors.map(({ owner, name }) => `${owner}/knowledge/${name}`),
413
+ }))),
414
+ apply: (input) => runKnowledgeProjectionAdapter({
415
+ bundles: input.contributors,
416
+ config,
417
+ instructionsTarget,
418
+ }).pipe(Effect.asVoid),
419
+ },
420
+ });
421
+ });
422
+ const projectionPlans = () => makeKnowledgeProjectionPlan().pipe(Effect.map((plan) => [plan]));
423
+ const applyKnowledgeProjection = projectionPlans().pipe(Effect.flatMap(applyProjectionPlans));
424
+ const reconcileDiscovery = (options) => resolveKnowledgeProjection().pipe(Effect.flatMap(({ graph, locked, configured, config, instructionsTarget }) => requireCompleteGraph(graph).pipe(Effect.flatMap((completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled)), Effect.flatMap(({ contributors, exclusions }) => runKnowledgeProjectionAdapter({
425
+ bundles: contributors,
426
+ config,
427
+ instructionsTarget,
428
+ ...(options?.dryRun === undefined ? {} : { dryRun: options.dryRun }),
429
+ }).pipe(Effect.map((result) => ({
430
+ ...result,
431
+ warnings: formatProjectionExclusions({
432
+ exclusions,
433
+ targetFile: instructionsTarget.path,
434
+ }),
435
+ })))))));
436
+ const buildLockEntry = (ref, materialization) => Effect.gen(function* () {
437
+ const state = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.acquired)));
438
+ switch (ref.refType) {
439
+ case "registry":
440
+ return state?.treeIntegrity === undefined
441
+ ? yield* new KnowledgeInstallStateMissing({
442
+ name: ref.knowledge.name,
443
+ kind: "tree-integrity",
444
+ })
445
+ : Option.some(registryLockEntry(ref, state.treeIntegrity));
446
+ case "git-hosted":
447
+ return state?.treeIntegrity === undefined
448
+ ? yield* new KnowledgeInstallStateMissing({
449
+ name: ref.knowledge.name,
450
+ kind: "content-identity",
451
+ })
452
+ : Option.some(gitLockEntry(ref, state.sourceHash, state.treeIntegrity));
453
+ case "local":
454
+ return state?.treeIntegrity === undefined
455
+ ? yield* new KnowledgeInstallStateMissing({
456
+ name: ref.knowledge.name,
457
+ kind: "content-identity",
458
+ })
459
+ : Option.some(localLockEntry(ref, state.relativeLocalSource, state.sourceHash, state.treeIntegrity));
460
+ case "workspace":
461
+ return Option.none();
462
+ }
463
+ });
464
+ const setKnowledgeSourceEntry = (name, source) => Effect.gen(function* () {
465
+ const configured = yield* ws.getConfiguredKnowledgeEntries();
466
+ const current = configured[name];
467
+ yield* ws.setKnowledgeEntry(name, {
468
+ source,
469
+ enabled: true,
470
+ ...(current?.instructionEntry === undefined
471
+ ? {}
472
+ : { instructionEntry: current.instructionEntry }),
473
+ });
474
+ });
475
+ const restoreLockedPackage = (name, entry) => Effect.gen(function* () {
476
+ const ref = yield* knowledgeLockEntryToRef(name, entry, {
477
+ baseDir,
478
+ path,
479
+ scope: ws.scope,
480
+ getConfiguredSourceByName: (sourceName) => ws.getConfiguredSourceByName(sourceName),
481
+ });
482
+ if (ref.refType === "registry" && Option.isNone(ref.integrity)) {
483
+ return yield* new KnowledgeUnavailable({
484
+ detail: `Locked registry Knowledge bundle has no integrity and cannot be restored: ${name}`,
485
+ });
486
+ }
487
+ if (ref.refType !== "git-hosted") {
488
+ return yield* preparePackage(ref);
489
+ }
490
+ const discovered = yield* sources.find(ref.source, {
491
+ names: [name],
492
+ type: "knowledge",
493
+ owner: Option.none(),
494
+ versionRange: Option.none(),
495
+ });
496
+ const candidate = discovered.find((item) => item.type === "knowledge" && item.refType === "git-hosted");
497
+ if (candidate === undefined ||
498
+ candidate.gitTreeSha !== ref.gitTreeSha ||
499
+ candidate.gitCommitSha !== ref.gitCommitSha) {
500
+ return yield* new KnowledgeUnavailable({
501
+ detail: `Git Knowledge source no longer resolves to locked commit/tree ${ref.gitCommitSha}/${ref.gitTreeSha}: ${name}`,
502
+ });
503
+ }
504
+ return yield* preparePackage(candidate);
505
+ });
506
+ const syncLocked = (dryRun) => Effect.gen(function* () {
507
+ const desired = yield* activeKnowledgeNodes();
508
+ const locked = yield* getKnowledgeLockEntries(ws);
509
+ const prepared = [];
510
+ for (const node of desired) {
511
+ const { name } = node;
512
+ const entry = locked[name];
513
+ const root = yield* desiredCanonicalRoot(node, entry);
514
+ const inspection = yield* Effect.result(inspectPackage(root));
515
+ if (Result.isSuccess(inspection))
516
+ continue;
517
+ if (node.identity.startsWith("workspace:")) {
518
+ return yield* new KnowledgeDefinitionInvalid({
519
+ detail: `Active workspace-authored Knowledge bundle is missing or invalid: ${name}`,
520
+ cause: inspection.failure,
521
+ });
522
+ }
523
+ if (entry === undefined) {
524
+ return yield* new KnowledgeResolutionMissing({ name });
525
+ }
526
+ const restored = yield* Effect.result(restoreLockedPackage(name, entry));
527
+ if (Result.isFailure(restored)) {
528
+ yield* Effect.forEach([...prepared].reverse(), (item) => item.rollback, {
529
+ discard: true,
530
+ });
531
+ const restoreFailure = restored.failure;
532
+ return yield* new KnowledgeUnavailable({
533
+ detail: `Active Knowledge bundle could not be restored: ${name}. ${describeKnowledgeFailure(restoreFailure)}`,
534
+ cause: restoreFailure,
535
+ });
536
+ }
537
+ else
538
+ prepared.push(restored.success);
539
+ }
540
+ const discovered = yield* Effect.result(reconcileDiscovery({ dryRun: true }));
541
+ if (Result.isFailure(discovered)) {
542
+ yield* Effect.forEach([...prepared].reverse(), (item) => item.rollback, {
543
+ discard: true,
544
+ });
545
+ return yield* discovered.failure;
546
+ }
547
+ if (!dryRun)
548
+ yield* applyKnowledgeProjection;
549
+ yield* Effect.forEach(dryRun ? [...prepared].reverse() : prepared, (item) => (dryRun ? item.rollback : item.commit), { discard: true });
550
+ const discovery = discovered.success;
551
+ return {
552
+ changed: prepared.length > 0 || discovery.changed,
553
+ warnings: discovery.warnings,
554
+ artifacts: discovery.artifacts,
555
+ };
556
+ });
557
+ const installAtomically = (args) => runWorkspaceTransaction({
558
+ transition: Effect.gen(function* () {
559
+ const name = args.ref.knowledge.name;
560
+ const relativeLocalSource = args.ref.refType === "local"
561
+ ? makeWorkspaceRelativeSourcePath(path, baseDir, args.ref.sourcePath ?? stripFileProtocol(args.ref.location))
562
+ : Option.none();
563
+ if (args.ref.refType === "local" && Option.isNone(relativeLocalSource)) {
564
+ return yield* new KnowledgeDefinitionInvalid({
565
+ detail: `Local knowledge source must stay within the workspace: ${args.ref.source.path}`,
566
+ });
567
+ }
568
+ const cleanupSupersededCanonical = yield* provide(prepareAcceptedCanonicalTransition({
569
+ workspace: ws,
570
+ type: "knowledge",
571
+ name,
572
+ ref: args.ref,
573
+ }));
574
+ const prepared = yield* preparePackage(args.ref);
575
+ const committed = yield* Effect.gen(function* () {
576
+ const facts = acquiredFacts(prepared, relativeLocalSource);
577
+ if (args.ref.refType !== "workspace" && prepared.treeIntegrity === undefined) {
578
+ return yield* new KnowledgeInstallStateMissing({
579
+ name,
580
+ kind: "staged-tree-integrity",
581
+ });
582
+ }
583
+ const lockEntry = yield* buildLockEntry(args.ref, Option.some(facts));
584
+ if (Option.isSome(lockEntry)) {
585
+ yield* ws.setKnowledge({
586
+ name,
587
+ lockEntry: lockEntry.value,
588
+ versionRange: args.versionRange,
589
+ });
590
+ }
591
+ else {
592
+ yield* setKnowledgeSourceEntry(name, "workspace");
593
+ }
594
+ yield* cleanupSupersededCanonical;
595
+ return { name };
596
+ });
597
+ if (args.deferProjection !== true)
598
+ yield* applyKnowledgeProjection;
599
+ // The workspace transaction owns a nested scope. Settle the staged
600
+ // package before that scope closes; a later postcondition failure
601
+ // still restores the transaction snapshot.
602
+ yield* prepared.commit;
603
+ return committed;
604
+ }),
605
+ validate: ({ name }) => isObservedInstalled(ws, "knowledge", name).pipe(Effect.flatMap((installed) => installed ? Effect.void : new KnowledgeObservableContractViolated({ name }))),
606
+ }).pipe(Effect.scoped, Effect.asVoid);
607
+ // Canonical removal only. The shared operation flow re-renders the
608
+ // discovery region after settings and lock removal, once the target has
609
+ // left the graph.
610
+ const materializeUninstall = Effect.fn("KnowledgeManager.materializeUninstall")(function* ({ target, }) {
611
+ const canonical = yield* provide(acceptedCanonicalObservation({
612
+ workspace: ws,
613
+ type: "knowledge",
614
+ name: target.name,
615
+ }));
616
+ const root = removableAcceptedCanonicalPath(canonical);
617
+ const locked = yield* ws.getLockedKnowledgeEntry(target.name);
618
+ const ownedRoot = Option.orElse(root, () => Option.map(locked, (entry) => canonicalRoot(target.name, entry)));
619
+ if (Option.isSome(ownedRoot)) {
620
+ yield* protectWorkspacePath(ownedRoot.value);
621
+ yield* fs.remove(ownedRoot.value, { recursive: true, force: true }).pipe(Effect.mapError((cause) => new KnowledgeIoFailed({
622
+ detail: `Failed to remove Knowledge package source: ${ownedRoot.value}`,
623
+ cause,
624
+ })));
625
+ }
626
+ return withdrawn;
627
+ });
628
+ // Deactivation retains canonical content; the caller updates settings
629
+ // first, so re-rendering the whole region drops this bundle's routing.
630
+ const materializeDeactivate = Effect.fn("KnowledgeManager.materializeDeactivate")(() => applyKnowledgeProjection.pipe(Effect.as(withdrawn)));
631
+ return {
632
+ type: "knowledge",
633
+ projectionPlans,
634
+ refreshCatalog: () => runWorkspaceTransaction({
635
+ transition: applyKnowledgeProjection,
636
+ validate: () => Effect.void,
637
+ }),
638
+ sync: ({ dryRun }) => dryRun
639
+ ? Effect.scoped(syncLocked(true))
640
+ : Effect.scoped(runWorkspaceTransaction({
641
+ transition: syncLocked(false),
642
+ validate: () => Effect.void,
643
+ })),
644
+ install: installAtomically,
645
+ isInstalled: ({ target }) => isObservedInstalled(ws, "knowledge", target.name),
646
+ materializeInstall: Effect.fn("KnowledgeManager.materializeInstall")(function* ({ ref, force, }) {
647
+ const relativeLocalSource = ref.refType === "local"
648
+ ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
649
+ : Option.none();
650
+ if (ref.refType === "local" && Option.isNone(relativeLocalSource)) {
651
+ return yield* new KnowledgeDefinitionInvalid({
652
+ detail: `Local knowledge source must stay within the workspace: ${ref.source.path}`,
653
+ });
654
+ }
655
+ const prepared = yield* preparePackage(ref, force === true);
656
+ yield* prepared.commit;
657
+ return acquiredFacts(prepared, relativeLocalSource);
658
+ }, Effect.scoped),
659
+ prepareSourceTransition: ({ ref }) => provide(prepareAcceptedCanonicalTransition({
660
+ workspace: ws,
661
+ type: "knowledge",
662
+ name: ref.knowledge.name,
663
+ ref,
664
+ })),
665
+ getConfiguredSource: ({ target }) => ws
666
+ .getConfiguredKnowledgeEntries()
667
+ .pipe(Effect.map((entries) => Option.fromUndefinedOr(entries[target.name]?.source))),
668
+ /**
669
+ * Every enabled entry's accepted canonical package, read from accepted
670
+ * resolution rather than re-resolved from source. Materialization
671
+ * realizes what the workspace already accepted; going back to the
672
+ * source would put an unrelated configured entry's release age between
673
+ * an operator and the extension they are authoring.
674
+ */
675
+ listMaterializable: () => Effect.gen(function* () {
676
+ const nodes = yield* activeKnowledgeNodes();
677
+ const refs = yield* Effect.forEach(nodes, (node) => provide(usableAcceptedCanonicalRef({
678
+ workspace: ws,
679
+ type: "knowledge",
680
+ name: node.name,
681
+ }).pipe(Effect.map(Option.filter((ref) => ref.type === "knowledge")))), { concurrency: "unbounded" });
682
+ return refs.flatMap((ref) => (Option.isSome(ref) ? [ref.value] : []));
683
+ }),
684
+ materializeUninstall,
685
+ materializeDeactivate,
686
+ upsertSettingsEntry: ({ ref, versionRange, materialization }) => buildLockEntry(ref, materialization).pipe(Effect.flatMap((lockEntry) => Option.isSome(lockEntry)
687
+ ? ws.setKnowledge({
688
+ name: ref.knowledge.name,
689
+ lockEntry: lockEntry.value,
690
+ versionRange,
691
+ })
692
+ : setKnowledgeSourceEntry(ref.knowledge.name, "workspace"))),
693
+ removeSettingsEntry: ({ target }) => ws.removeKnowledgeSettings(target.name),
694
+ upsertLockfileEntry: ({ ref, materialization }) => buildLockEntry(ref, materialization).pipe(Effect.flatMap((lockEntry) => {
695
+ if (Option.isNone(lockEntry))
696
+ return ws.removeKnowledgeLock(ref.knowledge.name);
697
+ const validate = lockEntry.value.type === "registry"
698
+ ? validateExactResolvedVersion(`knowledge.${ref.knowledge.name}.resolvedVersion`, lockEntry.value.resolvedVersion)
699
+ : Effect.void;
700
+ return validate.pipe(Effect.flatMap(() => ws.setKnowledgeLock({
701
+ name: ref.knowledge.name,
702
+ lockEntry: lockEntry.value,
703
+ versionRange: Option.none(),
704
+ })));
705
+ })),
706
+ removeLockfileEntry: ({ target }) => ws.removeKnowledgeLock(target.name),
707
+ };
708
+ }));
709
+ //# sourceMappingURL=manager.js.map