@agentxm/extension-resolution 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 (47) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +56 -0
  3. package/dist/src/accepted-pack-dependency-resolver.d.ts +23 -0
  4. package/dist/src/accepted-pack-dependency-resolver.js +31 -0
  5. package/dist/src/accepted-pack-hydration.d.ts +33 -0
  6. package/dist/src/accepted-pack-hydration.js +93 -0
  7. package/dist/src/axm-skill-candidate.d.ts +16 -0
  8. package/dist/src/axm-skill-candidate.js +80 -0
  9. package/dist/src/axm-skill-compatibility.d.ts +87 -0
  10. package/dist/src/axm-skill-compatibility.js +248 -0
  11. package/dist/src/axm-skill-workspace-compatibility.d.ts +23 -0
  12. package/dist/src/axm-skill-workspace-compatibility.js +110 -0
  13. package/dist/src/configured-entry-resolution.d.ts +118 -0
  14. package/dist/src/configured-entry-resolution.js +735 -0
  15. package/dist/src/configured-entry.d.ts +38 -0
  16. package/dist/src/configured-entry.js +8 -0
  17. package/dist/src/errors.d.ts +113 -0
  18. package/dist/src/errors.js +71 -0
  19. package/dist/src/index.d.ts +34 -0
  20. package/dist/src/index.js +45 -0
  21. package/dist/src/live.d.ts +25 -0
  22. package/dist/src/live.js +47 -0
  23. package/dist/src/named-registry-resolution.d.ts +26 -0
  24. package/dist/src/named-registry-resolution.js +110 -0
  25. package/dist/src/pack-dependency-resolution.d.ts +73 -0
  26. package/dist/src/pack-dependency-resolution.js +422 -0
  27. package/dist/src/publisher-binding.d.ts +66 -0
  28. package/dist/src/publisher-binding.js +75 -0
  29. package/dist/src/release-age-policy.d.ts +55 -0
  30. package/dist/src/release-age-policy.js +115 -0
  31. package/dist/src/release-age-posture.d.ts +23 -0
  32. package/dist/src/release-age-posture.js +15 -0
  33. package/dist/src/resolution-timeout.d.ts +11 -0
  34. package/dist/src/resolution-timeout.js +17 -0
  35. package/dist/src/resolved-pack-dependency.d.ts +32 -0
  36. package/dist/src/resolved-pack-dependency.js +28 -0
  37. package/dist/src/source-authority.d.ts +60 -0
  38. package/dist/src/source-authority.js +70 -0
  39. package/dist/src/testing.d.ts +40 -0
  40. package/dist/src/testing.js +40 -0
  41. package/dist/src/update/constraint-precedence.d.ts +75 -0
  42. package/dist/src/update/constraint-precedence.js +99 -0
  43. package/dist/src/update/targeted-update-context.d.ts +101 -0
  44. package/dist/src/update/targeted-update-context.js +217 -0
  45. package/dist/src/version-resolution.d.ts +39 -0
  46. package/dist/src/version-resolution.js +76 -0
  47. package/package.json +66 -0
