@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.
- package/LICENSE +110 -0
- package/README.md +56 -0
- package/dist/src/accepted-pack-dependency-resolver.d.ts +23 -0
- package/dist/src/accepted-pack-dependency-resolver.js +31 -0
- package/dist/src/accepted-pack-hydration.d.ts +33 -0
- package/dist/src/accepted-pack-hydration.js +93 -0
- package/dist/src/axm-skill-candidate.d.ts +16 -0
- package/dist/src/axm-skill-candidate.js +80 -0
- package/dist/src/axm-skill-compatibility.d.ts +87 -0
- package/dist/src/axm-skill-compatibility.js +248 -0
- package/dist/src/axm-skill-workspace-compatibility.d.ts +23 -0
- package/dist/src/axm-skill-workspace-compatibility.js +110 -0
- package/dist/src/configured-entry-resolution.d.ts +118 -0
- package/dist/src/configured-entry-resolution.js +735 -0
- package/dist/src/configured-entry.d.ts +38 -0
- package/dist/src/configured-entry.js +8 -0
- package/dist/src/errors.d.ts +113 -0
- package/dist/src/errors.js +71 -0
- package/dist/src/index.d.ts +34 -0
- package/dist/src/index.js +45 -0
- package/dist/src/live.d.ts +25 -0
- package/dist/src/live.js +47 -0
- package/dist/src/named-registry-resolution.d.ts +26 -0
- package/dist/src/named-registry-resolution.js +110 -0
- package/dist/src/pack-dependency-resolution.d.ts +73 -0
- package/dist/src/pack-dependency-resolution.js +422 -0
- package/dist/src/publisher-binding.d.ts +66 -0
- package/dist/src/publisher-binding.js +75 -0
- package/dist/src/release-age-policy.d.ts +55 -0
- package/dist/src/release-age-policy.js +115 -0
- package/dist/src/release-age-posture.d.ts +23 -0
- package/dist/src/release-age-posture.js +15 -0
- package/dist/src/resolution-timeout.d.ts +11 -0
- package/dist/src/resolution-timeout.js +17 -0
- package/dist/src/resolved-pack-dependency.d.ts +32 -0
- package/dist/src/resolved-pack-dependency.js +28 -0
- package/dist/src/source-authority.d.ts +60 -0
- package/dist/src/source-authority.js +70 -0
- package/dist/src/testing.d.ts +40 -0
- package/dist/src/testing.js +40 -0
- package/dist/src/update/constraint-precedence.d.ts +75 -0
- package/dist/src/update/constraint-precedence.js +99 -0
- package/dist/src/update/targeted-update-context.d.ts +101 -0
- package/dist/src/update/targeted-update-context.js +217 -0
- package/dist/src/version-resolution.d.ts +39 -0
- package/dist/src/version-resolution.js +76 -0
- 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
|