@try-works/dsh-memory 0.1.13 → 0.1.14

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/tasks.js +27 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@try-works/dsh-memory",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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",
package/src/tasks.js CHANGED
@@ -56,14 +56,22 @@ export const TASK_STATES = {
56
56
  * without one.
57
57
  */
58
58
  export const TASK_STATE_EXITS = {
59
- active: ['stopped', 'done', 'failed'],
60
- pending: ['active'],
59
+ // Two entries were missing, and NOTHING noticed until writeTask began reading this table.
60
+ //
61
+ // 'orphaned': the session holding a live task can disappear while the task itself does not
62
+ // change, so an armed task can become one that can never fire.
63
+ //
64
+ // 'paused' in every row: holding a task back is a DECISION the user makes about a task in any
65
+ // condition, not a lifecycle step. Writing the table without it left the entire pause path
66
+ // undeclared - the feature worked, and the declaration that was supposed to describe it did not.
67
+ active: ['stopped', 'done', 'failed', 'orphaned', 'paused'],
68
+ pending: ['active', 'paused'],
61
69
  paused: ['pending'],
62
- stopped: ['pending'],
63
- failed: ['active'],
64
- orphaned: ['pending'],
65
- skipped: ['active'],
66
- done: ['active'],
70
+ stopped: ['pending', 'paused'],
71
+ failed: ['active', 'paused'],
72
+ orphaned: ['pending', 'paused'],
73
+ skipped: ['active', 'paused'],
74
+ done: ['active', 'paused'],
67
75
  }
68
76
 
69
77
  const STATES = Object.values(TASK_STATES)
@@ -183,6 +191,18 @@ function ensureTasksDir(root) {
183
191
  export function writeTask(root, record, state, nowIso) {
184
192
  ensureTasksDir(root)
185
193
  const file = taskFilePath(root, record)
194
+ // ENFORCED, not documented. This table sat in the file unread since it was written, so a state
195
+ // could move anywhere and nothing objected - the same shape as the vocabulary states that had a
196
+ // test and no producer. Refusing here means a task cannot silently leave a state by a route
197
+ // nobody declared, which is what makes 'every resting state has a working exit' checkable
198
+ // rather than aspirational.
199
+ const previous = readTaskFile(file)
200
+ if (previous && previous.stateKnown && previous.state !== state) {
201
+ const allowed = TASK_STATE_EXITS[previous.state] ?? []
202
+ if (!allowed.includes(state)) {
203
+ throw new Error('tasks: refusing the transition ' + previous.state + ' -> ' + state + ' for "' + String(record.title ?? '') + '"; allowed from ' + previous.state + ': ' + allowed.join(', '))
204
+ }
205
+ }
186
206
  writeFileSync(file, JSON.stringify({ backedUpAt: nowIso, state, record }, null, 2) + '\n', 'utf8')
187
207
  return file;
188
208
  }