@try-works/dsh-memory 0.1.15 → 0.1.17

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/STATUS.md CHANGED
@@ -84,15 +84,34 @@ your automated tasks and is in no git repository. The host DELETES a task when i
84
84
  stops, so a task can vanish with nothing to observe; the backup runs on the sync cadence and a
85
85
  restore re-arms what is missing at the first session after boot.
86
86
 
87
- Verified on disk: 386 tests pass. `backupTasks` pointed at `E:\dsh-memory` wrote the layer for
88
- real — a second pass reported `written=0`, so it converges. `planRestore` against that layer and
89
- the live `schedule.json` returned `add=0 present=2`, which is the correct answer rather than an
90
- empty one: both tasks are in the live store, so a restore finds nothing to do.
91
-
92
- NOT verified until a restart: that the plugin itself runs either pass. The cadence
93
- (`maybeBackupTasks`) and the first-session restore (`maybeRestoreTasks`) have only been exercised
94
- through the wiring tests, where the schedule service is a stub and the store is temporary. Writing
95
- those files by calling the module is not the same claim as the plugin writing them.
87
+ Verified on disk: 396 tests pass, and `@try-works/dsh-memory@0.1.16` is published and installed
88
+ byte-identical.
89
+
90
+ - `backupTasks` against `E:\dsh-memory`: two cron tasks captured, a second pass `written=0`.
91
+ - The layer is at `global/tasks/`. It first shipped at `<root>/tasks`; every test used a
92
+ self-consistent temp root and could not see the difference (`learning-0213`).
93
+ - **The emptied-store drill, on a COPY of the real `schedule.json`:** emptied, backed up, restored
94
+ through the schedule plugin's own `createCronScheduleRecord`. Both tasks came back with their
95
+ expressions, zones and prompts (3597 and 2678 chars). The SECOND boot reported `add=0 present=2` —
96
+ which is the property that matters, since `create` mints a fresh id every time and id-based
97
+ de-duplication would duplicate every task on every boot.
98
+ - The record -> request -> record round trip holds against the plugin's own builder; only the id
99
+ differs.
100
+ - No task file leaks into memory search, checked by building the index over a store holding the layer.
101
+ - `SYSTEM.md` names the layer, its path, `memory_tasks`, and all eight states.
102
+
103
+ **NOT verified, and the log proves it.** `E:\dsh-memory\logs\memory.log` holds 3976 entries and
104
+ ZERO `tasks-backup` or `tasks-restore` ones, while the running host is 3h24m old and predates every
105
+ install from 0.1.10 on. The plugin is alive — it is injecting, capturing and consolidating — it has
106
+ simply never executed this code. The cadence and the boot restore have only ever run inside the
107
+ wiring tests, where the schedule service is a stub and the store is temporary. **Calling the module
108
+ directly is not the same claim as the plugin calling it.**
109
+
110
+ Seven defects were found by asking what the REAL path does, and not one by an existing test: the
111
+ empty-string config fallback, the wrong directory, a fixture-coincidence assertion, `orphaned` with
112
+ no producer, `skipped` never written while unknown-kind tasks were dropped from the backup, an
113
+ exits table read by nothing and wrong twice, and a manual restore that returned silently while
114
+ reporting success.
96
115
 
97
116
  Known limitation, by design: the host deletes a task when its owning session stops, so a restored
98
117
  task lives as long as the session that adopted it. The boot restore is what re-arms them.
@@ -126,7 +145,7 @@ the flag on did not change what the user sees.
126
145
 
127
146
  ### Blocked on a restart
128
147
 
129
- - [ ] **Restart, then check the tasks layer.** It writes to ANOTHER plugin's storage through
148
+ - [ ] **Restart, then check the tasks layer — THE BLOCKER.** It writes to ANOTHER plugin's storage through
130
149
  that plugin's service, so it deserves a real observation rather than a passing suite. After
131
150
  the restart, ask for `memory_tasks` and expect a report with `live: 2`, `backedUp: 2` and
132
151
  `pending.present: 2`. Then check `logs/memory.log` for a `tasks-backup` entry and a
@@ -183,7 +202,7 @@ Kept because the pattern matters more than the list: every one of these was foun
183
202
  | A test edit silently discarded | my own script re-read and overwrote the file before writing |
184
203
  ## Published
185
204
 
