@bluewombat/work-ledger 0.8.0 → 0.10.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.
- package/README.md +1 -1
- package/SPECS.md +120 -94
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ledger/feature-commands.d.ts +71 -0
- package/dist/ledger/feature-commands.d.ts.map +1 -0
- package/dist/ledger/feature-commands.js +272 -0
- package/dist/ledger/feature-commands.js.map +1 -0
- package/dist/ledger/open-work-ledger.d.ts +38 -55
- package/dist/ledger/open-work-ledger.d.ts.map +1 -1
- package/dist/ledger/open-work-ledger.js +228 -300
- package/dist/ledger/open-work-ledger.js.map +1 -1
- package/dist/ledger/results.d.ts +11 -0
- package/dist/ledger/results.d.ts.map +1 -0
- package/dist/ledger/results.js +4 -0
- package/dist/ledger/results.js.map +1 -0
- package/dist/persist/port.d.ts +4 -4
- package/dist/persist/port.d.ts.map +1 -1
- package/dist/records.d.ts +122 -41
- package/dist/records.d.ts.map +1 -1
- package/dist/records.js +6 -1
- package/dist/records.js.map +1 -1
- package/package.json +2 -2
- package/dist/ledger/check-plan.d.ts +0 -17
- package/dist/ledger/check-plan.d.ts.map +0 -1
- package/dist/ledger/check-plan.js +0 -73
- package/dist/ledger/check-plan.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WorkLedger
|
|
2
2
|
|
|
3
|
-
Kernel package: the durable store of
|
|
3
|
+
Kernel package: the durable store of TicketStandards, states,
|
|
4
4
|
Attempts, and Traces. Glue writes here before it acts.
|
|
5
5
|
|
|
6
6
|
Behavioural contract: [SPECS.md](./SPECS.md). Persistence adapters live under
|
package/SPECS.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Kernel package, not a Transformer. It has many operations, not one IN and one
|
|
4
4
|
OUT. Its code, identifiers, comments, and messages name only what it owns:
|
|
5
|
-
|
|
5
|
+
Ticket records, Attempts, Traces, states, bails, and the Persistence
|
|
6
6
|
Port. No Transformer is named inside that perimeter, because none of them exist
|
|
7
7
|
there.
|
|
8
8
|
|
|
9
|
-
WorkLedger is the durable store of
|
|
10
|
-
|
|
9
|
+
WorkLedger is the durable store of TicketStandards, states, Attempts, and
|
|
10
|
+
Traces. It is the system's source of truth. Glue writes here **before** it
|
|
11
11
|
acts. Isolated workspaces are a consequence of that state, never the source of
|
|
12
12
|
it.
|
|
13
13
|
|
|
14
14
|
WorkLedger does not isolate a directory, run a Builder, fold a Child, or report
|
|
15
|
-
to a
|
|
15
|
+
to a TicketTracker. It accepts an already-normalized TicketStandard (or an
|
|
16
16
|
`invalid` verdict). It does not convert raw intentions.
|
|
17
17
|
|
|
18
18
|
Callers inject a persistence adapter. Adapters are their own packages under
|
|
@@ -54,7 +54,7 @@ Transformers do not import this package. Conductor does. Host chooses the adapte
|
|
|
54
54
|
## 3. Two layers
|
|
55
55
|
|
|
56
56
|
```
|
|
57
|
-
commands / queries src/ledger/
|
|
57
|
+
commands / queries src/ledger/ TicketStandard states,
|
|
58
58
|
bail, Attempt, Trace, refusals
|
|
59
59
|
│
|
|
60
60
|
│ Persistence Port (plain records)
|
|
@@ -65,7 +65,7 @@ Transformers do not import this package. Conductor does. Host chooses the adapte
|
|
|
65
65
|
| Layer | Owns | Does not own |
|
|
66
66
|
| ------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------- |
|
|
67
67
|
| Ledger | Legal transitions, idempotence, bound counters, bail | `node:fs`, `sqlite`, `pg`, paths |
|
|
68
|
-
| Port | The record shapes that cross the boundary, and the verbs `load` / `save` / `listSummaries` |
|
|
68
|
+
| Port | The record shapes that cross the boundary, and the verbs `load` / `save` / `listSummaries` | TicketStandard state names as a state machine |
|
|
69
69
|
| Adapter | How those records are stored and listed | Whether `merging` may be cancelled |
|
|
70
70
|
|
|
71
71
|
A caller never talks to an adapter. It talks to the ledger. The ledger never
|
|
@@ -92,7 +92,7 @@ Hard rules. Breaking one is a spec bug, not a refactor.
|
|
|
92
92
|
honest. Its package does not exist until someone implements it.
|
|
93
93
|
7. **Domain tests inject a Port.** They use a test double, or an adapter package
|
|
94
94
|
on a temp path. They do not reach into adapter internals. Adapter tests do
|
|
95
|
-
not run
|
|
95
|
+
not run TicketStandard transitions.
|
|
96
96
|
|
|
97
97
|
## 5. Persistence Port
|
|
98
98
|
|
|
@@ -101,15 +101,15 @@ does not care.
|
|
|
101
101
|
|
|
102
102
|
| Operation | In | Out |
|
|
103
103
|
| --------------- | ------------------------- | ------------------------------------ |
|
|
104
|
-
| `load` |
|
|
105
|
-
| `save` |
|
|
104
|
+
| `load` | Ticket `key` | Aggregate, or missing |
|
|
105
|
+
| `save` | Ticket `key` + aggregate | Durable replace of that key. Atomic. |
|
|
106
106
|
| `listSummaries` | none | One summary per known key |
|
|
107
107
|
|
|
108
108
|
`save` replaces the whole aggregate for that key. The ledger read-modify-writes.
|
|
109
109
|
The adapter does not merge. If `save` cannot complete, it fails; it does not
|
|
110
110
|
leave a half-written key that `load` could return.
|
|
111
111
|
|
|
112
|
-
`listSummaries` exists so `claim` can pick the next `received`
|
|
112
|
+
`listSummaries` exists so `claim` can pick the next `received` TicketStandard
|
|
113
113
|
without loading every Trace. A summary carries at least: `key`, `project`,
|
|
114
114
|
`state`, `priority`, `received_at`, bail expiry if any.
|
|
115
115
|
|
|
@@ -135,7 +135,7 @@ segment (`fake%3A42.json`). The adapter never uses the raw key as a filename.
|
|
|
135
135
|
file or the new file, never a truncated JSON document that parses as a
|
|
136
136
|
different aggregate.
|
|
137
137
|
|
|
138
|
-
This adapter knows directories and JSON. It does not know `merging`, `
|
|
138
|
+
This adapter knows directories and JSON. It does not know `merging`, `resume`,
|
|
139
139
|
or Isolator.
|
|
140
140
|
|
|
141
141
|
## 7. Adapter packages
|
|
@@ -154,53 +154,46 @@ through the Port.
|
|
|
154
154
|
|
|
155
155
|
## 8. Aggregate
|
|
156
156
|
|
|
157
|
-
One aggregate per
|
|
157
|
+
One aggregate per TicketStandard `key` (`<manager>:<external_id>`). The Adapter
|
|
158
158
|
produced `key` and `fingerprint`; WorkLedger owns everything else.
|
|
159
159
|
|
|
160
160
|
| Part | Content |
|
|
161
161
|
| ------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
162
162
|
| Identity | `key`, `project`, `fingerprint` |
|
|
163
|
-
| Intention |
|
|
164
|
-
|
|
|
163
|
+
| Intention | TicketStandard fields this package stores (intention, title, priority, source, …) and its place in a Feature: `feature`, `blocked_by` (Tickets that must be `done` first), `line` (the line it lands on) |
|
|
164
|
+
| Ticket state | Exactly one of §9 |
|
|
165
165
|
| Invalid | `code` + `reason` when state is `invalid` |
|
|
166
|
-
| Pending fingerprint | Set when an update arrives during `
|
|
167
|
-
|
|
|
168
|
-
| Subtask states | Per Subtask, exactly one of §9 |
|
|
169
|
-
| Attempts | Numbered, each with a Trace when an Attempt ran, and stamped with the round (`resumes` at the time) it belongs to |
|
|
166
|
+
| Pending fingerprint | Set when an update arrives during `running` / `integrating`; applied only after the in-flight work stops |
|
|
167
|
+
| Attempts | Numbered from 1, each with a Trace when an Attempt ran, and stamped with the round (`resumes` at the time) it belongs to |
|
|
170
168
|
| Bound counters | Attempts used, as this package tracks them |
|
|
171
|
-
| Resumes | How many `
|
|
169
|
+
| Resumes | How many `resumeTicket` were taken. A round tells two escalations of one kind apart |
|
|
172
170
|
| Parked refusal | A report `assembly.validate` sent back, held on the aggregate because there may be no Submission yet. Present only while `integrating`; cleared once validate accepts |
|
|
173
171
|
| Parked refusals | How many times `assembly.validate` sent the work back with no Submission's own counter to hold it. Shares the `maxRefusals` budget with `submission.refusals` |
|
|
174
|
-
| Bail | Optional. `expires_at`
|
|
175
|
-
| Declared
|
|
172
|
+
| Bail | Optional. `expires_at` while a unit is held (from `startUnit`) and while the work is assembled and judged |
|
|
173
|
+
| Declared workspace | Optional absolute path glue wrote **before** creating that directory |
|
|
176
174
|
|
|
177
|
-
Unknown fields from a
|
|
175
|
+
Unknown fields from a TicketStandard that Adapter would drop are not
|
|
178
176
|
invented here either. WorkLedger does not reopen the Adapter's closed shape.
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
rewritten; a new Attempt is appended.
|
|
178
|
+
An Attempt is never rewritten; a new Attempt is appended.
|
|
182
179
|
|
|
183
180
|
## 9. States
|
|
184
181
|
|
|
185
|
-
|
|
182
|
+
TicketStandard states, copied from the product glossary. WorkLedger is the
|
|
186
183
|
component that actually holds them.
|
|
187
184
|
|
|
188
185
|
| State | Meaning |
|
|
189
186
|
| ------------- | -------------------------------------------------- |
|
|
190
187
|
| `received` | Admitted, not yet claimed |
|
|
191
188
|
| `invalid` | Stored as unusable, with a code; not silent |
|
|
192
|
-
| `
|
|
193
|
-
| `running` | Subtask loop (bail held) |
|
|
189
|
+
| `running` | The unit is made (bail held once it starts) |
|
|
194
190
|
| `escalated` | Frozen for a human |
|
|
195
|
-
| `integrating` |
|
|
191
|
+
| `integrating` | The unit passed its Gates; assembly Gates |
|
|
196
192
|
| `submitted` | Offered to an Authority; waiting on its judgement |
|
|
197
193
|
| `merging` | Final fold into WorkLineStable. Point of no return |
|
|
198
194
|
| `done` | Terminal. In WorkLineStable |
|
|
199
195
|
| `cancelled` | Terminal. Abandoned before final integration |
|
|
200
196
|
|
|
201
|
-
Subtask states: `pending`, `runnable`, `running`, `escalated`, `integrated`,
|
|
202
|
-
`cancelled`.
|
|
203
|
-
|
|
204
197
|
A command that would require a transition not in this section is `refused`.
|
|
205
198
|
The aggregate is unchanged.
|
|
206
199
|
|
|
@@ -214,43 +207,40 @@ Idempotence is on `key` unless a row says otherwise.
|
|
|
214
207
|
|
|
215
208
|
| Command | Meaning | Refused when |
|
|
216
209
|
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
217
|
-
| `admit` | Store a
|
|
210
|
+
| `admit` | Store a TicketStandard in `received`. Same `key` already `received` with the same fingerprint → success, no rewrite. Same `key`, different fingerprint, not in-flight → update the stored intention. Same `key` in-flight (`running` / `integrating` / `submitted`) with the same fingerprint (or the fingerprint already pending) → success, no rewrite. A different fingerprint while in-flight is recorded as pending; the live work is not rewritten. | `key` in `merging` or `done`; `key` in `cancelled` |
|
|
218
211
|
| `recordInvalid` | Store `invalid` with code, reason and the fingerprint of what was received | `key` already in `merging` or `done` |
|
|
219
|
-
| `claim` | Take the next `received`
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
227
|
-
| `recordParkedRefusal` | `assembly.validate` sent the work back before any Submission exists. Store the parked report, increment `parked_refusals` | Not `integrating` |
|
|
212
|
+
| `claim` | Take the next `received` TicketStandard for a Project (priority then `received_at`). State → `running`, with no bail yet. No-op with `empty` when none | That Project already has `running`, `escalated`, `integrating`, `submitted`, or `merging` |
|
|
213
|
+
| `startUnit` | The unit starts: take the bail | Not `running`; a bail is already held (`another-running`) |
|
|
214
|
+
| `recordAttempt` | Append an Attempt + Trace (`ended`, optional `report` and `refusedBy`) | Not `running`; Attempt number not the next integer |
|
|
215
|
+
| `markIntegrating` | The unit passed its Gates. State → `integrating`, bail renewed | Not `running` |
|
|
216
|
+
| `markSubmitted` | Ticket → `submitted`. Records the Authority's opaque `reference`. A second call keeps the first reference and any `last_report` | Not `integrating` or `submitted` |
|
|
217
|
+
| `recordRefusal` | Authority / assembly Gates sent the work back. Increment `refusals`, store `last_report`, Ticket → `integrating`. Clears a stale parked refusal, if one is held | Not `submitted` |
|
|
218
|
+
| `recordRealign` | The line moved on while the Submission waited. Ticket → `integrating`, `realigns` + 1, the report of an earlier refusal dropped (it was repaired), no refusal counted. Only from `submitted`. `markSubmitted` keeps `realigns` |
|
|
219
|
+
| `recordParkedRefusal` | `assembly.validate` sent the work back before any Submission exists. Store the parked report, increment `parked_refusals` | Not `integrating` |
|
|
228
220
|
| `clearParkedRefusal` | `assembly.validate` accepted. Drop the parked report; the counter stays — it is the budget already spent | — |
|
|
229
|
-
| `markMerging` |
|
|
230
|
-
| `markDone` |
|
|
231
|
-
| `escalate` |
|
|
232
|
-
| `
|
|
233
|
-
| `cancel` |
|
|
234
|
-
| `declareWorkspace` | Record
|
|
235
|
-
| `clearWorkspace` | Drop
|
|
236
|
-
| `expireBail` | Bail past expiry:
|
|
237
|
-
| `releaseBail` |
|
|
221
|
+
| `markMerging` | Ticket → `merging` | Not `integrating` or `submitted` |
|
|
222
|
+
| `markDone` | Ticket → `done`. Records the fold's `reference` when given (the work line's own name for it, opaque). Clear bail | Not `merging` |
|
|
223
|
+
| `escalate` | Ticket → `escalated`. For kind `unit`, a Trace is required if an Attempt exists. Consumes `pending_fingerprint` | Ticket already `done` or `cancelled`; a `unit` escalation when the last Attempt has no Trace |
|
|
224
|
+
| `resumeTicket` | Human `resume`. Escalation `unit` → `running` from zero, no bail. Escalation from `merging`, `submitted`, or `assembly` → `integrating`. Counts a round | Not `escalated` or `invalid`; `invalid` without a new admissible TicketStandard |
|
|
225
|
+
| `cancel` | Ticket → `cancelled` | `merging` or `done`; an escalation that was born in `merging` |
|
|
226
|
+
| `declareWorkspace` | Record the workspace path **before** glue creates it | Ticket `done` or `cancelled` |
|
|
227
|
+
| `clearWorkspace` | Drop the declared path after glue deleted that directory | — |
|
|
228
|
+
| `expireBail` | Bail past expiry: a `running` Ticket's unit stops (consume an Attempt; the workspace stays declared); any other state only loses its bail | No bail, or bail not expired |
|
|
229
|
+
| `releaseBail` | A `running` Ticket's unit stops, without waiting for the clock and without consuming an Attempt (the stopped pass recorded its own); the workspace stays declared. A `running` Ticket with no bail is success with no rewrite | Ticket not `running` |
|
|
238
230
|
| `renewBail` | Push bail expiry forward by the configured duration. Allowed while a bail is held | No bail |
|
|
239
231
|
|
|
240
|
-
`
|
|
232
|
+
`resumeTicket` from `invalid` requires `admit` of a corrected TicketStandard
|
|
241
233
|
first (`invalid` → `received` is `admit` after correction, then claim).
|
|
242
234
|
|
|
243
|
-
In-flight update (`admit` while `
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
integrated, and when entering `integrating`, if the field is set. WorkLedger
|
|
235
|
+
In-flight update (`admit` while `running` / `integrating` / `submitted`): same
|
|
236
|
+
fingerprint as the live intention, or as the fingerprint already pending →
|
|
237
|
+
success, no rewrite (an echo of a report). A different fingerprint is stored as
|
|
238
|
+
pending; do not rewrite the live intention. `escalate` consumes
|
|
239
|
+
`pending_fingerprint`. Conductor calls `escalate({ kind: "unit" })` after the
|
|
240
|
+
unit ends, and when entering `integrating`, if the field is set. WorkLedger
|
|
250
241
|
does not watch a Transformer's clock.
|
|
251
242
|
|
|
252
|
-
Escalation `kind` is one of `
|
|
253
|
-
`merging`.
|
|
243
|
+
Escalation `kind` is one of `unit`, `assembly`, `submitted`, `merging`.
|
|
254
244
|
|
|
255
245
|
## 11. Queries
|
|
256
246
|
|
|
@@ -258,34 +248,74 @@ Escalation `kind` is one of `plan`, `subtask`, `assembly`, `submitted`,
|
|
|
258
248
|
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
259
249
|
| `get` | Aggregate for a `key`, or missing |
|
|
260
250
|
| `list` | One summary per known key, every state. No persist, no bail, no transition |
|
|
261
|
-
| `listInFlight` | The `list` subset that is mid-flight (`
|
|
262
|
-
| `activeOn` | The active
|
|
251
|
+
| `listInFlight` | The `list` subset that is mid-flight (`running` / `integrating` / `submitted`) |
|
|
252
|
+
| `activeOn` | The active TicketStandard on a Project (`running` / `escalated` / `integrating` / `submitted` / `merging`), or none |
|
|
263
253
|
| `nextReceived` | What `claim` would take, without taking it |
|
|
264
|
-
| `nextRunnable` | A `runnable` Subtask of a Feature (ties: the one that unblocks the most others). Glue still serializes |
|
|
265
254
|
| `listDeclaredWorkspaces` | Keys and paths the ledger believes exist. Glue reconciles disk to this list. Orphans on disk that are not listed are glue's to destroy |
|
|
266
255
|
|
|
267
256
|
Queries do not persist. They do not take a bail.
|
|
268
257
|
|
|
258
|
+
### Features
|
|
259
|
+
|
|
260
|
+
A Feature is stored through the same Port, beside the Tickets, with
|
|
261
|
+
`kind: "feature"`. Ticket queries (`list`, `listInFlight`, `activeOn`,
|
|
262
|
+
`nextReceived`, `listDeclaredWorkspaces`) never return it; its own queries do.
|
|
263
|
+
|
|
264
|
+
| Feature state | Meaning |
|
|
265
|
+
| ------------- | ------------------------------------------------------- |
|
|
266
|
+
| `waiting` | Admitted, not yet opened |
|
|
267
|
+
| `open` | Its line exists and its Tickets are made, one at a time |
|
|
268
|
+
| `submitted` | Its line is with the Authority |
|
|
269
|
+
| `escalated` | Frozen for a human |
|
|
270
|
+
| `done` | Terminal. Its line is folded |
|
|
271
|
+
| `cancelled` | Terminal |
|
|
272
|
+
|
|
273
|
+
| Command | Meaning | Refused when |
|
|
274
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
275
|
+
| `setFeatureSkipE2e` | Put on or take off the tracker's e2e skip mark of a Feature (`intention.skip_e2e`), while it is `waiting`, `open` or `escalated`. A Ticket's `intention.e2e` is set by its admission | `submitted`, `done`, `cancelled` (`illegal-transition`); unknown key (`not-found`) |
|
|
276
|
+
| `admitFeature` | Store as `waiting`. While waiting, an edit replaces the intention; once held (`open` / `submitted` / `escalated`), it is recorded as pending and the echo of an edit is a success | `done` (`too-late`); `cancelled` (`illegal-transition`) |
|
|
277
|
+
| `openFeature` | `waiting` → `open`, recording the line and the keys of its Tickets | Not `waiting`; the Project already holds a Feature (`project-busy`) |
|
|
278
|
+
| `markFeatureSubmitted` | `open` → `submitted`, recording the Authority's opaque `reference` | Not `open` |
|
|
279
|
+
| `markFeatureDone` | `submitted` → `done`, recording the fold's reference when given | Not `submitted` |
|
|
280
|
+
| `escalateFeature` | `open` or `submitted` → `escalated` with the report; consumes a pending edit | Any other state |
|
|
281
|
+
| `resumeFeature` | `escalated` → `open`, counting a round. The Submission, if there is one, stays | Not `escalated` |
|
|
282
|
+
| `cancelFeature` | Any state but `done` → `cancelled` | `done` (`point-of-no-return`) |
|
|
283
|
+
|
|
284
|
+
| Query | Out |
|
|
285
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
286
|
+
| `getFeature` | The aggregate, or missing |
|
|
287
|
+
| `listFeatures` | One summary per Feature |
|
|
288
|
+
| `activeFeatureOn` | The Feature that holds a Project: `open`, `submitted` or `escalated` |
|
|
289
|
+
| `nextFeature` | What would open next: `waiting`, every Feature it waits on `done`, highest priority then oldest. Nothing while a Feature holds the Project |
|
|
290
|
+
|
|
291
|
+
A Ticket that waits on others is not claimed until each is `done`; a blocker the
|
|
292
|
+
ledger does not know blocks. `claim` and `nextReceived` skip a received Ticket
|
|
293
|
+
that may not start and take the next one that may.
|
|
294
|
+
|
|
269
295
|
## 12. Bail and reconcile
|
|
270
296
|
|
|
271
|
-
While a
|
|
272
|
-
or `merging`, it holds a bail (`expires_at`). There is no holder id
|
|
273
|
-
implementation. Glue renews
|
|
274
|
-
|
|
275
|
-
Attempt.
|
|
297
|
+
While a unit is held (`startUnit`) and while a TicketStandard is `integrating`,
|
|
298
|
+
`submitted`, or `merging`, it holds a bail (`expires_at`). There is no holder id
|
|
299
|
+
in this implementation. Glue renews with `startUnit` / `markIntegrating` /
|
|
300
|
+
`markSubmitted`; `renewBail` exists if expiry would otherwise pass during a
|
|
301
|
+
long Attempt.
|
|
276
302
|
|
|
277
|
-
Expired bail: `expireBail`.
|
|
278
|
-
|
|
279
|
-
|
|
303
|
+
Expired bail: `expireBail`. A `running` Ticket's unit stops and consumes an
|
|
304
|
+
Attempt. The declared workspace stays declared, with `first_stage_done` if the
|
|
305
|
+
stage before the producer was complete in it: the next pass picks it up, or
|
|
306
|
+
starts over if the directory is gone (`clearWorkspace`, `releaseBail` + glue).
|
|
307
|
+
A human's `resumeTicket` of a unit forgets both: that unit starts from zero.
|
|
308
|
+
The ledger decides; glue acts.
|
|
280
309
|
|
|
281
310
|
A clean stop (or an operator) that knows the holder is gone calls
|
|
282
|
-
`releaseBail`: the same `
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
`
|
|
311
|
+
`releaseBail`: the same `running` outcome, without the clock and without
|
|
312
|
+
consuming an Attempt (`markFirstStageDone` records that the stage before the
|
|
313
|
+
producer is complete in the declared workspace; `declareWorkspace` resets it). It refuses
|
|
314
|
+
`integrating`, `submitted`, `merging`, and every terminal state — those do not
|
|
315
|
+
hold a unit. A `running` Ticket that holds no bail is success with no rewrite.
|
|
286
316
|
|
|
287
317
|
After a process start, glue calls `listDeclaredWorkspaces` and destroys
|
|
288
|
-
directories that are not on that list. It does not invent
|
|
318
|
+
directories that are not on that list. It does not invent Ticket state from
|
|
289
319
|
disk.
|
|
290
320
|
|
|
291
321
|
## 13. Refusals
|
|
@@ -297,11 +327,8 @@ A refused command leaves the store as it was. `code` is one token.
|
|
|
297
327
|
| `not-found` | `key` is unknown |
|
|
298
328
|
| `duplicate` | Reserved; `admit` of an identical fingerprint is success, not this |
|
|
299
329
|
| `illegal-transition` | State does not allow this command |
|
|
300
|
-
| `project-busy` | `claim` while that Project already has an active
|
|
301
|
-
| `
|
|
302
|
-
| `plan-not-frozen` | A Subtask command before `recordPlan` |
|
|
303
|
-
| `subtask-not-runnable` | `startSubtask` on a Subtask whose dependencies are not `integrated` |
|
|
304
|
-
| `another-running` | `startSubtask` while one Subtask is already `running` |
|
|
330
|
+
| `project-busy` | `claim` while that Project already has an active TicketStandard; `openFeature` while it holds a Feature |
|
|
331
|
+
| `another-running` | `startUnit` while a unit is already held |
|
|
305
332
|
| `missing-trace` | `escalate` after an Attempt with no Trace |
|
|
306
333
|
| `point-of-no-return` | `cancel` in `merging` or `done`, or abandon after merging-born escalation |
|
|
307
334
|
| `too-late` | `admit` / `recordInvalid` / intention edit in `done` |
|
|
@@ -345,22 +372,21 @@ directories here.
|
|
|
345
372
|
|
|
346
373
|
## 16. Acceptance
|
|
347
374
|
|
|
348
|
-
1. `admit` of a new
|
|
375
|
+
1. `admit` of a new TicketStandard → `get` returns it in `received` with that
|
|
349
376
|
`key` and `fingerprint`.
|
|
350
377
|
2. `admit` of the same `key` and fingerprint again → success, aggregate
|
|
351
378
|
unchanged (byte-for-byte on the stored fingerprint and intention).
|
|
352
379
|
3. `admit` of the same `key` with a new fingerprint while `received` → stored
|
|
353
380
|
intention updates.
|
|
354
381
|
4. `admit` while `running` with a new fingerprint → pending fingerprint set;
|
|
355
|
-
|
|
382
|
+
Ticket state still `running`. Same fingerprint
|
|
356
383
|
while `running` or `submitted` → success, no pending field.
|
|
357
|
-
5. `claim` on a Project with one `received` → that
|
|
358
|
-
|
|
359
|
-
6. `
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`integrating`.
|
|
384
|
+
5. `claim` on a Project with one `received` → that Ticket is `running`, with
|
|
385
|
+
no bail. A second `claim` on the same Project → `project-busy`.
|
|
386
|
+
6. `startUnit` takes the bail once; a second call → `another-running`.
|
|
387
|
+
7. `recordAttempt` numbers the Attempts from 1; a gap → `illegal-transition`.
|
|
388
|
+
8. `markIntegrating` on a `running` Ticket → `integrating`; on any other
|
|
389
|
+
state → `illegal-transition`.
|
|
364
390
|
9. `cancel` in `merging` → `point-of-no-return`; state still `merging`.
|
|
365
391
|
10. `escalate` after `recordAttempt` without a Trace → `missing-trace`; state
|
|
366
392
|
unchanged.
|
|
@@ -382,8 +408,8 @@ consequence: another answer was possible.
|
|
|
382
408
|
|
|
383
409
|
1. **Not a Transformer.** A store with many verbs is not IN/OUT-shaped. One IN
|
|
384
410
|
and one OUT would force a toolbox CLI and hide the transition table.
|
|
385
|
-
2. **Commands, not `set(state)`.** The product rules (
|
|
386
|
-
|
|
411
|
+
2. **Commands, not `set(state)`.** The product rules (one active
|
|
412
|
+
TicketStandard per Project, no abandon in `merging`) live here or they live
|
|
387
413
|
nowhere.
|
|
388
414
|
3. **Injected Persistence Port.** Sniffing env or a path to pick SQLite later
|
|
389
415
|
would couple the ledger to ops. Glue chooses; the ledger stays portable.
|
|
@@ -394,12 +420,12 @@ consequence: another answer was possible.
|
|
|
394
420
|
verbs would leak SQL into the ledger. Adapters may split storage internally.
|
|
395
421
|
6. **Filesystem and SQLite both ship.** Enough to be truth on disk or in one
|
|
396
422
|
file. Postgres is an adapter, not a prerequisite.
|
|
397
|
-
7. **Keys encoded for filenames.**
|
|
423
|
+
7. **Keys encoded for filenames.** Adapter keys contain `:`. Using them
|
|
398
424
|
raw would create extra directories or illegal paths.
|
|
399
425
|
8. **Refused commands do not persist.** A failed transition must not look like
|
|
400
426
|
history.
|
|
401
427
|
9. **`persist-failed` is not `invalid`.** Disk or database blips must not brand
|
|
402
|
-
a
|
|
428
|
+
a TicketStandard unusable.
|
|
403
429
|
10. **Declared workspaces are ledger facts, not discoveries.** Glue writes the
|
|
404
430
|
path before creating the directory, so crash reconcile has a list that
|
|
405
431
|
disk cannot lie about.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export type { ClaimResult,
|
|
1
|
+
export type { FeatureAdmission, FeatureCommands, FeatureGetResult, } from "./ledger/feature-commands.js";
|
|
2
|
+
export type { ClaimResult, CommandOk, CommandRefused, CommandResult, DeclaredWorkspace, DeclareWorkspaceInput, EscalateInput, GetResult, OpenWorkLedgerOptions, RecordAttemptInput, TicketAdmission, WorkLedger, } from "./ledger/open-work-ledger.js";
|
|
3
3
|
export { openWorkLedger } from "./ledger/open-work-ledger.js";
|
|
4
4
|
export type { PersistencePort, PersistSaveResult } from "./persist/port.js";
|
|
5
5
|
export type * from "./records.js";
|
|
6
|
+
export { isFeatureAggregate, isFeatureSummary } from "./records.js";
|
|
6
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,WAAW,EACX,SAAS,EACT,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EAClB,eAAe,EACf,UAAU,GACX,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,mBAAmB,cAAc,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PersistencePort } from "../persist/port.js";
|
|
2
|
+
import type { FeatureAggregate, FeatureIntention, FeatureSummary } from "../records.js";
|
|
3
|
+
import { type CommandResult } from "./results.js";
|
|
4
|
+
export type FeatureAdmission = FeatureIntention;
|
|
5
|
+
export type FeatureGetResult = {
|
|
6
|
+
ok: true;
|
|
7
|
+
aggregate: FeatureAggregate;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
code: "not-found";
|
|
11
|
+
};
|
|
12
|
+
export type FeatureCommands = {
|
|
13
|
+
/** Store a Feature as `waiting`. An edit while it waits replaces the intention; while it is held, it is recorded as pending. */
|
|
14
|
+
admitFeature(feature: FeatureAdmission): Promise<CommandResult>;
|
|
15
|
+
/** What would open next on the Project: waiting, every blocker `done`, highest priority then oldest. Nothing while the Project holds a Feature. */
|
|
16
|
+
nextFeature(project: string): Promise<FeatureSummary | undefined>;
|
|
17
|
+
/** `waiting` → `open`, with the line its Tickets land on (none without an Authority) and the keys of those Tickets. */
|
|
18
|
+
openFeature(input: {
|
|
19
|
+
key: string;
|
|
20
|
+
line?: string;
|
|
21
|
+
tickets: string[];
|
|
22
|
+
}): Promise<CommandResult>;
|
|
23
|
+
/** `open` → `submitted`: the line is with the Authority. */
|
|
24
|
+
markFeatureSubmitted(input: {
|
|
25
|
+
key: string;
|
|
26
|
+
reference: string;
|
|
27
|
+
}): Promise<CommandResult>;
|
|
28
|
+
/** The Authority's judgement sent the line back: `submitted` → `open`, with why, for the next fix. */
|
|
29
|
+
recordFeatureRefusal(input: {
|
|
30
|
+
key: string;
|
|
31
|
+
report: string;
|
|
32
|
+
refusedBy?: string;
|
|
33
|
+
}): Promise<CommandResult>;
|
|
34
|
+
/** The line it is offered to moved on: `submitted` → `open`, to be aligned again. No fix is owed. */
|
|
35
|
+
recordFeatureRealign(key: string): Promise<CommandResult>;
|
|
36
|
+
/** A check refused the line before it was offered: kept, with the round it costs. */
|
|
37
|
+
recordFeatureParkedRefusal(input: {
|
|
38
|
+
key: string;
|
|
39
|
+
report: string;
|
|
40
|
+
refusedBy?: string;
|
|
41
|
+
}): Promise<CommandResult>;
|
|
42
|
+
/** Whatever sent the line back is repaired and checked. */
|
|
43
|
+
clearFeatureParkedRefusal(key: string): Promise<CommandResult>;
|
|
44
|
+
/** `submitted` → `done`. */
|
|
45
|
+
markFeatureDone(key: string, input?: {
|
|
46
|
+
reference?: string | undefined;
|
|
47
|
+
}): Promise<CommandResult>;
|
|
48
|
+
/** The tracker's e2e skip label moved: honoured while the Feature waits, is open or is escalated. */
|
|
49
|
+
setFeatureSkipE2e(input: {
|
|
50
|
+
key: string;
|
|
51
|
+
skip: boolean;
|
|
52
|
+
}): Promise<CommandResult>;
|
|
53
|
+
/** `open` or `submitted` → `escalated`, for a human. Consumes a pending edit. */
|
|
54
|
+
escalateFeature(input: {
|
|
55
|
+
key: string;
|
|
56
|
+
report?: string;
|
|
57
|
+
}): Promise<CommandResult>;
|
|
58
|
+
/** `escalated` → `open`, a round later. */
|
|
59
|
+
resumeFeature(key: string): Promise<CommandResult>;
|
|
60
|
+
/** Anything but `done` → `cancelled`. */
|
|
61
|
+
cancelFeature(key: string): Promise<CommandResult>;
|
|
62
|
+
getFeature(key: string): Promise<FeatureGetResult>;
|
|
63
|
+
listFeatures(): Promise<FeatureSummary[]>;
|
|
64
|
+
/** The Feature that holds the Project: `open`, `submitted` or `escalated`. */
|
|
65
|
+
activeFeatureOn(project: string): Promise<FeatureSummary | undefined>;
|
|
66
|
+
};
|
|
67
|
+
export declare function openFeatureCommands(options: {
|
|
68
|
+
persist: PersistencePort;
|
|
69
|
+
now: () => number;
|
|
70
|
+
}): FeatureCommands;
|
|
71
|
+
//# sourceMappingURL=feature-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-commands.d.ts","sourceRoot":"","sources":["../../src/ledger/feature-commands.ts"],"names":[],"mappings":"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"}
|