@agentxm/extension-lifecycle 0.28.7-preview.1788522540.63ba4b89a → 0.28.8

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.
@@ -20,15 +20,16 @@ import type { SubagentExtensionRef } from "@agentxm/extension-model/unstable/ext
20
20
  import type { VersionRange } from "@agentxm/extension-model/unstable/version-constraints";
21
21
  import { WorkspaceMutations } from "@agentxm/workspace-state";
22
22
  import { ExtensionLifecycleFailed } from "./errors.js";
23
+ import { ReleaseAgePosture } from "./release-age-posture.js";
23
24
  import { LifecycleFailureAdapter } from "./failure-adapter.js";
24
25
  import type { AcceptedCanonicalRefError } from "@agentxm/workspace-state";
25
26
  import type { ConfiguredRegistryResolution } from "@agentxm/workspace-state";
26
- export declare const makeConfiguredReleaseAgeEvaluation: (mode: "enforce" | "ignore") => Effect.Effect<{
27
+ export declare const makeConfiguredReleaseAgeEvaluation: () => Effect.Effect<{
27
28
  minimumReleaseAge: import("effect/Duration").Duration;
28
29
  evaluatedAt: DateTime.Utc;
29
- mode: "enforce" | "ignore";
30
+ mode: import("./release-age-posture.js").ReleaseAgePostureValue;
30
31
  exclude: readonly import("@agentxm/extension-model/unstable/extensions/release-age").ScopedReleaseAgeExcludePattern[];
31
- }, ExtensionLifecycleFailed | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, WorkspaceMutations>;
32
+ }, ExtensionLifecycleFailed | import("@agentxm/workspace-state").WorkspaceSettingsReadFailure, ReleaseAgePosture | WorkspaceMutations>;
32
33
  export declare const resolveConfiguredRegistryEntry: (name: string, source: string, expectedType: ExtensionType, releaseAgeEvaluation: ReleaseAgeEvaluation) => Effect.Effect<Option.Option<ConfiguredRegistryResolution>, ExtensionLifecycleFailed | SourceResolutionFailure | AcceptedCanonicalRefError, LifecycleFailureAdapter | SourceHostProviders | WorkspaceCatalog | WorkspaceMutations | FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | Scope.Scope>;