186
- **`@try-works/dsh-memory@0.1.10`** on npm (0.1.0 was the first release; the Memory tab and the
205
+ **`@try-works/dsh-memory@0.1.16`** on npm (0.1.0 was the first release; the Memory tab and the
187
206
  tasks layer each added several). The unscoped name `dsh-memory` was already taken by an
188
207
  unrelated DeepSeek Harness memory plugin, so the package publishes under the existing `@try-works`
189
208
  scope.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@try-works/dsh-memory",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Git-backed, workspace-scoped memory for the DeepSeek Harness (global layer + per-workspace layers)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -254,6 +254,45 @@ Sync failures are never swallowed: a failed pull or push is logged, recorded in
254
254
  surfaced by `status()`, reported by `memory_sync`, and leaves a `state/CONFLICT.md` marker that
255
255
  the next clean sync clears.
256
256
 
257
+ ## 9b. The scheduled tasks layer
258
+
259
+ `global/tasks/` is not a memory layer and holds no memories. It is a portable backup of the DSH
260
+ scheduled tasks on this machine, and it exists because the schedule store is the only copy of them:
261
+ it is in no git repository, and the host DELETES a task when the session that owns it stops. A task
262
+ can therefore vanish with nothing to observe.
263
+
264
+ One JSON file per task, plus a regenerated `index.md`. A file holds the task's record - id, kind,
265
+ title, prompt, and the selector that says when it runs - and its lifecycle state. The owning session
266
+ and the delivery history are deliberately NOT stored: they are machine-local, and carrying a session
267
+ id to another machine would make a restored task fail, because the session it names does not exist
268
+ there.
269
+
270
+ Because the task files live in this store and are not gitignored, a push carries them to your other
271
+ machines, where they are re-armed on boot.
272
+
273
+ The states, and what each one means:
274
+
275
+ | state | meaning |
276
+ | --- | --- |
277
+ | `active` | armed in the live store, waiting for its next occurrence |
278
+ | `pending` | in the backup, not in the live store; a restore re-arms it |
279
+ | `paused` | held back on purpose. Every automatic pass leaves it alone |
280
+ | `stopped` | was armed, and its owning session stopped, so the host deleted it |
281
+ | `failed` | was due and has still not delivered. The host records no failure of its own, so this is what is observable |
282
+ | `orphaned` | armed, but the session that owns it no longer exists, so it can never fire |
283
+ | `skipped` | captured, but its kind is not one we can re-arm, so a restore refuses it |
284
+ | `done` | delivered, or its recurrence is exhausted |
285
+
286
+ Two cadences, both off the write path. The **backup** runs with the sync cadence and is additive:
287
+ it never deletes, so a stale backup cannot wipe a schedule. The **restore** runs once per process at
288
+ the first real session, because a task needs a live session to own it, and it binds each restored
289
+ task to that session - you are never asked which one. It matches tasks by CONTENT, not by id, since
290
+ the schedule service mints a fresh id on every create.
291
+
292
+ `memory_tasks` reports the whole picture and can `backup`, `restore`, `pause` or `resume` on
293
+ demand. Holding a task back is a decision, and it outranks every automatic pass: a `paused` task is
294
+ never silently re-armed.
295
+
257
296
  ## 10. Current state of this store
258
297
 
259
298
  {{storeState}}
package/src/tasks.js CHANGED
@@ -361,9 +361,14 @@ function scheduleOf(record) {
361
361
  if (kind === 'cron') return 'cron ' + String(record.expression ?? '') + zone;
362
362
  if (kind === 'weekly') return 'weekly ' + JSON.stringify(record.weekly ?? {}) + zone;
363
363
  if (kind === 'daily') return 'daily ' + JSON.stringify(record.daily ?? {}) + zone;
364
- if (kind === 'every') return 'every ' + String(record.every_seconds ?? '') + 's';
364
+ // The same camelCase/snake_case confusion as `after` above, in the same function. Writing a test
365
+ // to pin ONE of them surfaced the other.
366
+ if (kind === 'every') return 'every ' + String(record.everySeconds ?? '') + 's';
365
367
  if (kind === 'at') return 'at ' + String(record.scheduledAt ?? '');
366
- if (kind === 'after') return 'after ' + String(record.after_seconds ?? '') + 's';
368
+ // `afterSeconds`, not `after_seconds` - the record uses camelCase, the CREATE REQUEST uses
369
+ // snake_case. The live index printed "after s" with the interval missing, which no test saw
370
+ // because no test rendered an `after` record.
371
+ if (kind === 'after') return 'after ' + String(record.afterSeconds ?? '') + 's';
367
372
  return kind;
368
373
  }
369
374