@microck/canonfig 2.0.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -0
  3. package/dist/agent/agent-resolution.errors.js +42 -0
  4. package/dist/agent/agent-resolution.layer.js +204 -0
  5. package/dist/agent/agent-resolution.service.js +2259 -0
  6. package/dist/agent/agent-resolution.types.js +1 -0
  7. package/dist/agent/controlled-executor.js +704 -0
  8. package/dist/agent/harness-adapters.js +85 -0
  9. package/dist/cli/cli.js +618 -0
  10. package/dist/cli/exit-codes.js +28 -0
  11. package/dist/cli/follower-commands.js +3 -0
  12. package/dist/cli/render.js +56 -0
  13. package/dist/cli/source-commands.js +5 -0
  14. package/dist/domain/brand.js +29 -0
  15. package/dist/domain/identity.js +31 -0
  16. package/dist/domain/npm-package-spec.js +186 -0
  17. package/dist/domain/profile.js +950 -0
  18. package/dist/domain/recipe-versions.js +297 -0
  19. package/dist/domain/resource.js +259 -0
  20. package/dist/domain/synchronization.js +346 -0
  21. package/dist/enrollment/enrollment.errors.js +43 -0
  22. package/dist/enrollment/enrollment.layer.js +724 -0
  23. package/dist/enrollment/enrollment.service.js +3 -0
  24. package/dist/enrollment/enrollment.types.js +59 -0
  25. package/dist/enrollment/follower-client.js +585 -0
  26. package/dist/enrollment/source-server.js +313 -0
  27. package/dist/machine/linux.layer.js +1183 -0
  28. package/dist/machine/machine-state.errors.js +52 -0
  29. package/dist/machine/machine-state.service.js +3 -0
  30. package/dist/machine/machine-state.types.js +1 -0
  31. package/dist/machine/macos.layer.js +470 -0
  32. package/dist/machine/windows.layer.js +879 -0
  33. package/dist/profile/discovery.js +740 -0
  34. package/dist/profile/profile-catalog.errors.js +50 -0
  35. package/dist/profile/profile-catalog.layer.js +20 -0
  36. package/dist/profile/profile-catalog.service.js +7 -0
  37. package/dist/profile/profile-codec.js +153 -0
  38. package/dist/profile/publication.js +298 -0
  39. package/dist/profile/tool-catalog.js +384 -0
  40. package/dist/runtime/doctor.js +306 -0
  41. package/dist/runtime/layers.js +706 -0
  42. package/dist/runtime/main.js +38 -0
  43. package/dist/schedule/linux-schedule.js +24 -0
  44. package/dist/schedule/macos-schedule.js +25 -0
  45. package/dist/schedule/schedule-manager.errors.js +17 -0
  46. package/dist/schedule/schedule-manager.layer.js +205 -0
  47. package/dist/schedule/schedule-manager.service.js +3 -0
  48. package/dist/schedule/schedule-manager.types.js +114 -0
  49. package/dist/schedule/windows-schedule.js +25 -0
  50. package/dist/state/state-repository.errors.js +55 -0
  51. package/dist/state/state-repository.layer.js +1507 -0
  52. package/dist/state/state-repository.service.js +3 -0
  53. package/dist/state/state-repository.types.js +1 -0
  54. package/dist/state/state-schema.js +298 -0
  55. package/dist/synchronization/config-codec.js +97 -0
  56. package/dist/synchronization/executor.js +700 -0
  57. package/dist/synchronization/follower-orchestration.js +939 -0
  58. package/dist/synchronization/follower-sync-config.js +81 -0
  59. package/dist/synchronization/npm-artifact.js +670 -0
  60. package/dist/synchronization/planner.js +378 -0
  61. package/dist/synchronization/recovery.js +397 -0
  62. package/dist/synchronization/resource-executors.js +1198 -0
  63. package/dist/synchronization/resource-plans.js +645 -0
  64. package/dist/synchronization/synchronization.errors.js +102 -0
  65. package/dist/synchronization/synchronization.layer.js +97 -0
  66. package/dist/synchronization/synchronization.service.js +11 -0
  67. package/dist/synchronization/synchronization.types.js +1 -0
  68. package/package.json +66 -0