33
34
  export declare const resolveConfiguredSkill: (name: string, source: string, releaseAgeEvaluation: ReleaseAgeEvaluation) => Effect.Effect<{
34
35
  ref: import("@agentxm/extension-model/unstable/extensions/refs/skill").RegistrySkillRef;
@@ -7,10 +7,12 @@ import { resolveSource, SourceHostProviders, WorkspaceCatalog } from "@agentxm/e
7
7
  import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
8
8
  import { WorkspaceMutations } from "@agentxm/workspace-state";
9
9
  import { ExtensionLifecycleFailed } from "./errors.js";
10
+ import { ReleaseAgePosture } from "./release-age-posture.js";
10
11
  import { LifecycleFailureAdapter } from "./failure-adapter.js";
11
12
  import { acceptedResolutionRef } from "@agentxm/workspace-state";
12
13
  import { resolveWorkspaceExtensionRef } from "@agentxm/workspace-state";
13
- export const makeConfiguredReleaseAgeEvaluation = (mode) => Effect.gen(function* () {
14
+ export const makeConfiguredReleaseAgeEvaluation = () => Effect.gen(function* () {
15
+ const mode = yield* ReleaseAgePosture;
14
16
  const ws = yield* WorkspaceMutations;
15
17
  const configured = yield* ws.getMinimumReleaseAge();
16
18
  const minimumReleaseAge = parseMinimumReleaseAge(configured);
@@ -61,6 +63,9 @@ const configuredRegistryResolution = (resolution) => Effect.gen(function* () {
61
63
  category: "conflict",
62
64
  title: "Release held by minimum release age",
63
65
  detail: `${resolution.target}@${resolution.candidate.version} is held by the minimum release age until ${resolution.candidate.eligibleAt}`,
66
+ // Both routes are reachable from wherever the operator is standing:
67
+ // the exemption is declared once, the flag lasts one run.
68
+ recover: `Declare ${resolution.target} in minimumReleaseAgeExclude, or rerun this command with --ignore-release-age to take it for this run only.`,
64
69
  });
65
70
  }
66
71
  const holdbacks = resolution.kind === "exempted" || resolution.newerHeld === undefined
@@ -29,7 +29,7 @@ import { stripFileProtocol } from "../internal/fs-helpers.js";
29
29
  import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
30
30
  import { decodeRelativePathSync, makeWorkspaceRelativePath, } from "@agentxm/extension-model/unstable/path-types";
31
31
  import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
32
- import { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, } from "../configured-entry-resolution.js";
32
+ import { usableAcceptedCanonicalRef } from "@agentxm/workspace-state";
33
33
  import { coupleLifecycleDependencyFailure } from "../errors.js";
34
34
  import { LifecycleFailureAdapter } from "../failure-adapter.js";
35
35
  import { HOOK_FALLBACKS_REGION_OWNER, HookManager } from "@agentxm/extension-workspace";
@@ -711,11 +711,17 @@ export const HookManagerLive = Layer.effect(HookManager, Effect.gen(function* ()
711
711
  const configured = yield* ws.getConfiguredHookEntries();
712
712
  return Option.fromUndefinedOr(configured[target.name]?.source);
713
713
  }),
714
+ /**
715
+ * Every enabled entry's accepted canonical package, read from accepted
716
+ * resolution rather than re-resolved from source. Materialization
717
+ * realizes what the workspace already accepted; going back to the
718
+ * source would put an unrelated configured entry's release age between
719
+ * an operator and the extension they are authoring.
720
+ */
714
721
  listMaterializable: Effect.fn("HookManager.listMaterializable")(function* () {
715
722
  const configured = yield* ws.getConfiguredHookEntries();
716
- const releaseAgeEvaluation = yield* provide(makeConfiguredReleaseAgeEvaluation("enforce")).pipe(Effect.mapError(coupleLifecycleDependencyFailure));
717
- const refs = yield* Effect.scoped(Effect.forEach(enabledConfiguredEntries(configured), ([name, entry]) => provide(resolveConfiguredHook(name, entry.source, releaseAgeEvaluation)).pipe(Effect.mapError(coupleLifecycleDependencyFailure), Effect.map(({ ref }) => ref)), { concurrency: "unbounded" }));
718
- return refs;
723
+ const refs = yield* Effect.forEach(enabledConfiguredEntries(configured), ([name]) => provide(usableAcceptedCanonicalRef({ workspace: ws, type: "hook", name }).pipe(Effect.map(Option.filter((ref) => ref.type === "hook")))), { concurrency: "unbounded" });
724
+ return refs.flatMap((ref) => (Option.isSome(ref) ? [ref.value] : []));
719
725
  }),
720
726
  materializeUninstall,
721
727
  materializeDeactivate,
@@ -9,9 +9,11 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
  export { ExtensionLifecycleFailed } from "./errors.js";
12
+ export { ReleaseAgePosture, type ReleaseAgePostureValue } from "./release-age-posture.js";
12
13
  export { LifecycleFailureAdapter, withAdaptedStepFailures, type LifecycleFailureAdapterService, } from "./failure-adapter.js";
13
14
  export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
14
15
  export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
16
+ export { PUBLISHER_CHANGE_CONDITION_ID, classifyPublisherBindingTransition, publisherChangeRiskCondition, publisherTransitionWarning, registryBindingProposal, withPublisherTrust, withPublisherTrustConditions, type PublisherBindingTransition, } from "./publisher-binding.js";
15
17
  export { buildInstallCommandPlan, runInstallCommandWorkflow, type InstallExtensionCommandWorkflowActions, } from "./workflows/install-command/workflow.js";
16
18
  export { runUninstallCommandWorkflow, type UninstallExtensionCommandWorkflowActions, type UninstallWorkflowFlags, } from "./workflows/uninstall-command/workflow.js";
17
19
  export { materializeRegistryPackage, materializeRegistryPackageWithTreeIntegrity, type MaterializeRegistryPackageArgs, type RegistryPackageMaterializationMessages, } from "./registry-materialization.js";
package/dist/src/index.js CHANGED
@@ -9,9 +9,11 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
  export { ExtensionLifecycleFailed } from "./errors.js";
12
+ export { ReleaseAgePosture } from "./release-age-posture.js";
12
13
  export { LifecycleFailureAdapter, withAdaptedStepFailures, } from "./failure-adapter.js";
13
14
  export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
14
15
  export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
16
+ export { PUBLISHER_CHANGE_CONDITION_ID, classifyPublisherBindingTransition, publisherChangeRiskCondition, publisherTransitionWarning, registryBindingProposal, withPublisherTrust, withPublisherTrustConditions, } from "./publisher-binding.js";
15
17
  export { buildInstallCommandPlan, runInstallCommandWorkflow, } from "./workflows/install-command/workflow.js";
16
18
  export { runUninstallCommandWorkflow, } from "./workflows/uninstall-command/workflow.js";
17
19
  export { materializeRegistryPackage, materializeRegistryPackageWithTreeIntegrity, } from "./registry-materialization.js";
@@ -26,7 +26,7 @@ import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstab
26
26
  import { recordFootprint } from "@agentxm/workspace-state";
27
27
  import { makeWorkspaceRelativePath } from "@agentxm/extension-model/unstable/path-types";
28
28
  import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
29
- import { makeConfiguredReleaseAgeEvaluation, resolveConfiguredKnowledge, } from "../configured-entry-resolution.js";
29
+ import { usableAcceptedCanonicalRef } from "@agentxm/workspace-state";
30
30
  import { getKnowledgeLockEntries } from "@agentxm/workspace-state";
31
31
  import { KnowledgeManager, } from "@agentxm/extension-workspace";
32
32
  import { WorkspaceMutations } from "@agentxm/workspace-state";
@@ -667,10 +667,21 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
667
667
  getConfiguredSource: ({ target }) => ws
668
668
  .getConfiguredKnowledgeEntries()
669
669
  .pipe(Effect.map((entries) => Option.fromUndefinedOr(entries[target.name]?.source))),
670
+ /**
671
+ * Every enabled entry's accepted canonical package, read from accepted
672
+ * resolution rather than re-resolved from source. Materialization
673
+ * realizes what the workspace already accepted; going back to the
674
+ * source would put an unrelated configured entry's release age between
675
+ * an operator and the extension they are authoring.
676
+ */
670
677
  listMaterializable: () => Effect.gen(function* () {
671
678
  const nodes = yield* activeKnowledgeNodes();
672
- const releaseAgeEvaluation = yield* provide(makeConfiguredReleaseAgeEvaluation("enforce")).pipe(Effect.mapError(coupleLifecycleDependencyFailure));
673
- return yield* Effect.scoped(Effect.forEach(nodes, (node) => provide(resolveConfiguredKnowledge(node.name, node.source, releaseAgeEvaluation)).pipe(Effect.mapError(coupleLifecycleDependencyFailure), Effect.map(({ ref }) => ref)), { concurrency: "unbounded" }));
679
+ const refs = yield* Effect.forEach(nodes, (node) => provide(usableAcceptedCanonicalRef({
680
+ workspace: ws,
681
+ type: "knowledge",
682
+ name: node.name,
683
+ }).pipe(Effect.map(Option.filter((ref) => ref.type === "knowledge")))), { concurrency: "unbounded" });
684
+ return refs.flatMap((ref) => (Option.isSome(ref) ? [ref.value] : []));
674
685
  }),
675
686
  materializeUninstall,
676
687
  materializeDeactivate,
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Publisher binding trust classification.
3
+ *
4
+ * A Registry resolution binds a publisher identity. When an operation
5
+ * proposes to replace an already accepted binding for the same configured
6
+ * extension with a different one, that transition is a trust decision a
7
+ * person makes at a prompt: no flag and no unattended mode can approve it.
8
+ * Every route that can replace an accepted binding — root and type update
9
+ * forms, reinstall and re-resolution, and pack closures — classifies the
10
+ * transition from the structured accepted and proposed identities here, so
11
+ * the same rule holds everywhere the transition can occur.
12
+ *
13
+ * A first acceptance is not a change, and an integrity mismatch while
14
+ * reacquiring the same accepted identity remains a hard failure elsewhere.
15
+ *
16
+ * @experimental This API is unstable and may change without notice.
17
+ */
18
+ import * as Effect from "effect/Effect";
19
+ import type * as FileSystem from "effect/FileSystem";
20
+ import * as Option from "effect/Option";
21
+ import type * as Path from "effect/Path";
22
+ import type { ExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/extension-ref";
23
+ import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions";
24
+ import type { Plan, PlanRiskCondition, RegistryBindingProposal } from "@agentxm/workspace-operations";
25
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
26
+ export interface PublisherBindingTransition {
27
+ readonly extensionType: ExtensionType;
28
+ /** The configured (local) name whose accepted resolution changes publisher. */
29
+ readonly target: string;
30
+ readonly owner: string;
31
+ readonly packageName: string;
32
+ /** The accepted publisher binding being replaced. */
33
+ readonly accepted: string;
34
+ /** The publisher binding the operation proposes to accept. */
35
+ readonly proposed: string;
36
+ }
37
+ export declare const PUBLISHER_CHANGE_CONDITION_ID = "publisher-ownership-change";
38
+ /** The structured proposal an extension ref makes, when it is a Registry ref. */
39
+ export declare const registryBindingProposal: (ref: ExtensionRef) => RegistryBindingProposal | undefined;
40
+ /**
41
+ * Classify one proposed acceptance against the accepted resolution for the
42
+ * same configured target. Only a Registry binding replacing a different
43
+ * Registry binding is a publisher change.
44
+ */
45
+ export declare const classifyPublisherBindingTransition: (args: {
46
+ readonly accepted: Option.Option<ExtensionRef>;
47
+ readonly proposed: RegistryBindingProposal;
48
+ }) => Option.Option<PublisherBindingTransition>;
49
+ /** The warning a step carries when its acceptance changes publisher. */
50
+ export declare const publisherTransitionWarning: (transition: PublisherBindingTransition) => string;
51
+ /**
52
+ * The interactive-only condition a plan carries when any of its steps
53
+ * replaces an accepted publisher binding.
54
+ */
55
+ export declare const publisherChangeRiskCondition: (transitions: ReadonlyArray<PublisherBindingTransition>) => PlanRiskCondition | undefined;
56
+ /** Append the publisher-change condition for the given transitions, if any. */
57
+ export declare const withPublisherTrustConditions: <R, O>(plan: Plan<R, O>, transitions: ReadonlyArray<PublisherBindingTransition>) => Plan<R, O>;
58
+ /**
59
+ * Classify every proposed Registry acceptance in a plan against the accepted
60
+ * resolutions of the workspace, marking each changing step and attaching the
61
+ * interactive-only condition. When an accepted resolution cannot be read the
62
+ * plan blocks rather than proceeding without the comparison.
63
+ */
64
+ export declare const withPublisherTrust: <R, O>(plan: Plan<R, O>) => Effect.Effect<Plan<R, O>, never, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
65
+ //# sourceMappingURL=publisher-binding.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Publisher binding trust classification.
3
+ *
4
+ * A Registry resolution binds a publisher identity. When an operation
5
+ * proposes to replace an already accepted binding for the same configured
6
+ * extension with a different one, that transition is a trust decision a
7
+ * person makes at a prompt: no flag and no unattended mode can approve it.
8
+ * Every route that can replace an accepted binding — root and type update
9
+ * forms, reinstall and re-resolution, and pack closures — classifies the
10
+ * transition from the structured accepted and proposed identities here, so
11
+ * the same rule holds everywhere the transition can occur.
12
+ *
13
+ * A first acceptance is not a change, and an integrity mismatch while
14
+ * reacquiring the same accepted identity remains a hard failure elsewhere.
15
+ *
16
+ * @experimental This API is unstable and may change without notice.
17
+ */
18
+ import * as Effect from "effect/Effect";
19
+ import * as Option from "effect/Option";
20
+ import { toExtensionTypePlural } from "@agentxm/extension-model/unstable/extensions/common";
21
+ import { acceptedResolutionRef, WorkspaceMutations } from "@agentxm/workspace-state";
22
+ import { targetFromRef } from "@agentxm/extension-workspace";
23
+ export const PUBLISHER_CHANGE_CONDITION_ID = "publisher-ownership-change";
24
+ const ACCEPTED_RESOLUTION_UNREADABLE_ID = "accepted-resolution-unreadable";
25
+ /** The structured proposal an extension ref makes, when it is a Registry ref. */
26
+ export const registryBindingProposal = (ref) => ref.refType === "registry"
27
+ ? {
28
+ extensionType: ref.type,
29
+ target: targetFromRef(ref).name,
30
+ owner: ref.owner,
31
+ packageName: ref.name,
32
+ version: ref.version,
33
+ publisherBindingId: ref.publisherBindingId,
34
+ }
35
+ : undefined;
36
+ /**
37
+ * Classify one proposed acceptance against the accepted resolution for the
38
+ * same configured target. Only a Registry binding replacing a different
39
+ * Registry binding is a publisher change.
40
+ */
41
+ export const classifyPublisherBindingTransition = (args) => Option.flatMap(args.accepted, (accepted) => accepted.refType === "registry" &&
42
+ accepted.type === args.proposed.extensionType &&
43
+ accepted.publisherBindingId !== args.proposed.publisherBindingId
44
+ ? Option.some({
45
+ extensionType: args.proposed.extensionType,
46
+ target: args.proposed.target,
47
+ owner: args.proposed.owner,
48
+ packageName: args.proposed.packageName,
49
+ accepted: accepted.publisherBindingId,
50
+ proposed: args.proposed.publisherBindingId,
51
+ })
52
+ : Option.none());
53
+ const describeTransition = (transition) => `${transition.owner}/${toExtensionTypePlural(transition.extensionType)}/${transition.packageName}`;
54
+ /** The warning a step carries when its acceptance changes publisher. */
55
+ export const publisherTransitionWarning = (transition) => `Publisher identity changed (${transition.accepted} → ${transition.proposed}); confirm only if you trust the current publisher`;
56
+ /**
57
+ * The interactive-only condition a plan carries when any of its steps
58
+ * replaces an accepted publisher binding.
59
+ */
60
+ export const publisherChangeRiskCondition = (transitions) => transitions.length === 0
61
+ ? undefined
62
+ : {
63
+ level: "confirmable",
64
+ consent: "interactive-only",
65
+ id: PUBLISHER_CHANGE_CONDITION_ID,
66
+ detail: `Publisher identity changed for ${transitions
67
+ .map(describeTransition)
68
+ .join(", ")}; confirm only if you trust the current publisher.`,
69
+ };
70
+ /** Append the publisher-change condition for the given transitions, if any. */
71
+ export const withPublisherTrustConditions = (plan, transitions) => {
72
+ const condition = publisherChangeRiskCondition(transitions);
73
+ return condition === undefined
74
+ ? plan
75
+ : { ...plan, riskConditions: [...(plan.riskConditions ?? []), condition] };
76
+ };
77
+ const stepWithTransitionWarning = (step, transition) => {
78
+ const warning = publisherTransitionWarning(transition);
79
+ switch (step.readiness) {
80
+ case "ready": {
81
+ const { message: _message, ...rest } = step;
82
+ return { ...rest, readiness: "warn", warnMessage: warning };
83
+ }
84
+ case "warn":
85
+ return { ...step, warnMessage: `${step.warnMessage}; ${warning}` };
86
+ case "error":
87
+ return step;
88
+ }
89
+ };
90
+ /**
91
+ * Classify every proposed Registry acceptance in a plan against the accepted
92
+ * resolutions of the workspace, marking each changing step and attaching the
93
+ * interactive-only condition. When an accepted resolution cannot be read the
94
+ * plan blocks rather than proceeding without the comparison.
95
+ */
96
+ export const withPublisherTrust = (plan) => Effect.gen(function* () {
97
+ const workspace = yield* WorkspaceMutations;
98
+ const proposals = plan.jobs.flatMap((job) => job.steps.flatMap((step) => (step.registryBinding === undefined ? [] : [step])));
99
+ if (proposals.length === 0)
100
+ return plan;
101
+ const unreadable = [];
102
+ const transitionsByKey = new Map();
103
+ for (const step of proposals) {
104
+ const proposed = step.registryBinding;
105
+ if (proposed === undefined)
106
+ continue;
107
+ const accepted = yield* acceptedResolutionRef({
108
+ workspace,
109
+ type: proposed.extensionType,
110
+ name: proposed.target,
111
+ }).pipe(Effect.option);
112
+ if (Option.isNone(accepted)) {
113
+ unreadable.push(`${proposed.extensionType}:${proposed.target}`);
114
+ continue;
115
+ }
116
+ const transition = classifyPublisherBindingTransition({
117
+ accepted: accepted.value,
118
+ proposed,
119
+ });
120
+ if (Option.isSome(transition)) {
121
+ transitionsByKey.set(`${proposed.extensionType}:${proposed.target}`, transition.value);
122
+ }
123
+ }
124
+ const transitions = [...transitionsByKey.values()];
125
+ const marked = {
126
+ ...plan,
127
+ jobs: plan.jobs.map((job) => ({
128
+ ...job,
129
+ steps: job.steps.map((step) => {
130
+ const binding = step.registryBinding;
131
+ const transition = binding === undefined
132
+ ? undefined
133
+ : transitionsByKey.get(`${binding.extensionType}:${binding.target}`);
134
+ return transition === undefined ? step : stepWithTransitionWarning(step, transition);
135
+ }),
136
+ })),
137
+ };
138
+ const blocked = unreadable.length === 0
139
+ ? undefined
140
+ : {
141
+ level: "blocked",
142
+ id: ACCEPTED_RESOLUTION_UNREADABLE_ID,
143
+ detail: `The accepted resolution for ${unreadable.join(", ")} could not be read, so the proposed publisher binding cannot be checked.`,
144
+ errorCode: "conflict",
145
+ };
146
+ const withTrust = withPublisherTrustConditions(marked, transitions);
147
+ return blocked === undefined
148
+ ? withTrust
149
+ : { ...withTrust, riskConditions: [...(withTrust.riskConditions ?? []), blocked] };
150
+ });
151
+ //# sourceMappingURL=publisher-binding.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-lifecycle/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-lifecycle/ReleaseAgePosture") {
14
+ }
15
+ //# sourceMappingURL=release-age-posture.js.map
@@ -30,8 +30,7 @@ import { removeIfExists } from "@agentxm/workspace-state";
30
30
  import { makeWorkspaceRelativePath } from "@agentxm/extension-model/unstable/path-types";
31
31
  import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
32
32
  import { WorkspaceMutations } from "@agentxm/workspace-state";
33
- import { makeConfiguredReleaseAgeEvaluation, resolveConfiguredRule, } from "../configured-entry-resolution.js";
34
- import { coupleLifecycleDependencyFailure } from "../errors.js";
33
+ import { usableAcceptedCanonicalRef } from "@agentxm/workspace-state";
35
34
  import { LifecycleFailureAdapter } from "../failure-adapter.js";
36
35
  import { isObservedInstalled } from "@agentxm/workspace-state";
37
36
  import { acceptedCanonicalObservation, prepareAcceptedCanonicalTransition, removableAcceptedCanonicalPath, } from "@agentxm/workspace-state";
@@ -423,11 +422,17 @@ export const RuleManagerLive = Layer.effect(RuleManager, Effect.gen(function* ()
423
422
  const configured = yield* ws.getConfiguredRuleEntries();
424
423
  return Option.fromUndefinedOr(configured[target.name]?.source);
425
424
  }),
425
+ /**
426
+ * Every enabled entry's accepted canonical package, read from accepted
427
+ * resolution rather than re-resolved from source. Materialization
428
+ * realizes what the workspace already accepted; going back to the
429
+ * source would put an unrelated configured entry's release age between
430
+ * an operator and the extension they are authoring.
431
+ */
426
432
  listMaterializable: Effect.fn("RuleManager.listMaterializable")(function* () {
427
433
  const configured = yield* ws.getConfiguredRuleEntries();
428
- const releaseAgeEvaluation = yield* provide(makeConfiguredReleaseAgeEvaluation("enforce")).pipe(Effect.mapError(coupleLifecycleDependencyFailure));
429
- const refs = yield* Effect.scoped(Effect.forEach(enabledConfiguredEntries(configured), ([name, entry]) => provide(resolveConfiguredRule(name, entry.source, releaseAgeEvaluation)).pipe(Effect.mapError(coupleLifecycleDependencyFailure), Effect.map(({ ref }) => ref)), { concurrency: "unbounded" }));
430
- return refs;
434
+ const refs = yield* Effect.forEach(enabledConfiguredEntries(configured), ([name]) => provide(usableAcceptedCanonicalRef({ workspace: ws, type: "rule", name }).pipe(Effect.map(Option.filter((ref) => ref.type === "rule")))), { concurrency: "unbounded" });
435
+ return refs.flatMap((ref) => (Option.isSome(ref) ? [ref.value] : []));
431
436
  }),
432
437
  materializeUninstall,
433
438
  materializeDeactivate,
@@ -41,7 +41,7 @@ export interface InstallExtensionCommandWorkflowActions<Args, Parsed, Req, Ref,
41
41
  export declare const buildInstallCommandPlan: <Args, Parsed, Req, Ref, Intent, ParseError, ResolveError, TransformError = never, TransformRequirements = never>(args: Args, actions: InstallExtensionCommandWorkflowActions<Args, Parsed, Req, Ref, Intent, ParseError, ResolveError>, options?: {
42
42
  readonly transformIntent?: (intent: Intent) => Intent;
43
43
  readonly transformPlan?: (plan: Plan) => Effect.Effect<Plan, TransformError, TransformRequirements>;
44
- }) => Effect.Effect<Plan<never, never>, ParseError | ResolveError | TransformError, Scope.Scope | TransformRequirements>;
44
+ }) => Effect.Effect<Plan<never, never>, ParseError | ResolveError | TransformError, import("@agentxm/workspace-state").WorkspaceMutations | import("effect/FileSystem").FileSystem | import("effect/Path").Path | Scope.Scope | TransformRequirements>;
45
45
  /**
46
46
  * Run the canonical install command workflow.
47
47
  *
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import * as Effect from "effect/Effect";
10
10
  import { observeUnit, previewOrApplyPlan, publishPhaseStarted, } from "@agentxm/workspace-operations";
11
+ import { withPublisherTrust } from "../../publisher-binding.js";
11
12
  // -----------------------------------------------------------------------------
12
13
  // Install Command Workflow
13
14
  // -----------------------------------------------------------------------------
@@ -26,7 +27,11 @@ export const buildInstallCommandPlan = (args, actions, options) => Effect.gen(fu
26
27
  const finalizedIntent = yield* actions.finalizeIntent(parsed, refs);
27
28
  const intent = options?.transformIntent?.(finalizedIntent) ?? finalizedIntent;
28
29
  const plan = yield* actions.buildPlan(intent);
29
- return options?.transformPlan === undefined ? plan : yield* options.transformPlan(plan);
30
+ const transformed = options?.transformPlan === undefined ? plan : yield* options.transformPlan(plan);
31
+ // Every acceptance the plan proposes is classified against the accepted
32
+ // resolution here, so root, type, reinstall, and closure routes share one
33
+ // publisher-trust rule.
34
+ return yield* withPublisherTrust(transformed);
30
35
  });
31
36
  /**
32
37
  * Run the canonical install command workflow.
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@agentxm/extension-lifecycle",
3
- "version": "0.28.7-preview.1788522540.63ba4b89a",
4
- "description": "AXM extension-lifecycle feature: install, update, uninstall, enable, and disable policy across root and type-specific command forms for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
5
- "type": "module",
6
- "license": "FSL-1.1-MIT",
7
- "homepage": "https://axm.sh",
2
+ "author": "AgentXM <hello@agentxm.ai> (https://agentxm.ai)",
8
3
  "bugs": {
9
4
  "url": "https://github.com/agentxm/axm/issues"
10
5
  },
11
- "author": "AgentXM <hello@agentxm.ai> (https://agentxm.ai)",
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/agentxm/axm.git",
15
- "directory": "packages/extension-lifecycle"
6
+ "dependencies": {
7
+ "@agentxm/extension-model": "^0.28.8",
8
+ "@agentxm/extension-sources": "^0.28.8",
9
+ "@agentxm/extension-workspace": "^0.28.8",
10
+ "@agentxm/registry-client": "^0.28.8",
11
+ "@agentxm/registry-protocol": "^0.28.8",
12
+ "@agentxm/workspace-operations": "^0.28.8",
13
+ "@agentxm/workspace-state": "^0.28.8",
14
+ "effect": "4.0.0-rc.112",
15
+ "semver": "^7.8.5"
16
+ },
17
+ "description": "AXM extension-lifecycle feature: install, update, uninstall, enable, and disable policy across root and type-specific command forms for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
18
+ "devDependencies": {
19
+ "@effect/platform-node": "4.0.0-rc.112",
20
+ "@effect/vitest": "4.0.0-rc.112",
21
+ "@types/bun": "^1.3.14",
22
+ "@types/semver": "^7.7.1",
23
+ "@typescript/native": "npm:typescript@^7.0.2",
24
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
25
+ "vitest": "^4.1.10",
26
+ "yaml": "^2.9.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=22.19.0"
16
30
  },
17
- "sideEffects": false,
18
31
  "exports": {
19
32
  ".": {
20
- "types": "./dist/src/index.d.ts",
21
33
  "axm-source": "./src/index.ts",
22
- "default": "./dist/src/index.js"
34
+ "default": "./dist/src/index.js",
35
+ "types": "./dist/src/index.d.ts"
23
36
  },
24
37
  "./live": {
25
- "types": "./dist/src/live.d.ts",
26
38
  "axm-source": "./src/live.ts",
27
- "default": "./dist/src/live.js"
39
+ "default": "./dist/src/live.js",
40
+ "types": "./dist/src/live.d.ts"
28
41
  }
29
42
  },
30
43
  "files": [
31
44
  "dist/src/",
32
45
  "!**/*.map"
33
46
  ],
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "engines": {
38
- "node": ">=22.19.0"
39
- },
47
+ "homepage": "https://axm.sh",
48
+ "license": "FSL-1.1-MIT",
49
+ "name": "@agentxm/extension-lifecycle",
40
50
  "nx": {
41
51
  "includedScripts": []
42
52
  },
43
- "dependencies": {
44
- "effect": "4.0.0-rc.112",
45
- "semver": "^7.8.5",
46
- "@agentxm/extension-sources": "^0.28.7-preview.1788522540.63ba4b89a",
47
- "@agentxm/extension-model": "^0.28.7-preview.1788522540.63ba4b89a",
48
- "@agentxm/extension-workspace": "^0.28.7-preview.1788522540.63ba4b89a",
49
- "@agentxm/workspace-operations": "^0.28.7-preview.1788522540.63ba4b89a",
50
- "@agentxm/registry-client": "^0.28.7-preview.1788522540.63ba4b89a",
51
- "@agentxm/registry-protocol": "^0.28.7-preview.1788522540.63ba4b89a",
52
- "@agentxm/workspace-state": "^0.28.7-preview.1788522540.63ba4b89a"
53
+ "publishConfig": {
54
+ "access": "public"
53
55
  },
54
- "devDependencies": {
55
- "@effect/platform-node": "4.0.0-rc.112",
56
- "@effect/vitest": "4.0.0-rc.112",
57
- "@types/bun": "^1.3.14",
58
- "@types/semver": "^7.5.8",
59
- "@typescript/native": "npm:typescript@^7.0.2",
60
- "typescript": "npm:@typescript/typescript6@^6.0.2",
61
- "vitest": "^4.1.10",
62
- "yaml": "^2.9.0"
63
- }
64
- }
56
+ "repository": {
57
+ "directory": "packages/extension-lifecycle",
58
+ "type": "git",
59
+ "url": "https://github.com/agentxm/axm.git"
60
+ },
61
+ "sideEffects": false,
62
+ "type": "module",
63
+ "version": "0.28.8"
64
+ }