@wipcomputer/wip-ldm-os 0.4.87-alpha.1 → 0.4.87-alpha.3
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/bin/ldm.js +49 -39
- package/docs/backup/README.md +83 -80
- package/docs/backup/TECHNICAL.md +107 -90
- package/package.json +15 -3
- package/scripts/ldm-backup-engine.py +1176 -0
- package/scripts/ldm-backup-policy.json +1420 -0
- package/scripts/ldm-backup.sh +15 -273
- package/scripts/test-backup-engine.py +609 -0
- package/scripts/test-bin-manifest.mjs +43 -0
- package/scripts/test-ldm-install-preserves-foreign-bin.mjs +5 -0
- package/shared/docs/README.md.tmpl +1 -1
- package/shared/docs/directory-map.md.tmpl +1 -1
- package/shared/docs/how-backup-works.md.tmpl +50 -83
- package/shared/docs/system-directories.md.tmpl +1 -1
- package/shared/docs/what-is-dotldm.md.tmpl +1 -1
- package/src/bridge/openclaw.plugin.json +13 -0
- package/src/bridge/package.json +3 -0
|
@@ -102,6 +102,11 @@ exit 0
|
|
|
102
102
|
assert((readFileSync(shimPath).length > 0), 'foreign shim remains non-empty', output);
|
|
103
103
|
assert((statSync(shimPath).mode & 0o111) !== 0, 'foreign shim remains executable', output);
|
|
104
104
|
assert((existsSync(join(binDir, 'ldm-backup.sh'))), 'ldm-owned scripts still deploy into ~/.ldm/bin', output);
|
|
105
|
+
assert((existsSync(join(binDir, 'ldm-backup-engine.py'))), 'backup engine deploys into ~/.ldm/bin', output);
|
|
106
|
+
assert((existsSync(join(binDir, 'ldm-backup-policy.json'))), 'backup policy deploys into ~/.ldm/bin', output);
|
|
107
|
+
assert((statSync(join(binDir, 'ldm-backup-engine.py')).mode & 0o111) !== 0, 'backup engine is executable', output);
|
|
108
|
+
assert((statSync(join(binDir, 'ldm-backup-policy.json')).mode & 0o111) === 0, 'backup policy is not executable', output);
|
|
109
|
+
assert(readFileSync(join(binDir, 'ldm-backup-policy.json'), 'utf8') === readFileSync(join(repo, 'scripts', 'ldm-backup-policy.json'), 'utf8'), 'backup policy content matches package source', output);
|
|
105
110
|
|
|
106
111
|
const restored = readFileSync(shimPath);
|
|
107
112
|
assert(restored.length === Buffer.byteLength(shimContent), 'foreign shim size is unchanged', output);
|
|
@@ -16,7 +16,7 @@ If something is wrong, update `~/.ldm/config.json` or run `ldm install`. The doc
|
|
|
16
16
|
| `what-is-dotldm.md` | The `~/.ldm/` runtime directory explained |
|
|
17
17
|
| `directory-map.md` | What lives where and why |
|
|
18
18
|
| `how-worktrees-work.md` | Git worktrees, the _worktrees/ convention |
|
|
19
|
-
| `how-backup-works.md` |
|
|
19
|
+
| `how-backup-works.md` | Safe dry run, verified internal snapshots, and later-tier gates |
|
|
20
20
|
| `how-releases-work.md` | The full release pipeline |
|
|
21
21
|
| `how-install-works.md` | ldm install, the install prompt, dogfooding |
|
|
22
22
|
| `how-agents-work.md` | Agents, harnesses, bridge, memory |
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
hooks/ <- Claude Code hooks
|
|
44
44
|
state/ <- runtime state
|
|
45
45
|
tmp/ <- install staging
|
|
46
|
-
backups/
|
|
46
|
+
backups/v1/internal/ <- verified internal snapshots and in-progress staging
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Harness Directories (deployment targets)
|
|
@@ -1,111 +1,78 @@
|
|
|
1
1
|
# How Backup Works
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Safe planning first
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The backup engine has a strictly read-only dry run:
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
~/.ldm/bin/ldm-backup.sh --dry-run
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|--------|--------|-------------|
|
|
11
|
-
| `~/.ldm/memory/crystal.db` | sqlite3 .backup | Irreplaceable memory (all agents) |
|
|
12
|
-
| `~/.ldm/agents/` | cp -a | Identity files, journals, daily logs |
|
|
13
|
-
| `~/.ldm/state/` | cp -a | Config, version, registry |
|
|
14
|
-
| `~/.ldm/config.json` | cp | Workspace pointer, org |
|
|
15
|
-
| `~/.openclaw/memory/main.sqlite` | sqlite3 .backup | OC conversations |
|
|
16
|
-
| `~/.openclaw/memory/context-embeddings.sqlite` | sqlite3 .backup | Embeddings |
|
|
17
|
-
| `~/.openclaw/workspace/` | tar | Shared context, daily logs |
|
|
18
|
-
| `~/.openclaw/agents/main/sessions/` | tar | OC session JSONL |
|
|
19
|
-
| `~/.openclaw/openclaw.json` | cp | OC config |
|
|
20
|
-
| `~/.claude/CLAUDE.md` | cp | CC instructions |
|
|
21
|
-
| `~/.claude/settings.json` | cp | CC settings |
|
|
22
|
-
| `~/.claude/projects/` | tar | CC auto-memory + transcripts |
|
|
23
|
-
| `~/wipcomputerinc/` | tar (excludes node_modules, .git/objects, old backups, _trash) | Entire workspace |
|
|
11
|
+
It shows every policy source, classification, exclusion reason, per-source estimate, total estimated new bytes, available disk capacity, required reserve, planned staging path, planned internal destination, blocked requirements, and the final preflight verdict.
|
|
24
12
|
|
|
25
|
-
|
|
13
|
+
Dry run does not create files or directories. It does not acquire the persistent lock, write state, copy data, create a manifest, create a completion marker, rotate snapshots, write to Vault or iCloud, change the schedule, enable the LaunchAgent, or modify containment state. Sensitive filenames and contents are not printed.
|
|
26
14
|
|
|
27
|
-
|
|
15
|
+
Exit zero means the proposed run passes the same policy and preflight used by the real engine. Exit nonzero means a required source, encryption gate, destination, or disk reserve blocks the run.
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
~/.ldm/backups/2026-03-24--09-50-22/
|
|
31
|
-
ldm/
|
|
32
|
-
memory/crystal.db
|
|
33
|
-
agents/
|
|
34
|
-
state/
|
|
35
|
-
config.json
|
|
36
|
-
openclaw/
|
|
37
|
-
memory/main.sqlite
|
|
38
|
-
memory/context-embeddings.sqlite
|
|
39
|
-
workspace.tar
|
|
40
|
-
sessions.tar
|
|
41
|
-
openclaw.json
|
|
42
|
-
claude/
|
|
43
|
-
CLAUDE.md
|
|
44
|
-
settings.json
|
|
45
|
-
projects.tar
|
|
46
|
-
wipcomputerinc.tar
|
|
47
|
-
```
|
|
17
|
+
## Canonical source policy
|
|
48
18
|
|
|
49
|
-
|
|
19
|
+
`~/.ldm/bin/ldm-backup-policy.json` defines required, rebuildable, sensitive, blocked, manifest-only, and excluded sources. Estimation, capture, manifest generation, and exclusion records all use this one policy.
|
|
50
20
|
|
|
51
|
-
|
|
21
|
+
Sources that require encryption remain visibly blocked until that later gate ships. The engine never turns a blocked or missing required source into a partial success.
|
|
52
22
|
|
|
53
|
-
|
|
54
|
-
~/Library/Mobile Documents/com~apple~CloudDocs/wipcomputerinc-icloud/backups/
|
|
55
|
-
wipcomputerinc-lesa-2026-03-24--09-50-22.tar.gz
|
|
56
|
-
```
|
|
23
|
+
The policy has one explicit ID for every approved source-inventory row. The test suite compares the exact ID list so a future omission cannot silently pass.
|
|
57
24
|
|
|
58
|
-
|
|
25
|
+
## Runtime and changing files
|
|
59
26
|
|
|
60
|
-
|
|
27
|
+
The engine uses Python 3 standard library only. No third-party Python packages are required. If `python3` is unavailable, the runner exits with `Backup failed [python3-missing]`. Owner acceptance of Python 3 as a release prerequisite must be recorded separately at the release gate.
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
~/.ldm/bin/ldm-backup.sh # run backup now
|
|
64
|
-
~/.ldm/bin/ldm-backup.sh --dry-run # preview what would be backed up
|
|
65
|
-
~/.ldm/bin/ldm-backup.sh --keep 14 # keep 14 days instead of 7
|
|
66
|
-
~/.ldm/bin/ldm-backup.sh --include-secrets # include ~/.ldm/secrets/
|
|
67
|
-
```
|
|
29
|
+
A real run observes each source again immediately before capture. Changes since dry-run or initial planning are recorded and captured from the newer observation. Files and trees must then remain stable while that source is copied. Same-size changes are detected by checksum, and any during-capture mutation fails the run. SQLite uses the SQLite backup API for a transactionally consistent snapshot.
|
|
68
30
|
|
|
69
|
-
|
|
31
|
+
The engine recalculates the reserve before copying a changed source. It includes staged bytes, stat-only refreshed remaining sources, a full checksum observation of the source about to be copied, projected metadata, and the required 10 percent reserve. Growth that crosses the floor fails before that source is copied.
|
|
70
32
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
33
|
+
Manifest-only requirements must produce validated machine-readable evidence. An entry without an implemented generator is marked blocked before its source path is walked. When a required evidence generator is not implemented, including repo-aware Git/worktree and system inventories, the source stays blocked instead of being treated as fulfilled.
|
|
34
|
+
|
|
35
|
+
## Successful snapshot
|
|
36
|
+
|
|
37
|
+
A completed internal snapshot lives under:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
~/.ldm/backups/v1/internal/<snapshot-id>/
|
|
78
41
|
```
|
|
79
42
|
|
|
80
|
-
|
|
43
|
+
It is built first as `.in-progress-<snapshot-id>`. Promotion happens only after every required capture succeeds, SQLite copies pass `PRAGMA quick_check`, checksums validate, the manifest validates, and `completion.json` is written last.
|
|
44
|
+
|
|
45
|
+
Any required failure exits nonzero, writes no completion marker, and never prints `Backup complete`.
|
|
46
|
+
|
|
47
|
+
## Retention
|
|
48
|
+
|
|
49
|
+
The engine retains {{backup.keep}} unpinned verified snapshots plus every pinned snapshot. Pinned snapshots do not consume an unpinned retention slot. In-progress and unverified directories are never rotated.
|
|
50
|
+
|
|
51
|
+
## Structured state
|
|
52
|
+
|
|
53
|
+
Diagnostic state lives under `~/.ldm/state/backup/v1/`:
|
|
54
|
+
|
|
55
|
+
- `schedule.json`
|
|
56
|
+
- `attempt.json`
|
|
57
|
+
- `result.json`
|
|
58
|
+
- `success-local.json`
|
|
81
59
|
|
|
82
|
-
|
|
60
|
+
State records are atomic, but they are not completion proof. A snapshot requires a valid manifest and completion marker.
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|------|------|-----|
|
|
86
|
-
| Backup | 3:00 AM | LaunchAgent `ai.openclaw.ldm-backup` runs `~/.ldm/bin/ldm-backup.sh` |
|
|
62
|
+
Snapshot identifiers use UTC and end in `Z`. If no valid containment marker is present, schedule state records evidence status as `unknown`.
|
|
87
63
|
|
|
88
|
-
|
|
64
|
+
## Current boundaries
|
|
89
65
|
|
|
90
|
-
|
|
66
|
+
This engine correctness slice covers the internal tier. Vault, iCloud, monitoring, isolated restore, migration, and schedule resumption remain separately gated. Do not run a real backup until those gates are approved.
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
- `paths.workspace` ... workspace path
|
|
94
|
-
- `paths.icloudBackup` ... iCloud offsite destination
|
|
95
|
-
- `backup.keep` ... retention days (default: 7)
|
|
96
|
-
- `backup.includeSecrets` ... whether to include `~/.ldm/secrets/`
|
|
97
|
-
- `org` ... used for tar filename prefix
|
|
68
|
+
## Your system
|
|
98
69
|
|
|
99
|
-
|
|
70
|
+
**Internal destination:** `~/.ldm/backups/v1/internal/`
|
|
100
71
|
|
|
101
|
-
|
|
72
|
+
**Configured iCloud path:** `~/Library/Mobile Documents/com~apple~CloudDocs/wipcomputerinc-icloud/backups/` (not written by this engine slice)
|
|
102
73
|
|
|
103
|
-
|
|
74
|
+
**Schedule label:** `ai.openclaw.ldm-backup`
|
|
104
75
|
|
|
105
|
-
|
|
76
|
+
**Retention:** {{backup.keep}} unpinned verified snapshots
|
|
106
77
|
|
|
107
|
-
**
|
|
108
|
-
**iCloud offsite:** `~/Library/Mobile Documents/com~apple~CloudDocs/wipcomputerinc-icloud/backups/`
|
|
109
|
-
**Schedule:** 3:00 AM via LaunchAgent `ai.openclaw.ldm-backup`
|
|
110
|
-
**Retention:** {{backup.keep}} days local, {{backup.keep}} days iCloud
|
|
111
|
-
**Script:** `~/.ldm/bin/ldm-backup.sh` (deployed by `ldm install`)
|
|
78
|
+
**Runner:** `~/.ldm/bin/ldm-backup.sh`
|
|
@@ -33,7 +33,7 @@ Where LDM OS runs. Rules, commands, and skills are authored here and deployed to
|
|
|
33
33
|
| `~/.ldm/hooks/` | Claude Code hooks | ldm install |
|
|
34
34
|
| `~/.ldm/state/` | Runtime state | Tools (watermarks, markers) |
|
|
35
35
|
| `~/.ldm/tmp/` | Install staging | ldm install (cleaned after) |
|
|
36
|
-
| `~/.ldm/backups/` |
|
|
36
|
+
| `~/.ldm/backups/v1/internal/` | Verified internal snapshots and in-progress staging | ldm-backup.sh |
|
|
37
37
|
|
|
38
38
|
## Harness Directories (deployment targets)
|
|
39
39
|
|
|
@@ -41,7 +41,7 @@ The LDM OS runtime directory. Everything the system needs to run lives here. You
|
|
|
41
41
|
hooks/ Claude Code hooks
|
|
42
42
|
state/ runtime state (last release marker, watermarks)
|
|
43
43
|
tmp/ install staging (cleaned after install)
|
|
44
|
-
backups/
|
|
44
|
+
backups/v1/internal/ verified internal snapshots and in-progress staging
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
## What Gets Backed Up
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "lesa-bridge",
|
|
3
|
+
"name": "LDM Bridge",
|
|
4
|
+
"description": "Agent-to-agent communication and shared-memory access through LDM OS.",
|
|
5
|
+
"activation": {
|
|
6
|
+
"onStartup": true
|
|
7
|
+
},
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/bridge/package.json
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "WIP Bridge ... agent-to-agent communication, memory search, skill bridging. Core module of LDM OS.",
|
|
7
|
+
"openclaw": {
|
|
8
|
+
"extensions": ["./dist/openclaw.js"]
|
|
9
|
+
},
|
|
7
10
|
"dependencies": {
|
|
8
11
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
9
12
|
"better-sqlite3": "^11.8.1",
|