@agentxm/workspace-transactions 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 ADDED
@@ -0,0 +1,110 @@
1
+ # Functional Source License, Version 1.1, MIT Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-MIT
6
+
7
+ ## Notice
8
+
9
+ Copyright 2025-2026 AgentXM, Inc.
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the MIT license that is effective on the second anniversary of the date we make
91
+ the Software available. On or after that date, you may use the Software under
92
+ the MIT license, in which case the following will apply:
93
+
94
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
95
+ this software and associated documentation files (the "Software"), to deal in
96
+ the Software without restriction, including without limitation the rights to
97
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
98
+ of the Software, and to permit persons to whom the Software is furnished to do
99
+ so, subject to the following conditions:
100
+
101
+ The above copyright notice and this permission notice shall be included in all
102
+ copies or substantial portions of the Software.
103
+
104
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
105
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
106
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
107
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
108
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
109
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @agentxm/workspace-transactions
2
+
3
+ The AXM workspace transaction capability: the cross-process workspace
4
+ transition lock, the per-closure snapshot ledger with restoration and
5
+ verification, atomic single-file publication, and footprint observation. Every
6
+ workspace writer registers its target here before the first mutation, and
7
+ `workspace-operations` settles or rolls back each semantic closure through the
8
+ closure API this package exports for it alone.
9
+
10
+ The package root is the public API; deterministic in-memory admission and the
11
+ fault-injection hooks live behind `./testing`. The surface is unsupported and
12
+ may change in any release. Ordinary users should use the
13
+ [`axm` CLI](https://axm.sh) instead.
14
+
15
+ Part of the [AgentXM](https://agentxm.ai) toolchain.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared atomic single-file replacement: write a uniquely named temp file in
3
+ * the target's directory, then rename it over the target.
4
+ *
5
+ * The temp file lives next to the target so the rename stays on one
6
+ * filesystem (and therefore atomic), and is removed on any failure or
7
+ * interruption. Temp names follow `<target>.tmp.<unique>`; sweepers that
8
+ * clean stale temps (e.g. the lockfile writer) rely on that prefix.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import type * as FileSystem from "effect/FileSystem";
14
+ import * as Path from "effect/Path";
15
+ import type { PlatformError } from "effect/PlatformError";
16
+ /**
17
+ * Step at which an atomic write failed. `check-target` and `read-target` can
18
+ * only occur with `skipIfUnchanged: "fail-on-read-error"`.
19
+ *
20
+ * @experimental This API is unstable and may change without notice.
21
+ */
22
+ export type AtomicWriteStep = "check-target" | "read-target" | "write-temp" | "rename";
23
+ /**
24
+ * Failure passed to `mapError` so each call site keeps its own error shape.
25
+ *
26
+ * @experimental This API is unstable and may change without notice.
27
+ */
28
+ export interface AtomicWriteFailure {
29
+ readonly step: AtomicWriteStep;
30
+ readonly targetPath: string;
31
+ readonly tempPath: string;
32
+ readonly cause: PlatformError;
33
+ }
34
+ /**
35
+ * Content-equality short-circuit mode: `fail-on-read-error` surfaces
36
+ * check/read failures via `mapError`; `ignore-read-errors` treats an
37
+ * unreadable target as changed and proceeds to write.
38
+ *
39
+ * @experimental This API is unstable and may change without notice.
40
+ */
41
+ export type SkipIfUnchanged = "fail-on-read-error" | "ignore-read-errors";
42
+ /**
43
+ * Options for `writeFileAtomic`.
44
+ *
45
+ * @experimental This API is unstable and may change without notice.
46
+ */
47
+ export interface WriteFileAtomicOptions<E> {
48
+ readonly targetPath: string;
49
+ readonly content: string | Uint8Array;
50
+ /** When set, leave the target untouched if it already has this content. */
51
+ readonly skipIfUnchanged?: SkipIfUnchanged;
52
+ /** Best-effort remove of the target before rename (Windows cannot always rename over an existing file). */
53
+ readonly removeTargetBeforeRename?: boolean;
54
+ readonly mapError: (failure: AtomicWriteFailure) => E;
55
+ }
56
+ export declare const atomicWriteTempPrefix: (targetPath: string) => string;
57
+ /** Remove only stale temp siblings belonging to one atomic-write target. */
58
+ export declare const sweepStaleAtomicWriteTemps: (fs: FileSystem.FileSystem, targetPath: string) => Effect.Effect<void, never, Path.Path>;
59
+ /**
60
+ * Atomically replace `targetPath` with `content` via a same-directory temp
61
+ * file and rename. Failures at each step are mapped by the caller, so error
62
+ * codes, details, and suggestions stay call-site specific.
63
+ *
64
+ * @experimental This API is unstable and may change without notice.
65
+ */
66
+ export declare const writeFileAtomic: <E>(fs: FileSystem.FileSystem, options: WriteFileAtomicOptions<E>) => Effect.Effect<"written" | "skipped", E>;
67
+ //# sourceMappingURL=atomic-write.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Shared atomic single-file replacement: write a uniquely named temp file in
3
+ * the target's directory, then rename it over the target.
4
+ *
5
+ * The temp file lives next to the target so the rename stays on one
6
+ * filesystem (and therefore atomic), and is removed on any failure or
7
+ * interruption. Temp names follow `<target>.tmp.<unique>`; sweepers that
8
+ * clean stale temps (e.g. the lockfile writer) rely on that prefix.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as Option from "effect/Option";
14
+ import * as Path from "effect/Path";
15
+ /** In-process sequence so concurrent fibers never share a temp path. */
16
+ let tempSequence = 0;
17
+ export const atomicWriteTempPrefix = (targetPath) => `${targetPath}.tmp.`;
18
+ /** Remove only stale temp siblings belonging to one atomic-write target. */
19
+ export const sweepStaleAtomicWriteTemps = (fs, targetPath) => Effect.gen(function* () {
20
+ const path = yield* Path.Path;
21
+ const parent = path.dirname(targetPath);
22
+ const filenamePrefix = `${path.basename(targetPath)}.tmp.`;
23
+ const entries = yield* fs.readDirectory(parent).pipe(Effect.orElseSucceed(() => []));
24
+ yield* Effect.forEach(entries.filter((entry) => entry.startsWith(filenamePrefix)), (entry) => fs.remove(path.join(parent, entry), { force: true }).pipe(Effect.ignore), { discard: true });
25
+ });
26
+ const makeTempPath = (targetPath) => {
27
+ tempSequence += 1;
28
+ const pid = typeof process === "object" ? process.pid.toString(36) : "x";
29
+ const random = Math.random().toString(36).slice(2, 8);
30
+ return `${atomicWriteTempPrefix(targetPath)}${pid}.${tempSequence.toString(36)}.${random}`;
31
+ };
32
+ const bytesEqual = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
33
+ const targetHasContent = (fs, targetPath, content) => typeof content === "string"
34
+ ? Effect.map(fs.readFileString(targetPath), (current) => current === content)
35
+ : Effect.map(fs.readFile(targetPath), (current) => bytesEqual(current, content));
36
+ /**
37
+ * Atomically replace `targetPath` with `content` via a same-directory temp
38
+ * file and rename. Failures at each step are mapped by the caller, so error
39
+ * codes, details, and suggestions stay call-site specific.
40
+ *
41
+ * @experimental This API is unstable and may change without notice.
42
+ */
43
+ export const writeFileAtomic = (fs, options) => Effect.gen(function* () {
44
+ const { content, mapError, targetPath } = options;
45
+ const tempPath = makeTempPath(targetPath);
46
+ const fail = (step) => (cause) => mapError({ step, targetPath, tempPath, cause });
47
+ if (options.skipIfUnchanged === "fail-on-read-error") {
48
+ const exists = yield* fs.exists(targetPath).pipe(Effect.mapError(fail("check-target")));
49
+ if (exists) {
50
+ const unchanged = yield* targetHasContent(fs, targetPath, content).pipe(Effect.mapError(fail("read-target")));
51
+ if (unchanged)
52
+ return "skipped";
53
+ }
54
+ }
55
+ else if (options.skipIfUnchanged === "ignore-read-errors") {
56
+ const unchanged = yield* targetHasContent(fs, targetPath, content).pipe(Effect.option);
57
+ if (Option.isSome(unchanged) && unchanged.value)
58
+ return "skipped";
59
+ }
60
+ yield* Effect.gen(function* () {
61
+ yield* (typeof content === "string"
62
+ ? fs.writeFileString(tempPath, content)
63
+ : fs.writeFile(tempPath, content)).pipe(Effect.mapError(fail("write-temp")));
64
+ if (options.removeTargetBeforeRename === true) {
65
+ yield* fs.remove(targetPath).pipe(Effect.ignore);
66
+ }
67
+ yield* fs.rename(tempPath, targetPath).pipe(Effect.mapError(fail("rename")));
68
+ }).pipe(Effect.ensuring(fs.remove(tempPath, { force: true }).pipe(Effect.ignore)));
69
+ return "written";
70
+ });
71
+ //# sourceMappingURL=atomic-write.js.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The ambient transaction context and the write registration primitives
3
+ * every workspace writer calls.
4
+ *
5
+ * `protectWorkspacePath` and `protectCreatedAncestors` snapshot a path before
6
+ * its first mutation within the active closure. The context itself — the
7
+ * ledger reference and the two ambient references that carry it — is
8
+ * package-private: the transaction runner in `./transaction.ts` constructs
9
+ * and provides it, and nothing outside this package can reach the ledger.
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ */
13
+ import * as ServiceMap from "effect/Context";
14
+ import * as Effect from "effect/Effect";
15
+ import * as FileSystem from "effect/FileSystem";
16
+ import * as Option from "effect/Option";
17
+ import * as Path from "effect/Path";
18
+ import * as SynchronizedRef from "effect/SynchronizedRef";
19
+ import { WorkspaceSnapshotError } from "./errors.js";
20
+ import { type TransactionLedger } from "./ledger.js";
21
+ export interface WorkspaceTransactionContext {
22
+ readonly isTransitionCompromised: () => boolean;
23
+ readonly fs: FileSystem.FileSystem;
24
+ readonly path: Path.Path;
25
+ readonly workspaceDir: string;
26
+ /** The one serialized record of preimages, closures, and pending restorations. */
27
+ readonly ledger: SynchronizedRef.SynchronizedRef<TransactionLedger>;
28
+ }
29
+ /** The active transaction, when one is running on this fiber's context. */
30
+ export declare const CurrentWorkspaceTransaction: ServiceMap.Reference<Option.Option<WorkspaceTransactionContext>>;
31
+ /**
32
+ * The semantic closure whose mutations are currently executing. Snapshots
33
+ * taken while a closure is active belong to it: they are dropped when the
34
+ * closure settles and restored when it — and only it — rolls back. Snapshots
35
+ * taken outside any closure belong to the operation closure and are restored
36
+ * by the transaction's own failure handling.
37
+ */
38
+ export declare const CurrentWorkspaceClosure: ServiceMap.Reference<string | undefined>;
39
+ /**
40
+ * Take one target's preimage into the ledger, deduplicating on first touch
41
+ * per closure. One serialized ledger transition: the snapshot store is
42
+ * created, the backup name allocated, and the bytes copied while the ledger
43
+ * is held, so no concurrent registration or rollback observes a half-taken
44
+ * snapshot.
45
+ */
46
+ export declare const protectInContext: (context: WorkspaceTransactionContext, target: string, closure: string | undefined) => Effect.Effect<void, WorkspaceSnapshotError>;
47
+ /** Snapshot a path before its first mutation when a workspace transaction is active. */
48
+ export declare const protectWorkspacePath: (target: string) => Effect.Effect<void, WorkspaceSnapshotError>;
49
+ /**
50
+ * Protect the first ancestor a recursive directory creation is about to
51
+ * create, so restoration removes the created directory chain instead of
52
+ * leaving empty parents behind. No-op outside a transaction or when the
53
+ * directory already exists.
54
+ */
55
+ export declare const protectCreatedAncestors: (fs: FileSystem.FileSystem, path: Path.Path, directory: string) => Effect.Effect<void, WorkspaceSnapshotError>;
56
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The ambient transaction context and the write registration primitives
3
+ * every workspace writer calls.
4
+ *
5
+ * `protectWorkspacePath` and `protectCreatedAncestors` snapshot a path before
6
+ * its first mutation within the active closure. The context itself — the
7
+ * ledger reference and the two ambient references that carry it — is
8
+ * package-private: the transaction runner in `./transaction.ts` constructs
9
+ * and provides it, and nothing outside this package can reach the ledger.
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ */
13
+ import * as ServiceMap from "effect/Context";
14
+ import * as Effect from "effect/Effect";
15
+ import * as FileSystem from "effect/FileSystem";
16
+ import * as Option from "effect/Option";
17
+ import * as Path from "effect/Path";
18
+ import * as SynchronizedRef from "effect/SynchronizedRef";
19
+ import { WorkspaceSnapshotError } from "./errors.js";
20
+ import { isProtected, withSnapshot } from "./ledger.js";
21
+ /** The active transaction, when one is running on this fiber's context. */
22
+ export const CurrentWorkspaceTransaction = ServiceMap.Reference("@agentxm/workspace-transactions/CurrentWorkspaceTransaction", {
23
+ defaultValue: () => Option.none(),
24
+ });
25
+ /**
26
+ * The semantic closure whose mutations are currently executing. Snapshots
27
+ * taken while a closure is active belong to it: they are dropped when the
28
+ * closure settles and restored when it — and only it — rolls back. Snapshots
29
+ * taken outside any closure belong to the operation closure and are restored
30
+ * by the transaction's own failure handling.
31
+ */
32
+ export const CurrentWorkspaceClosure = ServiceMap.Reference("@agentxm/workspace-transactions/CurrentWorkspaceClosure", { defaultValue: () => undefined });
33
+ /**
34
+ * Take one target's preimage into the ledger, deduplicating on first touch
35
+ * per closure. One serialized ledger transition: the snapshot store is
36
+ * created, the backup name allocated, and the bytes copied while the ledger
37
+ * is held, so no concurrent registration or rollback observes a half-taken
38
+ * snapshot.
39
+ */
40
+ export const protectInContext = (context, target, closure) => SynchronizedRef.updateEffect(context.ledger, (ledger) => Effect.gen(function* () {
41
+ const { fs, path } = context;
42
+ const normalized = path.resolve(target);
43
+ // First-touch dedupe is per closure: a later closure touching a target
44
+ // an earlier closure already committed needs its own — post-commit —
45
+ // preimage, so restoring it undoes only that closure's work.
46
+ if (isProtected(ledger, closure, normalized))
47
+ return ledger;
48
+ const link = yield* fs.readLink(normalized).pipe(Effect.option);
49
+ if (Option.isSome(link)) {
50
+ const snapshot = {
51
+ closure,
52
+ target: normalized,
53
+ state: "symlink",
54
+ linkTarget: link.value,
55
+ };
56
+ return withSnapshot(ledger, snapshot, ledger);
57
+ }
58
+ const exists = yield* fs
59
+ .exists(normalized)
60
+ .pipe(Effect.mapError((cause) => new WorkspaceSnapshotError({ target: normalized, step: "inspect-target", cause })));
61
+ if (!exists) {
62
+ return withSnapshot(ledger, { closure, target: normalized, state: "absent" }, ledger);
63
+ }
64
+ const snapshotDir = ledger.snapshotDir ??
65
+ (yield* fs
66
+ .makeTempDirectory({ prefix: "axm-rollback-" })
67
+ .pipe(Effect.mapError((cause) => new WorkspaceSnapshotError({ target: normalized, step: "create-store", cause }))));
68
+ const backup = path.join(snapshotDir, `${ledger.snapshotSequence}.snap`);
69
+ // The pre-change bytes are preserved before the path is first mutated;
70
+ // a path that cannot be snapshotted is never mutated.
71
+ yield* fs
72
+ .copy(normalized, backup, { preserveTimestamps: true })
73
+ .pipe(Effect.mapError((cause) => new WorkspaceSnapshotError({ target: normalized, step: "copy", cause })));
74
+ return withSnapshot(ledger, { closure, target: normalized, state: "copied", backup }, { snapshotDir, snapshotSequence: ledger.snapshotSequence + 1 });
75
+ }));
76
+ /** Snapshot a path before its first mutation when a workspace transaction is active. */
77
+ export const protectWorkspacePath = (target) => Effect.gen(function* () {
78
+ const current = yield* CurrentWorkspaceTransaction;
79
+ if (Option.isNone(current))
80
+ return;
81
+ const closure = yield* CurrentWorkspaceClosure;
82
+ yield* protectInContext(current.value, target, closure);
83
+ });
84
+ /**
85
+ * Protect the first ancestor a recursive directory creation is about to
86
+ * create, so restoration removes the created directory chain instead of
87
+ * leaving empty parents behind. No-op outside a transaction or when the
88
+ * directory already exists.
89
+ */
90
+ export const protectCreatedAncestors = (fs, path, directory) => CurrentWorkspaceTransaction.pipe(Effect.flatMap(Option.match({
91
+ onNone: () => Effect.void,
92
+ onSome: (context) => Effect.gen(function* () {
93
+ let firstMissing;
94
+ let current = path.resolve(directory);
95
+ while (true) {
96
+ const exists = yield* fs.exists(current).pipe(Effect.mapError((cause) => new WorkspaceSnapshotError({
97
+ target: current,
98
+ step: "inspect-ancestor",
99
+ cause,
100
+ })));
101
+ if (exists)
102
+ break;
103
+ firstMissing = current;
104
+ const parent = path.dirname(current);
105
+ if (parent === current)
106
+ break;
107
+ current = parent;
108
+ }
109
+ if (firstMissing !== undefined) {
110
+ const closure = yield* CurrentWorkspaceClosure;
111
+ yield* protectInContext(context, firstMissing, closure);
112
+ }
113
+ }),
114
+ })));
115
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Typed failure vocabulary of the workspace transaction and transition-lock
3
+ * machinery, plus the holder and contention facts the lock records. The
4
+ * application error boundary owns rendering, codes, and suggestions for every
5
+ * failure declared here.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import type * as Cause from "effect/Cause";
10
+ import type * as Option from "effect/Option";
11
+ /** Identity an invocation records while it holds the workspace transition. */
12
+ export interface TransitionLockHolder {
13
+ readonly command: string;
14
+ readonly pid: number;
15
+ readonly candidateId?: string;
16
+ }
17
+ export interface TransitionContention {
18
+ /** The holder recorded by the invocation that owns the lock, when readable. */
19
+ readonly holder: Option.Option<TransitionLockHolder>;
20
+ readonly waitedMillis: number;
21
+ }
22
+ declare const WorkspaceRestorationIncomplete_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]; }>) => Cause.YieldableError & {
23
+ readonly _tag: "WorkspaceRestorationIncomplete";
24
+ } & Readonly<A>;
25
+ /**
26
+ * Restoration did not complete: the typed fact the terminal resolution
27
+ * derives outcome, disposition, and exit status from. The pre-change
28
+ * snapshots survive in the OS-temporary snapshot directory; nothing about
29
+ * this failure persists in the workspace, and the next mutation converges
30
+ * from the current workspace state.
31
+ */
32
+ export declare class WorkspaceRestorationIncomplete extends WorkspaceRestorationIncomplete_base<{
33
+ readonly terminationCause: "failure" | "interruption";
34
+ readonly transitionCause: Cause.Cause<unknown>;
35
+ readonly restorationCause: unknown;
36
+ /** OS-temporary directory preserving the pre-change snapshots, when any were taken. */
37
+ readonly snapshotDir: string | undefined;
38
+ /** Protected paths, workspace-root-relative where possible, left as the failure left them. */
39
+ readonly retained: ReadonlyArray<string>;
40
+ /** Closures whose rollback did not complete, when closure-scoped. */
41
+ readonly closureIds?: ReadonlyArray<string>;
42
+ }> {
43
+ }
44
+ declare const WorkspaceSnapshotError_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]; }>) => Cause.YieldableError & {
45
+ readonly _tag: "WorkspaceSnapshotError";
46
+ } & Readonly<A>;
47
+ /**
48
+ * Registering a path with the active transaction failed: the pre-mutation
49
+ * preimage could not be taken, so the path was never mutated. `target` is the
50
+ * path being protected; the `create-store` step's message interpolates
51
+ * nothing.
52
+ */
53
+ export declare class WorkspaceSnapshotError extends WorkspaceSnapshotError_base<{
54
+ readonly target: string;
55
+ readonly step: "inspect-target" | "create-store" | "copy" | "inspect-ancestor";
56
+ readonly cause: unknown;
57
+ }> {
58
+ }
59
+ declare const WorkspaceDirectoryError_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]; }>) => Cause.YieldableError & {
60
+ readonly _tag: "WorkspaceDirectoryError";
61
+ } & Readonly<A>;
62
+ /** Preparing the workspace state directory for a transition failed. */
63
+ export declare class WorkspaceDirectoryError extends WorkspaceDirectoryError_base<{
64
+ readonly path: string;
65
+ readonly step: "inspect" | "create";
66
+ readonly cause: unknown;
67
+ }> {
68
+ }
69
+ declare const TransitionLockError_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]; }>) => Cause.YieldableError & {
70
+ readonly _tag: "TransitionLockError";
71
+ } & Readonly<A>;
72
+ /**
73
+ * Workspace transition-lock mechanics failed. `path` carries the fact each
74
+ * step's message interpolates: the scratch directory for `create-scratch`,
75
+ * the lock path otherwise. `missing-timestamp` has no underlying cause.
76
+ */
77
+ export declare class TransitionLockError extends TransitionLockError_base<{
78
+ readonly path: string;
79
+ readonly step: "create-scratch" | "acquire" | "record-holder" | "inspect-timestamp" | "missing-timestamp" | "preserve-timestamp" | "release";
80
+ readonly cause?: unknown;
81
+ }> {
82
+ }
83
+ declare const TransitionLockUnavailable_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]; }>) => Cause.YieldableError & {
84
+ readonly _tag: "TransitionLockUnavailable";
85
+ } & Readonly<A>;
86
+ /**
87
+ * The bounded contention wait elapsed while another invocation held the
88
+ * workspace transition.
89
+ */
90
+ export declare class TransitionLockUnavailable extends TransitionLockUnavailable_base<{
91
+ readonly holder: TransitionLockHolder | undefined;
92
+ readonly waitedMillis: number;
93
+ }> {
94
+ }
95
+ declare const WorkspaceTransitionCompromised_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]; }>) => Cause.YieldableError & {
96
+ readonly _tag: "WorkspaceTransitionCompromised";
97
+ } & Readonly<A>;
98
+ /**
99
+ * The hold is no longer provably owned: ownership could not be confirmed
100
+ * within the staleness window, so a contender may already have reclaimed the
101
+ * lock. Any further durable write by the original owner — mutation and
102
+ * restoration alike — could overwrite a successor's work.
103
+ */
104
+ export declare class WorkspaceTransitionCompromised extends WorkspaceTransitionCompromised_base<{
105
+ readonly workspaceDir: string;
106
+ readonly lockPath: string;
107
+ readonly cause: unknown;
108
+ }> {
109
+ }
110
+ declare const WorkspaceRestorationError_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]; }>) => Cause.YieldableError & {
111
+ readonly _tag: "WorkspaceRestorationError";
112
+ } & Readonly<A>;
113
+ /**
114
+ * One restoration step did not complete or verify. Never a channel failure:
115
+ * it travels as the `restorationCause` inside
116
+ * {@link WorkspaceRestorationIncomplete} and the pending closure records.
117
+ */
118
+ export declare class WorkspaceRestorationError extends WorkspaceRestorationError_base<{
119
+ readonly target: string;
120
+ readonly step: "stage" | "stopped" | "verify";
121
+ readonly cause: unknown;
122
+ }> {
123
+ }
124
+ /** Failures acquiring the workspace transition lock. */
125
+ export type WorkspaceTransitionAcquireFailure = WorkspaceDirectoryError | TransitionLockError;
126
+ /** Failures the transaction machinery itself produces, beside the transition's own. */
127
+ export type WorkspaceTransactionFailure = WorkspaceSnapshotError | WorkspaceDirectoryError | TransitionLockError | TransitionLockUnavailable | WorkspaceTransitionCompromised;
128
+ export {};
129
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Typed failure vocabulary of the workspace transaction and transition-lock
3
+ * machinery, plus the holder and contention facts the lock records. The
4
+ * application error boundary owns rendering, codes, and suggestions for every
5
+ * failure declared here.
6
+ *
7
+ * @experimental This API is unstable and may change without notice.
8
+ */
9
+ import * as Data from "effect/Data";
10
+ /**
11
+ * Restoration did not complete: the typed fact the terminal resolution
12
+ * derives outcome, disposition, and exit status from. The pre-change
13
+ * snapshots survive in the OS-temporary snapshot directory; nothing about
14
+ * this failure persists in the workspace, and the next mutation converges
15
+ * from the current workspace state.
16
+ */
17
+ export class WorkspaceRestorationIncomplete extends Data.TaggedError("WorkspaceRestorationIncomplete") {
18
+ }
19
+ /**
20
+ * Registering a path with the active transaction failed: the pre-mutation
21
+ * preimage could not be taken, so the path was never mutated. `target` is the
22
+ * path being protected; the `create-store` step's message interpolates
23
+ * nothing.
24
+ */
25
+ export class WorkspaceSnapshotError extends Data.TaggedError("WorkspaceSnapshotError") {
26
+ }
27
+ /** Preparing the workspace state directory for a transition failed. */
28
+ export class WorkspaceDirectoryError extends Data.TaggedError("WorkspaceDirectoryError") {
29
+ }
30
+ /**
31
+ * Workspace transition-lock mechanics failed. `path` carries the fact each
32
+ * step's message interpolates: the scratch directory for `create-scratch`,
33
+ * the lock path otherwise. `missing-timestamp` has no underlying cause.
34
+ */
35
+ export class TransitionLockError extends Data.TaggedError("TransitionLockError") {
36
+ }
37
+ /**
38
+ * The bounded contention wait elapsed while another invocation held the
39
+ * workspace transition.
40
+ */
41
+ export class TransitionLockUnavailable extends Data.TaggedError("TransitionLockUnavailable") {
42
+ }
43
+ /**
44
+ * The hold is no longer provably owned: ownership could not be confirmed
45
+ * within the staleness window, so a contender may already have reclaimed the
46
+ * lock. Any further durable write by the original owner — mutation and
47
+ * restoration alike — could overwrite a successor's work.
48
+ */
49
+ export class WorkspaceTransitionCompromised extends Data.TaggedError("WorkspaceTransitionCompromised") {
50
+ }
51
+ /**
52
+ * One restoration step did not complete or verify. Never a channel failure:
53
+ * it travels as the `restorationCause` inside
54
+ * {@link WorkspaceRestorationIncomplete} and the pending closure records.
55
+ */
56
+ export class WorkspaceRestorationError extends Data.TaggedError("WorkspaceRestorationError") {
57
+ }
58
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Footprint recorder — observed durable changes.
3
+ *
4
+ * The layers that touch durable state record what they actually committed,
5
+ * removed, or restored here; the operation resolution's footprint is these
6
+ * observations, not planner-claimed artifact paths. Recording is a no-op when
7
+ * no recorder is provided, so the writers stay usable outside an operation
8
+ * boundary; reading requires the recorder, because only an operation
9
+ * boundary has a footprint to report.
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ */
13
+ import * as Effect from "effect/Effect";
14
+ import * as Ref from "effect/Ref";
15
+ import * as ServiceMap from "effect/Context";
16
+ export interface FootprintObservation {
17
+ /** Absolute path of the durable change. */
18
+ readonly path: string;
19
+ readonly change: "created" | "modified" | "removed" | "restored";
20
+ }
21
+ declare const FootprintRecorder_base: ServiceMap.ServiceClass<FootprintRecorder, "@agentxm/workspace-transactions/FootprintRecorder", {
22
+ readonly ref: Ref.Ref<ReadonlyArray<FootprintObservation>>;
23
+ }>;
24
+ export declare class FootprintRecorder extends FootprintRecorder_base {
25
+ }
26
+ /** Record one observed durable change. No-op without a recorder. */
27
+ export declare const recordFootprint: (observation: FootprintObservation) => Effect.Effect<void>;
28
+ /** Every durable change observed so far by the operation's recorder. */
29
+ export declare const readFootprint: Effect.Effect<ReadonlyArray<FootprintObservation>, never, FootprintRecorder>;
30
+ export declare const makeFootprintRecorder: Effect.Effect<ServiceMap.Service.Shape<typeof FootprintRecorder>>;
31
+ export {};
32
+ //# sourceMappingURL=footprint-recorder.d.ts.map