@@ -0,0 +1,102 @@
1
+ import { Schema } from "effect";
2
+ export class DuplicatePlannerInputError extends Schema.TaggedError()("DuplicatePlannerInputError", {
3
+ collection: Schema.String,
4
+ id: Schema.String,
5
+ }) {
6
+ }
7
+ export class MissingDesiredResourceError extends Schema.TaggedError()("MissingDesiredResourceError", { resource: Schema.String }) {
8
+ }
9
+ export class MissingObservedResourceError extends Schema.TaggedError()("MissingObservedResourceError", { resource: Schema.String }) {
10
+ }
11
+ export class PlannerResourceKindMismatchError extends Schema.TaggedError()("PlannerResourceKindMismatchError", {
12
+ resource: Schema.String,
13
+ publishedKind: Schema.String,
14
+ desiredKind: Schema.String,
15
+ }) {
16
+ }
17
+ export class PlannerPolicyKindMismatchError extends Schema.TaggedError()("PlannerPolicyKindMismatchError", {
18
+ resource: Schema.String,
19
+ kind: Schema.String,
20
+ policy: Schema.String,
21
+ }) {
22
+ }
23
+ export class PlannerVerificationKindMismatchError extends Schema.TaggedError()("PlannerVerificationKindMismatchError", {
24
+ resource: Schema.String,
25
+ kind: Schema.String,
26
+ method: Schema.String,
27
+ }) {
28
+ }
29
+ export class PlannerVerificationContentMismatchError extends Schema.TaggedError()("PlannerVerificationContentMismatchError", {
30
+ resource: Schema.String,
31
+ kind: Schema.String,
32
+ method: Schema.String,
33
+ reason: Schema.String,
34
+ }) {
35
+ }
36
+ export class PlannerInvalidRecipeError extends Schema.TaggedError()("PlannerInvalidRecipeError", {
37
+ resource: Schema.String,
38
+ method: Schema.String,
39
+ package: Schema.String,
40
+ reason: Schema.String,
41
+ }) {
42
+ }
43
+ export class PlannerInvalidResourcePathError extends Schema.TaggedError()("PlannerInvalidResourcePathError", {
44
+ resource: Schema.String,
45
+ path: Schema.String,
46
+ reason: Schema.String,
47
+ }) {
48
+ }
49
+ export class PlannerConflictingResourcePathError extends Schema.TaggedError()("PlannerConflictingResourcePathError", {
50
+ resource: Schema.String,
51
+ path: Schema.String,
52
+ conflictsWith: Schema.String,
53
+ reason: Schema.String,
54
+ }) {
55
+ }
56
+ export class PlannerMissingDependencyError extends Schema.TaggedError()("PlannerMissingDependencyError", {
57
+ resource: Schema.String,
58
+ dependency: Schema.String,
59
+ }) {
60
+ }
61
+ export class PlannerDependencyCycleError extends Schema.TaggedError()("PlannerDependencyCycleError", { cycle: Schema.Array(Schema.String) }) {
62
+ }
63
+ export class MissingBlobMetadataError extends Schema.TaggedError()("MissingBlobMetadataError", {
64
+ resource: Schema.String,
65
+ blob: Schema.String,
66
+ }) {
67
+ }
68
+ export class InvalidObservedStateError extends Schema.TaggedError()("InvalidObservedStateError", {
69
+ resource: Schema.String,
70
+ kind: Schema.String,
71
+ observedState: Schema.String,
72
+ }) {
73
+ }
74
+ export class InvalidExecutionPlanError extends Schema.TaggedError()("InvalidExecutionPlanError", { message: Schema.String }) {
75
+ }
76
+ export class MissingExecutionResourceError extends Schema.TaggedError()("MissingExecutionResourceError", { resource: Schema.String }) {
77
+ }
78
+ export class MissingArtifactError extends Schema.TaggedError()("MissingArtifactError", { digest: Schema.String }) {
79
+ }
80
+ export class InvalidArtifactError extends Schema.TaggedError()("InvalidArtifactError", {
81
+ digest: Schema.String,
82
+ message: Schema.String,
83
+ }) {
84
+ }
85
+ export class ActionExecutionError extends Schema.TaggedError()("ActionExecutionError", {
86
+ action: Schema.String,
87
+ message: Schema.String,
88
+ }) {
89
+ }
90
+ export class RecoveryRunNotFoundError extends Schema.TaggedError()("RecoveryRunNotFoundError", { follower: Schema.String }) {
91
+ }
92
+ export class RecoveryIntegrityError extends Schema.TaggedError()("RecoveryIntegrityError", {
93
+ run: Schema.String,
94
+ message: Schema.String,
95
+ }) {
96
+ }
97
+ export class RollbackCleanupError extends Schema.TaggedError()("RollbackCleanupError", {
98
+ run: Schema.String,
99
+ outcome: Schema.String,
100
+ message: Schema.String,
101
+ }) {
102
+ }
@@ -0,0 +1,97 @@
1
+ import { Clock, Effect, Layer, Option } from "effect";
2
+ import { AgentResolution } from "../agent/agent-resolution.service.js";
3
+ import { MachineState } from "../machine/machine-state.service.js";
4
+ import { StateRepository } from "../state/state-repository.service.js";
5
+ import { cleanupRollbackSnapshots, executeSynchronizationPlan, executionFollower, executionRevision, } from "./executor.js";
6
+ import { recoverSynchronizationPlan } from "./recovery.js";
7
+ import { RollbackCleanupError } from "./synchronization.errors.js";
8
+ import { Synchronization } from "./synchronization.service.js";
9
+ const makeSynchronization = Effect.gen(function* () {
10
+ const repository = yield* StateRepository;
11
+ const machine = yield* MachineState;
12
+ const agentResolution = Option.getOrUndefined(yield* Effect.serviceOption(AgentResolution));
13
+ return Synchronization.of({
14
+ run: (input) => Effect.gen(function* () {
15
+ const follower = yield* executionFollower(input);
16
+ const revision = yield* executionRevision(input);
17
+ const startedAt = new Date(yield* Clock.currentTimeMillis).toISOString();
18
+ // startRun atomically persists the full plan and all pending actions.
19
+ // No MachineState operation is reachable before this succeeds.
20
+ yield* repository.startRun({
21
+ id: input.id,
22
+ follower,
23
+ revision,
24
+ plan: input.plan,
25
+ startedAt,
26
+ });
27
+ const result = yield* executeSynchronizationPlan({
28
+ ...input,
29
+ agentResolution: input.agentResolution ?? agentResolution,
30
+ }).pipe(Effect.provideService(StateRepository, repository), Effect.provideService(MachineState, machine), Effect.catch((error) => Effect.succeed({
31
+ outcome: {
32
+ outcome: "Failed",
33
+ run: input.id,
34
+ reason: String(error).slice(0, 2048),
35
+ },
36
+ appliedResources: [],
37
+ removedResources: [],
38
+ })));
39
+ const completedAt = new Date(yield* Clock.currentTimeMillis).toISOString();
40
+ yield* repository.completeRun({
41
+ run: input.id,
42
+ completedAt,
43
+ outcome: result.outcome,
44
+ appliedResources: result.appliedResources,
45
+ removedResources: result.removedResources,
46
+ });
47
+ if (result.outcome.outcome !== "Interrupted") {
48
+ yield* cleanupRollbackSnapshots(input.id, input.plan.actions.map((action) => action.id)).pipe(Effect.provideService(MachineState, machine), Effect.mapError((error) => new RollbackCleanupError({
49
+ run: input.id,
50
+ outcome: result.outcome.outcome,
51
+ message: String(error),
52
+ })));
53
+ }
54
+ return result.outcome;
55
+ }).pipe(Effect.onInterrupt(() => Effect.void)),
56
+ recover: (input) => Effect.gen(function* () {
57
+ const recovery = yield* repository.loadRecovery(input.follower);
58
+ const result = yield* recoverSynchronizationPlan({
59
+ ...input,
60
+ agentResolution: input.agentResolution ?? agentResolution,
61
+ }).pipe(Effect.provideService(StateRepository, repository), Effect.provideService(MachineState, machine));
62
+ yield* repository.completeRun({
63
+ run: result.outcome.run,
64
+ completedAt: new Date(yield* Clock.currentTimeMillis).toISOString(),
65
+ outcome: result.outcome,
66
+ appliedResources: result.appliedResources,
67
+ removedResources: result.removedResources,
68
+ });
69
+ if (result.outcome.outcome !== "Interrupted") {
70
+ yield* cleanupRollbackSnapshots(result.outcome.run, recovery?.run.plan.actions.map((action) => action.id) ?? []).pipe(Effect.provideService(MachineState, machine), Effect.mapError((error) => new RollbackCleanupError({
71
+ run: result.outcome.run,
72
+ outcome: result.outcome.outcome,
73
+ message: String(error),
74
+ })));
75
+ }
76
+ return result.outcome;
77
+ }).pipe(Effect.onInterrupt(() => Effect.gen(function* () {
78
+ const recovery = yield* repository.loadRecovery(input.follower);
79
+ if (recovery === undefined)
80
+ return;
81
+ yield* repository.completeRun({
82
+ run: recovery.run.id,
83
+ completedAt: new Date(yield* Clock.currentTimeMillis).toISOString(),
84
+ outcome: {
85
+ outcome: "Interrupted",
86
+ run: recovery.run.id,
87
+ completedActions: recovery.actions
88
+ .filter((event) => event.state === "succeeded" || event.state === "skipped")
89
+ .map((event) => event.action),
90
+ },
91
+ appliedResources: [],
92
+ });
93
+ }).pipe(Effect.ignore))),
94
+ });
95
+ });
96
+ export const SynchronizationLive = Layer.effect(Synchronization, makeSynchronization);
97
+ export const synchronizationLayer = SynchronizationLive;
@@ -0,0 +1,11 @@
1
+ import { Context } from "effect";
2
+ import { planSynchronization } from "./planner.js";
3
+ /** Pure synchronization planning boundary. C6 owns all execution side effects. */
4
+ export class SynchronizationPlanner extends Context.Service()("canonfig/synchronization/SynchronizationPlanner") {
5
+ }
6
+ export const synchronizationPlannerService = {
7
+ plan: planSynchronization,
8
+ };
9
+ /** Apply and crash-recovery boundary for recorded synchronization plans. */
10
+ export class Synchronization extends Context.Service()("canonfig/synchronization/Synchronization") {
11
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@microck/canonfig",
3
+ "version": "2.0.0",
4
+ "description": "one-way configuration synchronizer: publish a canonical AI agent setup from one Source Machine and converge Linux, macOS, and Windows Follower Machines",
5
+ "author": "Microck <contact@micr.dev>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "keywords": [
9
+ "canonfig",
10
+ "cli",
11
+ "sync",
12
+ "configuration",
13
+ "mcp",
14
+ "skills",
15
+ "ai-agents",
16
+ "effect"
17
+ ],
18
+ "bin": {
19
+ "canonfig": "dist/runtime/main.js"
20
+ },
21
+ "workspaces": [
22
+ "website"
23
+ ],
24
+ "files": [
25
+ "dist",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "engines": {
30
+ "node": ">=24"
31
+ },
32
+ "scripts": {
33
+ "build": "npm run build:cli && npm run website:build",
34
+ "build:cli": "tsc -p tsconfig.json",
35
+ "prepack": "npm run build:cli",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit && npm run website:typecheck",
37
+ "lint": "oxlint oxlint.config.ts vitest.config.ts tools/release src/agent src/cli src/domain src/enrollment src/machine src/profile src/runtime src/schedule src/state src/synchronization tests/agent-resolution.test.ts tests/acceptance tests/cli.test.ts tests/doctor.test.ts tests/foundation.test.ts tests/packed-cli.test.ts tests/profile-contract.test.ts tests/discovery.test.ts tests/publication.test.ts tests/state-repository.test.ts tests/machine-state-linux.test.ts tests/machine-state-macos.test.ts tests/machine-state-windows.test.ts tests/schedule-manager.test.ts tests/skills.test.ts tests/synchronization-planner.test.ts tests/contract/machine-state.contract.ts tests/contract/schedule-manager.contract.ts tests/integration/enrollment.test.ts tests/integration/transport.test.ts tests/integration/npm-artifact.test.ts tests/integration/synchronization-run.test.ts tests/integration/recovery.test.ts tests/integration/follower-orchestration.test.ts && npm run website:lint && npm run docs:validate && npm run skills:validate",
38
+ "test": "vitest run",
39
+ "test:acceptance": "vitest run tests/acceptance",
40
+ "dev": "tsx src/runtime/main.ts",
41
+ "docs:validate": "tsx website/scripts/validate-commands.ts",
42
+ "release:validate": "tsx tools/release/validate-release.ts",
43
+ "skills:validate": "tsx website/scripts/validate-skills.ts",
44
+ "website:dev": "npm run dev --workspace @canonfig/website",
45
+ "website:typecheck": "npm run typecheck --workspace @canonfig/website",
46
+ "website:lint": "npm run lint --workspace @canonfig/website",
47
+ "website:build": "npm run build --workspace @canonfig/website"
48
+ },
49
+ "dependencies": {
50
+ "@effect/platform-node": "4.0.0-rc.109",
51
+ "@effect/sql-sqlite-node": "4.0.0-rc.109",
52
+ "effect": "4.0.0-rc.109",
53
+ "selfsigned": "^5.5.0",
54
+ "smol-toml": "1.6.1",
55
+ "yaml": "^2.9.0"
56
+ },
57
+ "devDependencies": {
58
+ "@effect/vitest": "4.0.0-rc.109",
59
+ "@oxlint/plugins": "1.78.0",
60
+ "@types/node": "24.10.1",
61
+ "oxlint": "1.78.0",
62
+ "tsx": "4.20.6",
63
+ "typescript": "5.9.3",
64
+ "vitest": "4.1.10"
65
+ }
66
+ }