@@ -0,0 +1,115 @@
1
+ import * as DateTime from "effect/DateTime";
2
+ import * as Duration from "effect/Duration";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Option from "effect/Option";
5
+ import { matchesReleaseAgeExcludePattern } from "@agentxm/extension-model/unstable/extensions/fqn-pattern";
6
+ export const releaseAgeExemptionForIdentity = (evaluation, identity) => {
7
+ if (evaluation.grantedExemption !== undefined)
8
+ return evaluation.grantedExemption;
9
+ const excluded = evaluation.exclude?.find(({ pattern }) => matchesReleaseAgeExcludePattern(pattern, identity));
10
+ if (excluded !== undefined) {
11
+ return { bypassCause: "exclude", exemptionScope: excluded.scope };
12
+ }
13
+ return evaluation.mode === "ignore" ? { bypassCause: "ignore-flag" } : undefined;
14
+ };
15
+ const releaseAgeRecordKey = (record) => [
16
+ record.target,
17
+ ...record.dependencyPath,
18
+ record.candidateVersion,
19
+ record.selectedVersion ?? "",
20
+ record.currentVersion ?? "",
21
+ record.requestedRange ?? "",
22
+ "bypassCause" in record ? record.bypassCause : "",
23
+ "exemptionScope" in record ? record.exemptionScope : "",
24
+ ].join("\u0000");
25
+ /** Deduplicate and order operation evidence independently from concurrent resolution order. */
26
+ export const normalizeReleaseAgeRecords = (records) => {
27
+ const byKey = new Map();
28
+ for (const record of records) {
29
+ byKey.set(releaseAgeRecordKey(record), record);
30
+ }
31
+ return [...byKey.entries()]
32
+ .sort(([left], [right]) => left.localeCompare(right))
33
+ .map(([, record]) => record);
34
+ };
35
+ /**
36
+ * Documented exception: hand-rolled parser for the compact duration grammar
37
+ * (`24h`, `30d`, `500ms`).
38
+ *
39
+ * Effect v4 has no `Duration.decode`; the closest constructors are
40
+ * `Duration.fromInput` / `Duration.fromInputUnsafe`, and the `Duration.Input`
41
+ * string form only accepts space-separated long-unit strings ("30 days"), so
42
+ * the compact grammar is not expressible upstream. `durationPattern` and
43
+ * `parseMinimumReleaseAge` therefore stay as a thin syntax adapter over the
44
+ * `Duration` constructors (`Duration.millis` / `seconds` / `minutes` /
45
+ * `hours` / `days`).
46
+ *
47
+ * Removal condition: upstream `Duration` accepting the compact unit grammar.
48
+ */
49
+ const durationPattern = /^(\d+)(ms|s|m|h|d)$/;
50
+ export const parseMinimumReleaseAge = (value) => {
51
+ const trimmed = value.trim();
52
+ const match = durationPattern.exec(trimmed);
53
+ if (match === null)
54
+ return Option.none();
55
+ const amountText = match[1];
56
+ const unit = match[2];
57
+ if (amountText === undefined || unit === undefined)
58
+ return Option.none();
59
+ const amount = Number(amountText);
60
+ if (!Number.isSafeInteger(amount))
61
+ return Option.none();
62
+ switch (unit) {
63
+ case "ms":
64
+ return Option.some(Duration.millis(amount));
65
+ case "s":
66
+ return Option.some(Duration.seconds(amount));
67
+ case "m":
68
+ return Option.some(Duration.minutes(amount));
69
+ case "h":
70
+ return Option.some(Duration.hours(amount));
71
+ case "d":
72
+ return Option.some(Duration.days(amount));
73
+ }
74
+ // Unreachable at runtime, but `unit` is typed `string` (a regex capture),
75
+ // so the switch is not statically exhaustive and the compiler (TS2366)
76
+ // requires this ending return.
77
+ return Option.none();
78
+ };
79
+ /**
80
+ * Render a release-age window in the compact grammar the setting accepts, so
81
+ * output names the policy in the same units the reader would configure.
82
+ *
83
+ * Days are used only from two days up: the default window is written `24h`,
84
+ * and reporting it as `1d` would not match the setting the reader would edit.
85
+ * Falls back to whole seconds when the window is not a clean larger unit.
86
+ */
87
+ export const formatMinimumReleaseAgeSeconds = (seconds) => {
88
+ const whole = Math.max(0, Math.round(seconds));
89
+ if (whole === 0)
90
+ return "0s";
91
+ if (whole % 86_400 === 0 && whole >= 172_800)
92
+ return `${whole / 86_400}d`;
93
+ if (whole % 3_600 === 0)
94
+ return `${whole / 3_600}h`;
95
+ if (whole % 60 === 0)
96
+ return `${whole / 60}m`;
97
+ return `${whole}s`;
98
+ };
99
+ export const isVersionEntryMature = (entry, minimumAge) => Duration.isLessThanOrEqualTo(minimumAge, Duration.zero)
100
+ ? Effect.succeed(true)
101
+ : DateTime.now.pipe(Effect.map((now) =>
102
+ // Inclusive at the boundary: a release published exactly `minimumAge`
103
+ // ago is mature, matching the original `now - published >= minimumAge`.
104
+ DateTime.isLessThanOrEqualTo(DateTime.addDuration(entry.published, minimumAge), now)));
105
+ export const isVersionEntryEligibleAt = (entry, evaluation) => Duration.isLessThanOrEqualTo(evaluation.minimumReleaseAge, Duration.zero) ||
106
+ DateTime.isLessThanOrEqualTo(DateTime.addDuration(entry.published, evaluation.minimumReleaseAge), evaluation.evaluatedAt);
107
+ export const releaseAgeEvidence = (entry, evaluation) => ({
108
+ version: entry.version,
109
+ publishedAt: DateTime.formatIso(entry.published),
110
+ eligibleAt: DateTime.formatIso(DateTime.addDuration(entry.published, evaluation.minimumReleaseAge)),
111
+ minimumReleaseAgeSeconds: Math.max(0, Duration.toMillis(evaluation.minimumReleaseAge) / 1_000),
112
+ });
113
+ export const filterMatureVersions = (versions, minimumAge) => Effect.filter(versions, (entry) => isVersionEntryMature(entry, minimumAge));
114
+ export const releaseAgeHoldbackWarning = (args) => `${args.fqn} held at ${args.selectedVersion} — ${args.heldVersion} has not reached the ${args.minimumReleaseAge} minimum release age`;
115
+ //# sourceMappingURL=release-age-policy.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The minimum-release-age gate's posture for one invocation.
3
+ *
4
+ * A leaf cannot invent this value: `makeConfiguredReleaseAgeEvaluation` reads
5
+ * it from context, so every gated code path carries the requirement up to the
6
+ * command boundary that owns the operator's decision. A command is
7
+ * gate-blockable precisely when its execution requests this service, which is
8
+ * what the conformance specification reads instead of a hand-copied list.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as ServiceMap from "effect/Context";
13
+ /**
14
+ * `"enforce"` withholds a candidate that has not reached the configured
15
+ * minimum release age; `"ignore"` takes it for this run only. Neither value
16
+ * changes settings, and a declared exemption outranks both.
17
+ */
18
+ export type ReleaseAgePostureValue = "enforce" | "ignore";
19
+ declare const ReleaseAgePosture_base: ServiceMap.ServiceClass<ReleaseAgePosture, "axm.sh/extension-resolution/ReleaseAgePosture", ReleaseAgePostureValue>;
20
+ export declare class ReleaseAgePosture extends ReleaseAgePosture_base {
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=release-age-posture.d.ts.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The minimum-release-age gate's posture for one invocation.
3
+ *
4
+ * A leaf cannot invent this value: `makeConfiguredReleaseAgeEvaluation` reads
5
+ * it from context, so every gated code path carries the requirement up to the
6
+ * command boundary that owns the operator's decision. A command is
7
+ * gate-blockable precisely when its execution requests this service, which is
8
+ * what the conformance specification reads instead of a hand-copied list.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as ServiceMap from "effect/Context";
13
+ export class ReleaseAgePosture extends ServiceMap.Service()("axm.sh/extension-resolution/ReleaseAgePosture") {
14
+ }
15
+ //# sourceMappingURL=release-age-posture.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The bound on how long resolving one configured entry's source may take
3
+ * before the operation reports a network failure instead of waiting.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ */
7
+ import * as Effect from "effect/Effect";
8
+ import { ExtensionResolutionFailed } from "./errors.js";
9
+ export declare const CONFIGURED_ENTRY_RESOLUTION_TIMEOUT = "2 seconds";
10
+ export declare const withConfiguredEntryResolutionTimeout: (_source: string) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, ExtensionResolutionFailed | E, R>;
11
+ //# sourceMappingURL=resolution-timeout.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The bound on how long resolving one configured entry's source may take
3
+ * before the operation reports a network failure instead of waiting.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ */
7
+ import * as Effect from "effect/Effect";
8
+ import { ExtensionResolutionFailed } from "./errors.js";
9
+ export const CONFIGURED_ENTRY_RESOLUTION_TIMEOUT = "2 seconds";
10
+ export const withConfiguredEntryResolutionTimeout = (_source) => (effect) => effect.pipe(Effect.timeoutOrElse({
11
+ duration: CONFIGURED_ENTRY_RESOLUTION_TIMEOUT,
12
+ orElse: () => Effect.fail(new ExtensionResolutionFailed({
13
+ category: "network",
14
+ detail: "Timed out while resolving a configured extension source",
15
+ })),
16
+ }));
17
+ //# sourceMappingURL=resolution-timeout.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The resolved pack dependency map: the exact identity and version resolution
3
+ * chose for every declared pack member, in the shape the lockfile records.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ */
7
+ import * as Schema from "effect/Schema";
8
+ export declare const ResolvedPackDependencySchema: Schema.Union<readonly [Schema.Struct<{
9
+ readonly source: Schema.Literal<"registry">;
10
+ readonly version: Schema.brand<Schema.String, "Version">;
11
+ readonly publisherBindingId: Schema.NonEmptyString;
12
+ readonly integrity: Schema.String;
13
+ }>, Schema.Struct<{
14
+ readonly source: Schema.Literal<"workspace">;
15
+ readonly version: Schema.brand<Schema.String, "Version">;
16
+ readonly sourceIdentity: Schema.String;
17
+ readonly contentIdentity: Schema.brand<Schema.String, "SourceHash">;
18
+ }>]>;
19
+ export type ResolvedPackDependency = typeof ResolvedPackDependencySchema.Type;
20
+ export declare const ResolvedPackDependencyMapSchema: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.Struct<{
21
+ readonly source: Schema.Literal<"registry">;
22
+ readonly version: Schema.brand<Schema.String, "Version">;
23
+ readonly publisherBindingId: Schema.NonEmptyString;
24
+ readonly integrity: Schema.String;
25
+ }>, Schema.Struct<{
26
+ readonly source: Schema.Literal<"workspace">;
27
+ readonly version: Schema.brand<Schema.String, "Version">;
28
+ readonly sourceIdentity: Schema.String;
29
+ readonly contentIdentity: Schema.brand<Schema.String, "SourceHash">;
30
+ }>]>>;
31
+ export type ResolvedPackDependencyMap = typeof ResolvedPackDependencyMapSchema.Type;
32
+ //# sourceMappingURL=resolved-pack-dependency.d.ts.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The resolved pack dependency map: the exact identity and version resolution
3
+ * chose for every declared pack member, in the shape the lockfile records.
4
+ *
5
+ * @experimental This API is unstable and may change without notice.
6
+ */
7
+ import * as Schema from "effect/Schema";
8
+ import { ExtensionFqnSchema } from "@agentxm/extension-model/unstable/extensions";
9
+ import { SourceHashSchema } from "@agentxm/extension-model/unstable/sources/source-hash";
10
+ import { VersionSchema } from "@agentxm/extension-model/unstable/version-constraints";
11
+ const ResolvedRegistryDependencySchema = Schema.Struct({
12
+ source: Schema.Literal("registry"),
13
+ version: VersionSchema,
14
+ publisherBindingId: Schema.NonEmptyString,
15
+ integrity: Schema.String,
16
+ });
17
+ const ResolvedWorkspaceDependencySchema = Schema.Struct({
18
+ source: Schema.Literal("workspace"),
19
+ version: VersionSchema,
20
+ sourceIdentity: Schema.String,
21
+ contentIdentity: SourceHashSchema,
22
+ });
23
+ export const ResolvedPackDependencySchema = Schema.Union([
24
+ ResolvedRegistryDependencySchema,
25
+ ResolvedWorkspaceDependencySchema,
26
+ ]);
27
+ export const ResolvedPackDependencyMapSchema = Schema.Record(ExtensionFqnSchema, ResolvedPackDependencySchema);
28
+ //# sourceMappingURL=resolved-pack-dependency.js.map
@@ -0,0 +1,60 @@
1
+ import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
2
+ export type SourceAuthorityRelationship = {
3
+ readonly kind: "root";
4
+ } | {
5
+ readonly kind: "member";
6
+ readonly root: string;
7
+ };
8
+ export type WorkspaceAuthorityStatus = "usable" | "missing" | "constraint-mismatch" | "wrong-origin" | "corrupt" | "incomplete" | "locally-modified" | "not-applicable";
9
+ export interface SourceAuthorityTarget {
10
+ readonly type: ExtensionType;
11
+ readonly name: string;
12
+ readonly identity: string;
13
+ }
14
+ export interface SourceAuthorityInput {
15
+ readonly target: SourceAuthorityTarget;
16
+ readonly relationship: SourceAuthorityRelationship;
17
+ readonly requested: {
18
+ readonly identity: string;
19
+ readonly workspace: boolean;
20
+ };
21
+ readonly configured?: {
22
+ readonly identity: string;
23
+ readonly workspace: boolean;
24
+ readonly version?: string;
25
+ readonly status?: WorkspaceAuthorityStatus;
26
+ };
27
+ readonly requiredVersionRange?: string;
28
+ readonly allowWorkspaceReplacement?: boolean;
29
+ }
30
+ export type SourceAuthorityBlockedCause = "workspace-source-replacement" | "workspace-identity-mismatch" | "workspace-version-incompatible" | "workspace-unusable";
31
+ export interface SourceAuthorityBlockedFact {
32
+ readonly id: string;
33
+ readonly target: SourceAuthorityTarget;
34
+ readonly relationship: SourceAuthorityRelationship;
35
+ readonly requestedSource: string;
36
+ readonly configuredSource: string;
37
+ readonly cause: SourceAuthorityBlockedCause;
38
+ readonly detail: string;
39
+ readonly workspaceVersion?: string;
40
+ readonly requiredVersionRange?: string;
41
+ readonly recovery: ReadonlyArray<{
42
+ readonly description: string;
43
+ }>;
44
+ }
45
+ export type SourceAuthorityDecision = {
46
+ readonly kind: "allow-requested";
47
+ } | {
48
+ readonly kind: "workspace-satisfied";
49
+ readonly target: SourceAuthorityTarget;
50
+ readonly relationship: Extract<SourceAuthorityRelationship, {
51
+ readonly kind: "member";
52
+ }>;
53
+ readonly configuredSource: string;
54
+ readonly workspaceVersion?: string;
55
+ } | {
56
+ readonly kind: "blocked";
57
+ readonly fact: SourceAuthorityBlockedFact;
58
+ };
59
+ export declare const evaluateSourceAuthority: (input: SourceAuthorityInput) => SourceAuthorityDecision;
60
+ //# sourceMappingURL=source-authority.d.ts.map
@@ -0,0 +1,70 @@
1
+ import * as semver from "semver";
2
+ const blocked = (input, configured, cause, detail, recovery) => ({
3
+ kind: "blocked",
4
+ fact: {
5
+ id: `workspace-authority:${input.relationship.kind}:${input.target.identity}:${cause}`,
6
+ target: input.target,
7
+ relationship: input.relationship,
8
+ requestedSource: input.requested.identity,
9
+ configuredSource: configured.identity,
10
+ cause,
11
+ detail,
12
+ ...(configured.version === undefined ? {} : { workspaceVersion: configured.version }),
13
+ ...(input.requiredVersionRange === undefined
14
+ ? {}
15
+ : { requiredVersionRange: input.requiredVersionRange }),
16
+ recovery,
17
+ },
18
+ });
19
+ export const evaluateSourceAuthority = (input) => {
20
+ const configured = input.configured;
21
+ if (configured === undefined ||
22
+ !configured.workspace ||
23
+ input.requested.workspace ||
24
+ input.allowWorkspaceReplacement === true) {
25
+ return { kind: "allow-requested" };
26
+ }
27
+ if (input.relationship.kind === "root") {
28
+ return blocked(input, configured, "workspace-source-replacement", `Cannot install over workspace-sourced ${input.target.type} "${input.target.name}" with ${input.requested.identity}`, [
29
+ {
30
+ description: "Preserve the workspace source, or explicitly transition its authority before installing a different source.",
31
+ },
32
+ ]);
33
+ }
34
+ const configuredIdentity = configured.identity.startsWith("workspace:")
35
+ ? configured.identity.slice("workspace:".length)
36
+ : configured.identity;
37
+ if (configuredIdentity !== input.target.identity) {
38
+ return blocked(input, configured, "workspace-identity-mismatch", `Workspace dependency ${configuredIdentity} does not match required ${input.target.identity}`, [
39
+ {
40
+ description: "Rename or remove the conflicting workspace dependency, or explicitly transition its authority.",
41
+ },
42
+ ]);
43
+ }
44
+ if (configured.status !== undefined &&
45
+ configured.status !== "usable" &&
46
+ configured.status !== "constraint-mismatch") {
47
+ return blocked(input, configured, "workspace-unusable", `Workspace dependency ${input.target.identity} is ${configured.status}`, [
48
+ {
49
+ description: `Repair or explicitly remove the ${configured.status} workspace dependency before installing the pack.`,
50
+ },
51
+ ]);
52
+ }
53
+ if (input.requiredVersionRange !== undefined &&
54
+ (configured.version === undefined ||
55
+ !semver.satisfies(configured.version, input.requiredVersionRange))) {
56
+ return blocked(input, configured, "workspace-version-incompatible", `Workspace dependency ${input.target.identity}@${configured.version ?? "unknown"} does not satisfy ${input.requiredVersionRange}`, [
57
+ {
58
+ description: `Update the workspace dependency to satisfy ${input.requiredVersionRange}, or explicitly transition its authority.`,
59
+ },
60
+ ]);
61
+ }
62
+ return {
63
+ kind: "workspace-satisfied",
64
+ target: input.target,
65
+ relationship: input.relationship,
66
+ configuredSource: configured.identity,
67
+ ...(configured.version === undefined ? {} : { workspaceVersion: configured.version }),
68
+ };
69
+ };
70
+ //# sourceMappingURL=source-authority.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @agentxm/extension-resolution deterministic test ports and decoders.
3
+ *
4
+ * The two services a resolution keeps in `R` until its command boundary — the
5
+ * minimum-release-age posture the operator decides, and the official AXM
6
+ * skill compatibility policy — bound to fixed values, plus the branded-value
7
+ * decoders a fixture needs to state a handle, name, version, or range.
8
+ * Production source never imports this module.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ * @packageDocumentation
12
+ */
13
+ import * as Layer from "effect/Layer";
14
+ import { type ExtensionName } from "@agentxm/extension-model/unstable/extensions/common";
15
+ import { type Handle } from "@agentxm/extension-model/unstable/extensions/handle";
16
+ import { type Version, type VersionRange } from "@agentxm/extension-model/unstable/version-constraints";
17
+ import { AxmSkillCompatibilityPolicy } from "./axm-skill-compatibility.js";
18
+ import { ReleaseAgePosture, type ReleaseAgePostureValue } from "./release-age-posture.js";
19
+ /**
20
+ * The release-age gate's posture for one run. `"enforce"` withholds a
21
+ * candidate that has not reached the configured minimum release age, which is
22
+ * the product's default; pass `"ignore"` to model the operator taking it for
23
+ * this run only.
24
+ */
25
+ export declare const ReleaseAgePostureTest: (posture?: ReleaseAgePostureValue) => Layer.Layer<ReleaseAgePosture>;
26
+ /**
27
+ * The real official-skill compatibility policy, evaluated against a stated CLI
28
+ * version rather than the running executable's. `null` models an executable
29
+ * that reports no version, which the policy must still answer for.
30
+ */
31
+ export declare const AxmSkillCompatibilityPolicyTest: (cliVersion: string | null) => Layer.Layer<AxmSkillCompatibilityPolicy>;
32
+ /** The handle a fixture names, decoded into its branded form. */
33
+ export declare const handle: (value: string) => Handle;
34
+ /** The extension name a fixture names, decoded into its branded form. */
35
+ export declare const extensionName: (value: string) => ExtensionName;
36
+ /** One exact version, decoded into its branded form. */
37
+ export declare const exactVersion: (value: string) => Version;
38
+ /** One version range, decoded into its branded form. */
39
+ export declare const versionRange: (value: string) => VersionRange;
40
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @agentxm/extension-resolution deterministic test ports and decoders.
3
+ *
4
+ * The two services a resolution keeps in `R` until its command boundary — the
5
+ * minimum-release-age posture the operator decides, and the official AXM
6
+ * skill compatibility policy — bound to fixed values, plus the branded-value
7
+ * decoders a fixture needs to state a handle, name, version, or range.
8
+ * Production source never imports this module.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ * @packageDocumentation
12
+ */
13
+ import * as Layer from "effect/Layer";
14
+ import { decodeExtensionNameSync, } from "@agentxm/extension-model/unstable/extensions/common";
15
+ import { decodeHandleSync } from "@agentxm/extension-model/unstable/extensions/handle";
16
+ import { decodeVersionRangeSync, decodeVersionSync, } from "@agentxm/extension-model/unstable/version-constraints";
17
+ import { AxmSkillCompatibilityPolicy, makeAxmSkillCompatibilityPolicyLayer, } from "./axm-skill-compatibility.js";
18
+ import { ReleaseAgePosture } from "./release-age-posture.js";
19
+ /**
20
+ * The release-age gate's posture for one run. `"enforce"` withholds a
21
+ * candidate that has not reached the configured minimum release age, which is
22
+ * the product's default; pass `"ignore"` to model the operator taking it for
23
+ * this run only.
24
+ */
25
+ export const ReleaseAgePostureTest = (posture = "enforce") => Layer.succeed(ReleaseAgePosture, posture);
26
+ /**
27
+ * The real official-skill compatibility policy, evaluated against a stated CLI
28
+ * version rather than the running executable's. `null` models an executable
29
+ * that reports no version, which the policy must still answer for.
30
+ */
31
+ export const AxmSkillCompatibilityPolicyTest = (cliVersion) => makeAxmSkillCompatibilityPolicyLayer(cliVersion);
32
+ /** The handle a fixture names, decoded into its branded form. */
33
+ export const handle = (value) => decodeHandleSync(value);
34
+ /** The extension name a fixture names, decoded into its branded form. */
35
+ export const extensionName = (value) => decodeExtensionNameSync(value);
36
+ /** One exact version, decoded into its branded form. */
37
+ export const exactVersion = (value) => decodeVersionSync(value);
38
+ /** One version range, decoded into its branded form. */
39
+ export const versionRange = (value) => decodeVersionRangeSync(value);
40
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Which constraint governs an update when several declare one.
3
+ *
4
+ * A workspace can name a version range directly, and every Pack that owns the
5
+ * extension names one too. The rule is precedence, not intersection: an
6
+ * explicit workspace constraint is the intent a person expressed and it wins
7
+ * outright; a wildcard or absent workspace constraint defers to the Packs and
8
+ * intersects theirs. Where the newest published release is outside whichever
9
+ * constraint governs, the resolution says so — a held-back version is a fact
10
+ * a person is owed, not a silent omission.
11
+ *
12
+ * Pure over the visible version list; who supplies that list is the caller's.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ import * as Option from "effect/Option";
17
+ /**
18
+ * A pack's constraint on a skill version.
19
+ */
20
+ export interface PackConstraint {
21
+ readonly packName: string;
22
+ readonly constraint: string;
23
+ }
24
+ /**
25
+ * Collected constraints for a single skill.
26
+ */
27
+ export interface UpdateConstraints {
28
+ /** User's version constraint from settings source string. None = no constraint (treat as "*"). */
29
+ readonly userConstraint: Option.Option<string>;
30
+ /** Pack constraints from installed pack manifests. */
31
+ readonly packConstraints: ReadonlyArray<PackConstraint>;
32
+ }
33
+ /**
34
+ * Result of constraint resolution for a skill.
35
+ */
36
+ export interface ConstraintResolutionResult {
37
+ /** The resolved version string. */
38
+ readonly resolvedVersion: string;
39
+ /** Warnings generated during resolution. */
40
+ readonly warnings: ReadonlyArray<string>;
41
+ }
42
+ /**
43
+ * Resolve the best version for a skill given its constraints and available versions.
44
+ *
45
+ * Algorithm:
46
+ * 1. If user has explicit constraint (not "*"):
47
+ * - Find first version (newest-first) satisfying user constraint
48
+ * - If none found → return Option.none() (caller should fail)
49
+ * 2. If user has "*" or no constraint:
50
+ * - Collect all pack constraints
51
+ * - For each candidate (newest first): check all pack constraints
52
+ * - First satisfying all → use it
53
+ * - If none satisfies all → use newest, warn about each unsatisfied constraint
54
+ *
55
+ * @param versions - Available versions, newest first
56
+ * @param constraints - Collected constraints for the skill
57
+ * @param skillName - Skill name for warning messages
58
+ * @returns Option.some with result if resolved, Option.none if user constraint unsatisfiable
59
+ */
60
+ export declare const resolveConstrainedVersion: (versions: ReadonlyArray<string>, constraints: UpdateConstraints, skillName: string) => Option.Option<ConstraintResolutionResult>;
61
+ /**
62
+ * Detect when a pack constraint holds back a user-installed "*" skill.
63
+ *
64
+ * Emits a warning when:
65
+ * - User has skill with no constraint (wants latest)
66
+ * - Pack constraint resolves to a version below the latest available
67
+ *
68
+ * @param latestVersion - The latest available version
69
+ * @param resolvedVersion - The version that was resolved after constraint application
70
+ * @param constraints - The skill's constraints
71
+ * @param skillName - Skill name for warning messages
72
+ * @returns Warning messages (empty if no holdback detected)
73
+ */
74
+ export declare const detectHoldbackWarnings: (latestVersion: string, resolvedVersion: string, constraints: UpdateConstraints, skillName: string) => ReadonlyArray<string>;
75
+ //# sourceMappingURL=constraint-precedence.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Which constraint governs an update when several declare one.
3
+ *
4
+ * A workspace can name a version range directly, and every Pack that owns the
5
+ * extension names one too. The rule is precedence, not intersection: an
6
+ * explicit workspace constraint is the intent a person expressed and it wins
7
+ * outright; a wildcard or absent workspace constraint defers to the Packs and
8
+ * intersects theirs. Where the newest published release is outside whichever
9
+ * constraint governs, the resolution says so — a held-back version is a fact
10
+ * a person is owed, not a silent omission.
11
+ *
12
+ * Pure over the visible version list; who supplies that list is the caller's.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ import * as Option from "effect/Option";
17
+ import { decodeVersionSync, decodeVersionRangeSync, versionSatisfiesRange, } from "@agentxm/extension-model/unstable/version-constraints";
18
+ // -----------------------------------------------------------------------------
19
+ // Constraint Resolution
20
+ // -----------------------------------------------------------------------------
21
+ /**
22
+ * Resolve the best version for a skill given its constraints and available versions.
23
+ *
24
+ * Algorithm:
25
+ * 1. If user has explicit constraint (not "*"):
26
+ * - Find first version (newest-first) satisfying user constraint
27
+ * - If none found → return Option.none() (caller should fail)
28
+ * 2. If user has "*" or no constraint:
29
+ * - Collect all pack constraints
30
+ * - For each candidate (newest first): check all pack constraints
31
+ * - First satisfying all → use it
32
+ * - If none satisfies all → use newest, warn about each unsatisfied constraint
33
+ *
34
+ * @param versions - Available versions, newest first
35
+ * @param constraints - Collected constraints for the skill
36
+ * @param skillName - Skill name for warning messages
37
+ * @returns Option.some with result if resolved, Option.none if user constraint unsatisfiable
38
+ */
39
+ export const resolveConstrainedVersion = (versions, constraints, skillName) => {
40
+ const [newest] = versions;
41
+ if (newest === undefined)
42
+ return Option.none();
43
+ const userConstraintStr = Option.getOrElse(constraints.userConstraint, () => "*");
44
+ const isWildcard = userConstraintStr === "*";
45
+ // Case 1: User has explicit constraint
46
+ if (!isWildcard) {
47
+ const userConstraint = decodeVersionRangeSync(userConstraintStr);
48
+ const matched = versions.find((v) => versionSatisfiesRange(decodeVersionSync(v), userConstraint));
49
+ if (matched === undefined)
50
+ return Option.none();
51
+ return Option.some({ resolvedVersion: matched, warnings: [] });
52
+ }
53
+ // Case 2: User has wildcard — apply pack constraints
54
+ if (constraints.packConstraints.length === 0) {
55
+ // No pack constraints — use newest
56
+ return Option.some({ resolvedVersion: newest, warnings: [] });
57
+ }
58
+ // Try each version (newest first) against all pack constraints
59
+ for (const version of versions) {
60
+ const allSatisfied = constraints.packConstraints.every((pc) => versionSatisfiesRange(decodeVersionSync(version), decodeVersionRangeSync(pc.constraint)));
61
+ if (allSatisfied) {
62
+ return Option.some({ resolvedVersion: version, warnings: [] });
63
+ }
64
+ }
65
+ // No version satisfies all pack constraints — use newest, warn
66
+ const warnings = constraints.packConstraints
67
+ .filter((pc) => !versionSatisfiesRange(decodeVersionSync(newest), decodeVersionRangeSync(pc.constraint)))
68
+ .map((pc) => `${skillName} held at ${newest} by pack "${pc.packName}" (${pc.constraint})`);
69
+ return Option.some({ resolvedVersion: newest, warnings });
70
+ };
71
+ /**
72
+ * Detect when a pack constraint holds back a user-installed "*" skill.
73
+ *
74
+ * Emits a warning when:
75
+ * - User has skill with no constraint (wants latest)
76
+ * - Pack constraint resolves to a version below the latest available
77
+ *
78
+ * @param latestVersion - The latest available version
79
+ * @param resolvedVersion - The version that was resolved after constraint application
80
+ * @param constraints - The skill's constraints
81
+ * @param skillName - Skill name for warning messages
82
+ * @returns Warning messages (empty if no holdback detected)
83
+ */
84
+ export const detectHoldbackWarnings = (latestVersion, resolvedVersion, constraints, skillName) => {
85
+ const userConstraintStr = Option.getOrElse(constraints.userConstraint, () => "*");
86
+ // Only warn for wildcard users (wanting latest)
87
+ if (userConstraintStr !== "*")
88
+ return [];
89
+ // No holdback if resolved to latest
90
+ if (resolvedVersion === latestVersion)
91
+ return [];
92
+ // No pack constraints means no holdback
93
+ if (constraints.packConstraints.length === 0)
94
+ return [];
95
+ return constraints.packConstraints
96
+ .filter((pc) => !versionSatisfiesRange(decodeVersionSync(latestVersion), decodeVersionRangeSync(pc.constraint)))
97
+ .map((pc) => `${skillName} held at ${resolvedVersion} by pack "${pc.packName}" (${pc.constraint}), latest is ${latestVersion}`);
98
+ };
99
+ //# sourceMappingURL=constraint-precedence.js.map