@agentxm/extension-publish 0.28.4-bootstrap.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/dist/src/archive.d.ts +72 -0
- package/dist/src/archive.js +106 -0
- package/dist/src/authorization.d.ts +42 -0
- package/dist/src/authorization.js +42 -0
- package/dist/src/errors.d.ts +30 -0
- package/dist/src/errors.js +29 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/internal/glob.d.ts +39 -0
- package/dist/src/internal/glob.js +79 -0
- package/dist/src/jobs.d.ts +12 -0
- package/dist/src/jobs.js +20 -0
- package/dist/src/lint-gate.d.ts +47 -0
- package/dist/src/lint-gate.js +136 -0
- package/dist/src/preflight.d.ts +46 -0
- package/dist/src/preflight.js +125 -0
- package/dist/src/publish-ignore.d.ts +50 -0
- package/dist/src/publish-ignore.js +61 -0
- package/dist/src/publishable-types.d.ts +21 -0
- package/dist/src/publishable-types.js +15 -0
- package/dist/src/recovery.d.ts +11 -0
- package/dist/src/recovery.js +15 -0
- package/dist/src/settlement.d.ts +19 -0
- package/dist/src/settlement.js +70 -0
- package/package.json +54 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
3
|
+
import type * as Path from "effect/Path";
|
|
4
|
+
import { PublishFailed } from "./errors.js";
|
|
5
|
+
interface PublishLintPlatform {
|
|
6
|
+
readonly fs: FileSystem.FileSystem;
|
|
7
|
+
readonly path: Path.Path;
|
|
8
|
+
}
|
|
9
|
+
export type PublishLintArgs = {
|
|
10
|
+
readonly type: "skill";
|
|
11
|
+
readonly extensionDir: string;
|
|
12
|
+
readonly manifestJson: unknown;
|
|
13
|
+
readonly platform: PublishLintPlatform;
|
|
14
|
+
} | {
|
|
15
|
+
readonly type: "pack";
|
|
16
|
+
readonly extensionDir: string;
|
|
17
|
+
readonly manifestJson: unknown;
|
|
18
|
+
readonly platform: PublishLintPlatform;
|
|
19
|
+
} | {
|
|
20
|
+
readonly type: "subagent";
|
|
21
|
+
readonly extensionDir: string;
|
|
22
|
+
readonly manifestJson: unknown;
|
|
23
|
+
readonly platform: PublishLintPlatform;
|
|
24
|
+
} | {
|
|
25
|
+
readonly type: "mcp-server";
|
|
26
|
+
readonly extensionDir: string;
|
|
27
|
+
readonly manifestJson: unknown;
|
|
28
|
+
readonly platform: PublishLintPlatform;
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: "hook";
|
|
31
|
+
readonly extensionDir: string;
|
|
32
|
+
readonly manifestJson: unknown;
|
|
33
|
+
readonly platform: PublishLintPlatform;
|
|
34
|
+
} | {
|
|
35
|
+
readonly type: "rule";
|
|
36
|
+
readonly extensionDir: string;
|
|
37
|
+
readonly manifestJson: unknown;
|
|
38
|
+
readonly platform: PublishLintPlatform;
|
|
39
|
+
} | {
|
|
40
|
+
readonly type: "knowledge";
|
|
41
|
+
readonly extensionDir: string;
|
|
42
|
+
readonly manifestJson: unknown;
|
|
43
|
+
readonly platform: PublishLintPlatform;
|
|
44
|
+
};
|
|
45
|
+
export declare const runPublishLintGate: (args: PublishLintArgs) => Effect.Effect<void, PublishFailed>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=lint-gate.d.ts.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { PublishFailed } from "./errors.js";
|
|
3
|
+
import { makePlatformPackFileAccessor } from "@agentxm/extension-workspace";
|
|
4
|
+
import { makePlatformSkillFileAccessor } from "@agentxm/extension-workspace";
|
|
5
|
+
import { platformCanonicalLintConfig } from "@agentxm/registry-protocol/unstable/lint/config";
|
|
6
|
+
import { composePath } from "@agentxm/registry-protocol/unstable/lint/compose-path";
|
|
7
|
+
import { evaluateContexts } from "@agentxm/registry-protocol/unstable/lint/evaluate";
|
|
8
|
+
import { hookRules, knowledgeRules, mcpServerRules, packRules, skillRules, ruleRules, subagentRules, } from "@agentxm/registry-protocol/unstable/lint/publish";
|
|
9
|
+
const manifestName = (manifestJson) => {
|
|
10
|
+
if (manifestJson === null || typeof manifestJson !== "object" || Array.isArray(manifestJson)) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const descriptor = Object.getOwnPropertyDescriptor(manifestJson, "name");
|
|
14
|
+
return typeof descriptor?.value === "string" ? descriptor.value : undefined;
|
|
15
|
+
};
|
|
16
|
+
export const runPublishLintGate = (args) => {
|
|
17
|
+
switch (args.type) {
|
|
18
|
+
case "skill":
|
|
19
|
+
return evaluateSkill(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
20
|
+
case "pack":
|
|
21
|
+
return evaluatePack(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
22
|
+
case "subagent":
|
|
23
|
+
return evaluateSubagent(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
24
|
+
case "mcp-server":
|
|
25
|
+
return evaluateMcpServer(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
26
|
+
case "hook":
|
|
27
|
+
return evaluateHook(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
28
|
+
case "knowledge":
|
|
29
|
+
return evaluateKnowledge(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
30
|
+
case "rule":
|
|
31
|
+
return evaluateRule(args).pipe(Effect.flatMap(failOnErrorFindings(args.type)));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const evaluateSkill = (args) => {
|
|
35
|
+
const packageFiles = makePlatformSkillFileAccessor(args.platform, args.extensionDir);
|
|
36
|
+
const files = makePlatformSkillFileAccessor(args.platform, args.platform.path.join(args.extensionDir, "src"));
|
|
37
|
+
const expectedName = manifestName(args.manifestJson);
|
|
38
|
+
const context = {
|
|
39
|
+
subject: {
|
|
40
|
+
isNative: true,
|
|
41
|
+
skillJson: args.manifestJson,
|
|
42
|
+
...(expectedName === undefined ? {} : { expectedName }),
|
|
43
|
+
},
|
|
44
|
+
files,
|
|
45
|
+
packageFiles,
|
|
46
|
+
displayRoot: "",
|
|
47
|
+
};
|
|
48
|
+
return evaluateContexts(skillRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
49
|
+
};
|
|
50
|
+
const evaluatePack = (args) => {
|
|
51
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
52
|
+
const context = {
|
|
53
|
+
subject: { packJson: args.manifestJson },
|
|
54
|
+
files,
|
|
55
|
+
displayRoot: "",
|
|
56
|
+
};
|
|
57
|
+
return evaluateContexts(packRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
58
|
+
};
|
|
59
|
+
const evaluateSubagent = (args) => {
|
|
60
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
61
|
+
const context = {
|
|
62
|
+
subject: { subagentJson: args.manifestJson },
|
|
63
|
+
files,
|
|
64
|
+
displayRoot: "",
|
|
65
|
+
};
|
|
66
|
+
return evaluateContexts(subagentRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
67
|
+
};
|
|
68
|
+
const evaluateMcpServer = (args) => {
|
|
69
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
70
|
+
const context = {
|
|
71
|
+
subject: { mcpServerJson: args.manifestJson },
|
|
72
|
+
files,
|
|
73
|
+
displayRoot: "",
|
|
74
|
+
};
|
|
75
|
+
return evaluateContexts(mcpServerRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
76
|
+
};
|
|
77
|
+
const evaluateHook = (args) => {
|
|
78
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
79
|
+
const context = {
|
|
80
|
+
subject: { hookJson: args.manifestJson },
|
|
81
|
+
files,
|
|
82
|
+
displayRoot: "",
|
|
83
|
+
};
|
|
84
|
+
return evaluateContexts(hookRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
85
|
+
};
|
|
86
|
+
const evaluateRule = (args) => {
|
|
87
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
88
|
+
const context = {
|
|
89
|
+
subject: { ruleJson: args.manifestJson },
|
|
90
|
+
files,
|
|
91
|
+
displayRoot: "",
|
|
92
|
+
};
|
|
93
|
+
return evaluateContexts(ruleRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
94
|
+
};
|
|
95
|
+
const evaluateKnowledge = (args) => {
|
|
96
|
+
const files = makePlatformPackFileAccessor(args.platform, args.extensionDir);
|
|
97
|
+
const context = {
|
|
98
|
+
subject: { knowledgeJson: args.manifestJson },
|
|
99
|
+
files,
|
|
100
|
+
displayRoot: "",
|
|
101
|
+
};
|
|
102
|
+
return evaluateContexts(knowledgeRules, [context], platformCanonicalLintConfig).pipe(Effect.map((evaluated) => collectErrors(evaluated, (ctx) => ctx.displayRoot)));
|
|
103
|
+
};
|
|
104
|
+
const collectErrors = (evaluated, displayRoot) => {
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const entry of evaluated) {
|
|
107
|
+
for (const finding of entry.findings) {
|
|
108
|
+
if (finding.severity !== "error") {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
out.push({
|
|
112
|
+
path: composePath(displayRoot(entry.context), finding.location),
|
|
113
|
+
finding,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
};
|
|
119
|
+
const failOnErrorFindings = (type) => (findings) => {
|
|
120
|
+
if (findings.length === 0) {
|
|
121
|
+
return Effect.void;
|
|
122
|
+
}
|
|
123
|
+
return Effect.fail(new PublishFailed({
|
|
124
|
+
category: "validation",
|
|
125
|
+
detail: renderPublishLintFailure(type, findings),
|
|
126
|
+
}));
|
|
127
|
+
};
|
|
128
|
+
const renderPublishLintFailure = (type, findings) => {
|
|
129
|
+
const noun = type === "mcp-server" ? "MCP server" : type;
|
|
130
|
+
const lines = findings.map(({ path, finding }) => `- ${path} [${finding.ruleId}]: ${finding.message}`);
|
|
131
|
+
return [
|
|
132
|
+
`Publish lint failed for ${noun} manifest with ${findings.length} error${findings.length === 1 ? "" : "s"}.`,
|
|
133
|
+
...lines,
|
|
134
|
+
].join("\n");
|
|
135
|
+
};
|
|
136
|
+
//# sourceMappingURL=lint-gate.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { type Handle } from "@agentxm/extension-model/unstable/extensions";
|
|
3
|
+
import type { Version } from "@agentxm/extension-model/unstable/version-constraints";
|
|
4
|
+
import { type ExtensionConstraintInvariantFact, type PackDependencyReachability } from "@agentxm/extension-workspace";
|
|
5
|
+
import type { PublicationPackResult } from "@agentxm/registry-protocol/unstable/registry";
|
|
6
|
+
import type { RegistryClient, RegistryClientFailure } from "@agentxm/registry-client";
|
|
7
|
+
import { PublishFailed } from "./errors.js";
|
|
8
|
+
import type { PublishableType } from "./publishable-types.js";
|
|
9
|
+
export declare const alreadyPublishedVersionConflict: (args: {
|
|
10
|
+
readonly fqn: string;
|
|
11
|
+
readonly version: Version;
|
|
12
|
+
}) => PublishFailed;
|
|
13
|
+
export declare const nonMonotonicVersionConflict: (args: {
|
|
14
|
+
readonly fqn: string;
|
|
15
|
+
readonly version: Version;
|
|
16
|
+
readonly highestPublished: Version;
|
|
17
|
+
}) => PublishFailed;
|
|
18
|
+
/** Every publish owner must already exist before any candidate uploads. */
|
|
19
|
+
export declare const validatePublishOwners: (owners: ReadonlyArray<Handle>, client: Pick<RegistryClient, "ownerExists">) => Effect.Effect<void, PublishFailed | RegistryClientFailure>;
|
|
20
|
+
/** One suggested follow-up carried by a publish advisory finding. */
|
|
21
|
+
export interface PublishAdvisorySuggestion {
|
|
22
|
+
readonly description: string;
|
|
23
|
+
readonly cmd?: string;
|
|
24
|
+
readonly url?: string;
|
|
25
|
+
}
|
|
26
|
+
/** A non-gating structured warning observed during publication. */
|
|
27
|
+
export interface PublishAdvisoryFinding {
|
|
28
|
+
readonly ruleId: string;
|
|
29
|
+
readonly severity: "warning";
|
|
30
|
+
readonly message: string;
|
|
31
|
+
readonly suggestions: ReadonlyArray<PublishAdvisorySuggestion>;
|
|
32
|
+
}
|
|
33
|
+
/** The local constraint facts one selected candidate contributes. */
|
|
34
|
+
export interface LocalPackConstraintCandidate {
|
|
35
|
+
readonly fqn: string;
|
|
36
|
+
readonly type: PublishableType;
|
|
37
|
+
readonly authored: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare const findPackPublishDivergenceFindings: (args: {
|
|
40
|
+
readonly candidates: ReadonlyArray<LocalPackConstraintCandidate>;
|
|
41
|
+
readonly reachability: ReadonlyArray<PackDependencyReachability>;
|
|
42
|
+
readonly packs: ReadonlyArray<PublicationPackResult>;
|
|
43
|
+
}) => ReadonlyMap<string, ReadonlyArray<PublishAdvisoryFinding>>;
|
|
44
|
+
/** Local Pack-constraint exclusions that block a member's publication. */
|
|
45
|
+
export declare const localPackConstraintFailures: (facts: ReadonlyArray<ExtensionConstraintInvariantFact>) => ReadonlyMap<string, PublishFailed>;
|
|
46
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { formatFqn } from "@agentxm/extension-model/unstable/extensions";
|
|
3
|
+
import { extensionConstraintFactText, } from "@agentxm/extension-workspace";
|
|
4
|
+
import { PublishFailed } from "./errors.js";
|
|
5
|
+
export const alreadyPublishedVersionConflict = (args) => new PublishFailed({
|
|
6
|
+
category: "conflict",
|
|
7
|
+
detail: `Cannot publish: version ${args.version} is already published for ${args.fqn}. Published versions are immutable.`,
|
|
8
|
+
suggestions: [
|
|
9
|
+
{
|
|
10
|
+
description: "Bump the manifest version.",
|
|
11
|
+
cmd: `axm version ${args.fqn} patch`,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
description: "Re-run with --on-existing verify only when the local archive should be byte-equivalent to the published version.",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
export const nonMonotonicVersionConflict = (args) => new PublishFailed({
|
|
19
|
+
category: "conflict",
|
|
20
|
+
detail: `Cannot publish: version ${args.version} is lower than the highest published version ${args.highestPublished} for ${args.fqn}.`,
|
|
21
|
+
suggestions: [
|
|
22
|
+
{
|
|
23
|
+
description: "Bump the manifest version.",
|
|
24
|
+
cmd: `axm version ${args.fqn} patch`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
description: "Re-run with --backfill only if publishing an older unpublished version is intentional.",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
/** Every publish owner must already exist before any candidate uploads. */
|
|
32
|
+
export const validatePublishOwners = (owners, client) => Effect.forEach([...new Set(owners)], (owner) => client.ownerExists(owner).pipe(Effect.flatMap(({ exists }) => exists
|
|
33
|
+
? Effect.void
|
|
34
|
+
: new PublishFailed({
|
|
35
|
+
category: "not_found",
|
|
36
|
+
detail: `Publish owner ${owner} does not exist.`,
|
|
37
|
+
suggestions: [
|
|
38
|
+
{
|
|
39
|
+
description: "Create the organization in AgentXM before publishing.",
|
|
40
|
+
url: "https://agentxm.ai/orgs/new",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
}))), { concurrency: 4, discard: true });
|
|
44
|
+
export const findPackPublishDivergenceFindings = (args) => {
|
|
45
|
+
const authoredPacks = new Set(args.candidates
|
|
46
|
+
.filter((candidate) => candidate.authored && candidate.type === "pack")
|
|
47
|
+
.map((candidate) => candidate.fqn));
|
|
48
|
+
const localByPair = new Map(args.reachability.map((record) => [`${record.packFqn}\u0000${record.memberFqn}`, record]));
|
|
49
|
+
const findings = new Map();
|
|
50
|
+
for (const pack of args.packs) {
|
|
51
|
+
if (pack.status !== "admitted")
|
|
52
|
+
continue;
|
|
53
|
+
const packFqn = formatFqn(pack.target);
|
|
54
|
+
if (!authoredPacks.has(packFqn))
|
|
55
|
+
continue;
|
|
56
|
+
for (const resolution of pack.resolutions) {
|
|
57
|
+
const memberFqn = formatFqn(resolution.dependency);
|
|
58
|
+
const local = localByPair.get(`${packFqn}\u0000${memberFqn}`);
|
|
59
|
+
if (local?.classification !== "satisfying" ||
|
|
60
|
+
local.memberVersion === undefined ||
|
|
61
|
+
local.memberVersion === resolution.effectiveVersion) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const finding = {
|
|
65
|
+
ruleId: "pack/publish-resolution-divergence",
|
|
66
|
+
severity: "warning",
|
|
67
|
+
message: `${packFqn} resolves ${memberFqn}@${local.memberVersion} in this workspace, while Registry consumers resolve ${memberFqn}@${resolution.effectiveVersion} within ${resolution.dependency.range}.`,
|
|
68
|
+
suggestions: [
|
|
69
|
+
local.memberAuthority === "workspace"
|
|
70
|
+
? {
|
|
71
|
+
description: `Publish ${memberFqn} before publishing the pack if consumers should receive the workspace version`,
|
|
72
|
+
cmd: `axm publish ${memberFqn}`,
|
|
73
|
+
}
|
|
74
|
+
: {
|
|
75
|
+
description: `Update ${memberFqn} if this workspace should match Registry consumers`,
|
|
76
|
+
cmd: `axm update ${memberFqn}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
const current = findings.get(packFqn);
|
|
81
|
+
if (current === undefined)
|
|
82
|
+
findings.set(packFqn, [finding]);
|
|
83
|
+
else
|
|
84
|
+
current.push(finding);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return new Map([...findings.entries()].map(([packFqn, values]) => [
|
|
88
|
+
packFqn,
|
|
89
|
+
[...values].sort((left, right) => left.message.localeCompare(right.message)),
|
|
90
|
+
]));
|
|
91
|
+
};
|
|
92
|
+
/** Local Pack-constraint exclusions that block a member's publication. */
|
|
93
|
+
export const localPackConstraintFailures = (facts) => {
|
|
94
|
+
return new Map(facts.map((fact) => {
|
|
95
|
+
const memberFqn = fact.subject.identity;
|
|
96
|
+
const memberVersion = fact.observation.candidateVersion ?? "unknown";
|
|
97
|
+
const violations = fact.observation.violations ?? [];
|
|
98
|
+
return [
|
|
99
|
+
memberFqn,
|
|
100
|
+
new PublishFailed({
|
|
101
|
+
category: "validation",
|
|
102
|
+
detail: `${extensionConstraintFactText(fact)}; ${memberFqn}@${memberVersion} is excluded by the current workspace Pack constraints: ${violations
|
|
103
|
+
.map((constraint) => `${constraint.dependingPack ?? "unknown Pack"} declares ${constraint.range}`)
|
|
104
|
+
.join("; ")}`,
|
|
105
|
+
suggestions: violations.flatMap((constraint) => constraint.authority === "workspace"
|
|
106
|
+
? [
|
|
107
|
+
{
|
|
108
|
+
description: `Replace ${constraint.dependingPack ?? "the Pack"}'s constraint with the selected version, then publish the member and pack together`,
|
|
109
|
+
cmd: `axm packs add ${constraint.dependingPack ?? "<name>"} ${memberFqn}`,
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: [
|
|
113
|
+
{
|
|
114
|
+
description: `Update ${constraint.dependingPack ?? "the Pack"} if its owner has published a compatible constraint`,
|
|
115
|
+
cmd: `axm update ${constraint.dependingPack ?? "<extension[@version]>"}`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
description: `Otherwise stop workspace authority from shadowing ${memberFqn}`,
|
|
119
|
+
},
|
|
120
|
+
]),
|
|
121
|
+
}),
|
|
122
|
+
];
|
|
123
|
+
}));
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish ignore resolution.
|
|
3
|
+
*
|
|
4
|
+
* A manifest may declare `publish.ignore` to keep development-only files out of
|
|
5
|
+
* the archive. AXM also excludes its reserved canonical completion marker so
|
|
6
|
+
* installed packages can be republished without leaking workspace metadata.
|
|
7
|
+
*
|
|
8
|
+
* Some paths can never be ignored. Dropping the manifest would produce an
|
|
9
|
+
* archive the registry cannot identify, and the failure would surface as a
|
|
10
|
+
* confusing "manifest missing" error far from the pattern that caused it. This
|
|
11
|
+
* module rejects such a pattern at its source instead.
|
|
12
|
+
*
|
|
13
|
+
* @experimental This API is unstable and may change without notice.
|
|
14
|
+
*/
|
|
15
|
+
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as Result from "effect/Result";
|
|
17
|
+
import { PublishFailed } from "./errors.js";
|
|
18
|
+
import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
|
|
19
|
+
import type { BuildZipArchiveOptions } from "./archive.js";
|
|
20
|
+
declare const PublishIgnoreError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
21
|
+
readonly _tag: "PublishIgnoreError";
|
|
22
|
+
} & Readonly<A>;
|
|
23
|
+
/** @experimental This API is unstable and may change without notice. */
|
|
24
|
+
export declare class PublishIgnoreError extends PublishIgnoreError_base<{
|
|
25
|
+
readonly detail: string;
|
|
26
|
+
readonly pattern: string;
|
|
27
|
+
readonly path: string;
|
|
28
|
+
}> {
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Archive-relative paths a publish ignore list may never remove.
|
|
32
|
+
*
|
|
33
|
+
* Only the manifest is protected here. Required body files (a skill's
|
|
34
|
+
* `SKILL.md`, a hook's entrypoint, a knowledge bundle's root index) are
|
|
35
|
+
* enforced by filtered-package validation on the built archive, which reports
|
|
36
|
+
* the missing file by name at both the client and Registry ingestion boundary.
|
|
37
|
+
*/
|
|
38
|
+
export declare const protectedPublishPaths: (type: ExtensionType) => ReadonlyArray<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Effective ignore patterns for a publish, or the first pattern that would drop
|
|
41
|
+
* a protected path.
|
|
42
|
+
*/
|
|
43
|
+
export declare const resolvePublishIgnore: (type: ExtensionType, declared: ReadonlyArray<string> | undefined) => Result.Result<ReadonlyArray<string>, PublishIgnoreError>;
|
|
44
|
+
/**
|
|
45
|
+
* Archive options for one publish, as an Effect the per-type publish operations
|
|
46
|
+
* can yield directly.
|
|
47
|
+
*/
|
|
48
|
+
export declare const publishArchiveOptions: (type: ExtensionType, declared: ReadonlyArray<string> | undefined) => Effect.Effect<BuildZipArchiveOptions, PublishFailed>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=publish-ignore.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish ignore resolution.
|
|
3
|
+
*
|
|
4
|
+
* A manifest may declare `publish.ignore` to keep development-only files out of
|
|
5
|
+
* the archive. AXM also excludes its reserved canonical completion marker so
|
|
6
|
+
* installed packages can be republished without leaking workspace metadata.
|
|
7
|
+
*
|
|
8
|
+
* Some paths can never be ignored. Dropping the manifest would produce an
|
|
9
|
+
* archive the registry cannot identify, and the failure would surface as a
|
|
10
|
+
* confusing "manifest missing" error far from the pattern that caused it. This
|
|
11
|
+
* module rejects such a pattern at its source instead.
|
|
12
|
+
*
|
|
13
|
+
* @experimental This API is unstable and may change without notice.
|
|
14
|
+
*/
|
|
15
|
+
import * as Data from "effect/Data";
|
|
16
|
+
import * as Effect from "effect/Effect";
|
|
17
|
+
import * as Result from "effect/Result";
|
|
18
|
+
import { PublishFailed } from "./errors.js";
|
|
19
|
+
import { expandGlobs } from "./internal/glob.js";
|
|
20
|
+
import { manifestFilenameForType } from "@agentxm/registry-protocol/unstable/publish/manifest-policy";
|
|
21
|
+
/** @experimental This API is unstable and may change without notice. */
|
|
22
|
+
export class PublishIgnoreError extends Data.TaggedError("PublishIgnoreError") {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Archive-relative paths a publish ignore list may never remove.
|
|
26
|
+
*
|
|
27
|
+
* Only the manifest is protected here. Required body files (a skill's
|
|
28
|
+
* `SKILL.md`, a hook's entrypoint, a knowledge bundle's root index) are
|
|
29
|
+
* enforced by filtered-package validation on the built archive, which reports
|
|
30
|
+
* the missing file by name at both the client and Registry ingestion boundary.
|
|
31
|
+
*/
|
|
32
|
+
export const protectedPublishPaths = (type) => [
|
|
33
|
+
manifestFilenameForType(type),
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Effective ignore patterns for a publish, or the first pattern that would drop
|
|
37
|
+
* a protected path.
|
|
38
|
+
*/
|
|
39
|
+
export const resolvePublishIgnore = (type, declared) => {
|
|
40
|
+
if (declared === undefined || declared.length === 0) {
|
|
41
|
+
return Result.succeed([]);
|
|
42
|
+
}
|
|
43
|
+
const protectedPaths = protectedPublishPaths(type);
|
|
44
|
+
for (const pattern of declared) {
|
|
45
|
+
const [blocked] = expandGlobs([pattern], protectedPaths);
|
|
46
|
+
if (blocked !== undefined) {
|
|
47
|
+
return Result.fail(new PublishIgnoreError({
|
|
48
|
+
detail: `publish.ignore pattern "${pattern}" would leave "${blocked}" out of the archive, and a ${type} package cannot be published without it.`,
|
|
49
|
+
pattern,
|
|
50
|
+
path: blocked,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Result.succeed(declared);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Archive options for one publish, as an Effect the per-type publish operations
|
|
58
|
+
* can yield directly.
|
|
59
|
+
*/
|
|
60
|
+
export const publishArchiveOptions = (type, declared) => Effect.fromResult(resolvePublishIgnore(type, declared)).pipe(Effect.map((ignore) => ({ ignore })), Effect.mapError((cause) => new PublishFailed({ category: "validation", detail: cause.detail, cause })));
|
|
61
|
+
//# sourceMappingURL=publish-ignore.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions";
|
|
2
|
+
/**
|
|
3
|
+
* Publish policy, total over every extension type: a new type cannot be added
|
|
4
|
+
* without deciding whether it publishes.
|
|
5
|
+
*/
|
|
6
|
+
export declare const PUBLISHABLE_TYPES: {
|
|
7
|
+
readonly skill: true;
|
|
8
|
+
readonly "mcp-server": true;
|
|
9
|
+
readonly subagent: true;
|
|
10
|
+
readonly rule: true;
|
|
11
|
+
readonly hook: true;
|
|
12
|
+
readonly knowledge: true;
|
|
13
|
+
readonly pack: true;
|
|
14
|
+
};
|
|
15
|
+
type TruthyKeys<T> = {
|
|
16
|
+
[K in keyof T]: T[K] extends true ? K : never;
|
|
17
|
+
}[keyof T];
|
|
18
|
+
export type PublishableType = TruthyKeys<typeof PUBLISHABLE_TYPES>;
|
|
19
|
+
export declare const isPublishableType: (type: ExtensionType) => type is PublishableType;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=publishable-types.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish policy, total over every extension type: a new type cannot be added
|
|
3
|
+
* without deciding whether it publishes.
|
|
4
|
+
*/
|
|
5
|
+
export const PUBLISHABLE_TYPES = {
|
|
6
|
+
skill: true,
|
|
7
|
+
"mcp-server": true,
|
|
8
|
+
subagent: true,
|
|
9
|
+
rule: true,
|
|
10
|
+
hook: true,
|
|
11
|
+
knowledge: true,
|
|
12
|
+
pack: true,
|
|
13
|
+
};
|
|
14
|
+
export const isPublishableType = (type) => PUBLISHABLE_TYPES[type];
|
|
15
|
+
//# sourceMappingURL=publishable-types.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The per-item outcome facts recovery selection reads. */
|
|
2
|
+
export interface PublishRecoveryItem {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly status: string;
|
|
5
|
+
readonly reason?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const publishRecoverySelection: (results: ReadonlyArray<PublishRecoveryItem>) => {
|
|
8
|
+
readonly remainingItems: ReadonlyArray<string>;
|
|
9
|
+
readonly blockedDependents: ReadonlyArray<string>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const publishRecoverySelection = (results) => ({
|
|
2
|
+
// The continuation set covers everything not definitively published:
|
|
3
|
+
// failures, blocked dependents, indeterminate uploads (the re-run verifies
|
|
4
|
+
// byte-identical versions before retrying), and interrupted pending items.
|
|
5
|
+
remainingItems: results
|
|
6
|
+
.filter((result) => result.status === "failed" ||
|
|
7
|
+
result.status === "blocked" ||
|
|
8
|
+
result.status === "unknown" ||
|
|
9
|
+
(result.status === "pending" && result.reason === "interrupted"))
|
|
10
|
+
.map((result) => result.id),
|
|
11
|
+
blockedDependents: results
|
|
12
|
+
.filter((result) => result.status === "blocked")
|
|
13
|
+
.map((result) => result.id),
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { type PublishExtensionArgs, type PublishExtensionResponse, type RegistryClient, type RegistryClientFailure } from "@agentxm/registry-client";
|
|
3
|
+
import { PublishFailed } from "./errors.js";
|
|
4
|
+
/** Every failure a publish settlement can surface. */
|
|
5
|
+
export type PublishSettlementFailure = PublishFailed | RegistryClientFailure;
|
|
6
|
+
export type PublishSettlement = "response" | "readback" | "replay" | "unresolved";
|
|
7
|
+
export type SettledPublish = {
|
|
8
|
+
readonly status: "published";
|
|
9
|
+
readonly settlement: Exclude<PublishSettlement, "unresolved">;
|
|
10
|
+
readonly response?: PublishExtensionResponse;
|
|
11
|
+
} | {
|
|
12
|
+
readonly status: "unknown";
|
|
13
|
+
readonly settlement: "unresolved";
|
|
14
|
+
readonly reason: "settlement_unresolved" | "authorization_expired";
|
|
15
|
+
readonly error: PublishSettlementFailure;
|
|
16
|
+
};
|
|
17
|
+
/** Settle one exact immutable publish with bounded readback and one replay. */
|
|
18
|
+
export declare const settlePublish: (client: Pick<RegistryClient, "publishExtension" | "getExactExtensionVersion">, args: PublishExtensionArgs) => Effect.Effect<SettledPublish, PublishSettlementFailure>;
|
|
19
|
+
//# sourceMappingURL=settlement.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import { isRegistryClientFailure, } from "@agentxm/registry-client";
|
|
4
|
+
import { PublishFailed } from "./errors.js";
|
|
5
|
+
const isAmbiguousDispatch = (error) => error.category === "timeout" || error.metadata?.requestPolicy?.retryable === true;
|
|
6
|
+
const readback = (client, args, remaining, lastError) => client
|
|
7
|
+
.getExactExtensionVersion({
|
|
8
|
+
owner: args.owner,
|
|
9
|
+
type: args.type,
|
|
10
|
+
name: args.name,
|
|
11
|
+
version: args.version,
|
|
12
|
+
...(args.accessToken === undefined ? {} : { accessToken: args.accessToken }),
|
|
13
|
+
})
|
|
14
|
+
.pipe(Effect.matchEffect({
|
|
15
|
+
onFailure: (error) => remaining <= 1
|
|
16
|
+
? Effect.succeed({ kind: "absent", error })
|
|
17
|
+
: Effect.sleep("500 millis").pipe(Effect.andThen(readback(client, args, remaining - 1, error))),
|
|
18
|
+
onSuccess: (version) => {
|
|
19
|
+
if (Option.isSome(version)) {
|
|
20
|
+
return version.value.integrity === args.metadata.integrity
|
|
21
|
+
? Effect.succeed({ kind: "matching" })
|
|
22
|
+
: Effect.fail(new PublishFailed({
|
|
23
|
+
category: "conflict",
|
|
24
|
+
detail: `Registry version ${args.owner}/${args.type}/${args.name}@${args.version} has different content.`,
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
return remaining <= 1
|
|
28
|
+
? Effect.succeed({
|
|
29
|
+
kind: "absent",
|
|
30
|
+
...(lastError === undefined ? {} : { error: lastError }),
|
|
31
|
+
})
|
|
32
|
+
: Effect.sleep("500 millis").pipe(Effect.andThen(readback(client, args, remaining - 1, lastError)));
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
const unresolved = (initialError, laterError) => {
|
|
36
|
+
const error = laterError ?? initialError;
|
|
37
|
+
return {
|
|
38
|
+
status: "unknown",
|
|
39
|
+
settlement: "unresolved",
|
|
40
|
+
reason: isRegistryClientFailure(error) && error.category === "auth"
|
|
41
|
+
? "authorization_expired"
|
|
42
|
+
: "settlement_unresolved",
|
|
43
|
+
error,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/** Settle one exact immutable publish with bounded readback and one replay. */
|
|
47
|
+
export const settlePublish = (client, args) => client.publishExtension(args).pipe(Effect.map((response) => ({
|
|
48
|
+
status: "published",
|
|
49
|
+
settlement: "response",
|
|
50
|
+
response,
|
|
51
|
+
})), Effect.catch((initialError) => {
|
|
52
|
+
if (!isAmbiguousDispatch(initialError))
|
|
53
|
+
return Effect.fail(initialError);
|
|
54
|
+
return readback(client, args, 3).pipe(Effect.flatMap((firstReadback) => {
|
|
55
|
+
if (firstReadback.kind === "matching") {
|
|
56
|
+
const settled = { status: "published", settlement: "readback" };
|
|
57
|
+
return Effect.succeed(settled);
|
|
58
|
+
}
|
|
59
|
+
return client.publishExtension(args).pipe(Effect.map((response) => ({
|
|
60
|
+
status: "published",
|
|
61
|
+
settlement: "replay",
|
|
62
|
+
response,
|
|
63
|
+
})), Effect.catch((replayError) => replayError.category === "conflict"
|
|
64
|
+
? Effect.fail(replayError)
|
|
65
|
+
: readback(client, args, 3).pipe(Effect.map((finalReadback) => finalReadback.kind === "matching"
|
|
66
|
+
? { status: "published", settlement: "readback" }
|
|
67
|
+
: unresolved(initialError, finalReadback.error ?? replayError)))));
|
|
68
|
+
}));
|
|
69
|
+
}));
|
|
70
|
+
//# sourceMappingURL=settlement.js.map
|