@bluewombat/implementer 0.1.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 +91 -0
- package/SPECS.md +349 -0
- package/dist/args/parse-args.d.ts +6 -0
- package/dist/args/parse-args.d.ts.map +1 -0
- package/dist/args/parse-args.js +330 -0
- package/dist/args/parse-args.js.map +1 -0
- package/dist/child/parse-child-output.d.ts +11 -0
- package/dist/child/parse-child-output.d.ts.map +1 -0
- package/dist/child/parse-child-output.js +49 -0
- package/dist/child/parse-child-output.js.map +1 -0
- package/dist/child/run-builder.d.ts +24 -0
- package/dist/child/run-builder.d.ts.map +1 -0
- package/dist/child/run-builder.js +92 -0
- package/dist/child/run-builder.js.map +1 -0
- package/dist/child/run-child.d.ts +36 -0
- package/dist/child/run-child.d.ts.map +1 -0
- package/dist/child/run-child.js +141 -0
- package/dist/child/run-child.js.map +1 -0
- package/dist/child/run-gate.d.ts +14 -0
- package/dist/child/run-gate.d.ts.map +1 -0
- package/dist/child/run-gate.js +72 -0
- package/dist/child/run-gate.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/loop/decide-after-attempt.d.ts +23 -0
- package/dist/loop/decide-after-attempt.d.ts.map +1 -0
- package/dist/loop/decide-after-attempt.js +26 -0
- package/dist/loop/decide-after-attempt.js.map +1 -0
- package/dist/progress/emit.d.ts +5 -0
- package/dist/progress/emit.d.ts.map +1 -0
- package/dist/progress/emit.js +24 -0
- package/dist/progress/emit.js.map +1 -0
- package/dist/run/run-implementer.d.ts +23 -0
- package/dist/run/run-implementer.d.ts.map +1 -0
- package/dist/run/run-implementer.js +274 -0
- package/dist/run/run-implementer.js.map +1 -0
- package/dist/status/announce.d.ts +14 -0
- package/dist/status/announce.d.ts.map +1 -0
- package/dist/status/announce.js +40 -0
- package/dist/status/announce.js.map +1 -0
- package/dist/status/make-status.d.ts +9 -0
- package/dist/status/make-status.d.ts.map +1 -0
- package/dist/status/make-status.js +38 -0
- package/dist/status/make-status.js.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Implementer
|
|
2
|
+
|
|
3
|
+
Transformer: runs **one Task** in an existing workspace. It launches the
|
|
4
|
+
Builder, then the Gate sequence it was given (possibly empty), and loops Attempts
|
|
5
|
+
until the run is `validated`, `escalated`, or `interrupted`.
|
|
6
|
+
|
|
7
|
+
Behavioural contract: [SPECS.md](./SPECS.md).
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
Repository stack — TypeScript, Node 24.20, Biome, `node:test`. See [docs/DEVELOPMENT.md](../../../docs/DEVELOPMENT.md).
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
From the repository root:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
nvm use
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
| Task | Command |
|
|
25
|
+
| --------- | ---------------- |
|
|
26
|
+
| Lint | `npm run lint` |
|
|
27
|
+
| Format | `npm run format` |
|
|
28
|
+
| Typecheck | `npm run tsc` |
|
|
29
|
+
| Test | `npm test` |
|
|
30
|
+
| Build | `npm run build` |
|
|
31
|
+
|
|
32
|
+
Binary after build: `dist/cli.js` (package bin name `implementer`).
|
|
33
|
+
|
|
34
|
+
## Run
|
|
35
|
+
|
|
36
|
+
Workspace must already exist. Example with no Gates:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run build
|
|
40
|
+
node dist/cli.js \
|
|
41
|
+
--id task-1 \
|
|
42
|
+
--intention "do the work" \
|
|
43
|
+
--definition-of-done "checks pass" \
|
|
44
|
+
--workspace /absolute/path/to/workspace \
|
|
45
|
+
--builder -- node /path/to/builder.js \
|
|
46
|
+
--builder-timeout-ms 60000 \
|
|
47
|
+
--max-attempts 3
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Each child has its own ceiling: `--builder-timeout-ms` (required), and `--gate-timeout-ms` before each `--gate`. There is no Attempt clock and no Task clock.
|
|
51
|
+
|
|
52
|
+
Progress is JSON lines on stdout (`status`, `attempt-started`, `builder-finished`,
|
|
53
|
+
`gate-finished`, `attempt-finished`, `run-finished`). Exit codes: `0` validated,
|
|
54
|
+
`1` escalated, `2` invalid-invocation, `130` interrupted.
|
|
55
|
+
|
|
56
|
+
## Import
|
|
57
|
+
|
|
58
|
+
Same Transformer, in process. `cli.ts` is an adapter over this function — it reads argv
|
|
59
|
+
and stdin, calls `runImplementer`, and maps the outcome to an exit code. Nothing the CLI
|
|
60
|
+
can do is unreachable from here.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { runImplementer } from "@bluewombat/implementer";
|
|
64
|
+
|
|
65
|
+
const result = await runImplementer({
|
|
66
|
+
invocation, // same shape the CLI builds from argv
|
|
67
|
+
write: (line) => events.push(line), // progress as values, not stdout
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`src/index.ts` exports `runImplementer`, its `RunOptions` / `RunResult`, `ProgressWriter`,
|
|
72
|
+
and this Transformer's domain types.
|
|
73
|
+
|
|
74
|
+
## Layout
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
SPECS.md behavioural contract
|
|
78
|
+
README.md how to build and run
|
|
79
|
+
src/
|
|
80
|
+
cli.ts entry
|
|
81
|
+
types.ts Transformer-owned types
|
|
82
|
+
args/ CLI parsing
|
|
83
|
+
loop/ pure Attempt-loop decisions
|
|
84
|
+
status/ Status labels and --on-status
|
|
85
|
+
progress/ JSON-line progress writer
|
|
86
|
+
child/ spawn, Builder, Gate
|
|
87
|
+
run/ invocation orchestration
|
|
88
|
+
fixtures/ fake Builder / Gate scripts for tests
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Sources stay colocated: helpers used once live next to their caller.
|
package/SPECS.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Implementer
|
|
2
|
+
|
|
3
|
+
Independent Transformer: it has a binary, a contract, and nothing else. This paragraph
|
|
4
|
+
is the only place this spec talks about the rest of the system. From here on,
|
|
5
|
+
Implementer is alone. Its code, identifiers, comments, and messages name only
|
|
6
|
+
what it owns: the Task, its arguments, the workspace path, Builder, Gates,
|
|
7
|
+
Attempts, Traces, Status, clocks, and the run outcome. No other component is
|
|
8
|
+
mentioned there, because none of them exist inside this perimeter.
|
|
9
|
+
|
|
10
|
+
Implementer runs **one Task** in a directory it did not create. It
|
|
11
|
+
launches the Builder, then the Gate sequence it was given (that sequence may be
|
|
12
|
+
empty), and loops Attempts until the run is validated, escalated, or
|
|
13
|
+
interrupted.
|
|
14
|
+
|
|
15
|
+
The workspace is the only place files belong: that is where the Builder writes.
|
|
16
|
+
Intention, definition of done, reports, bounds — arguments, never files.
|
|
17
|
+
|
|
18
|
+
Stack and binary layout: [README.md](./README.md).
|
|
19
|
+
|
|
20
|
+
## 1. Job
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
arguments Implementer children
|
|
24
|
+
--------- ----------- --------
|
|
25
|
+
Task one invocation Builder, once per Attempt
|
|
26
|
+
workspace Attempt loop each Gate, in order, per Attempt
|
|
27
|
+
Builder until a run outcome
|
|
28
|
+
ordered Gates (may be none)
|
|
29
|
+
bounds
|
|
30
|
+
← run outcome + Traces + Status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**In:** arguments. **Out:** one run outcome, a Trace per Attempt that ran, and
|
|
34
|
+
a live Status each time it changes. Implementer does not persist. It does not
|
|
35
|
+
wait for the listener to acknowledge. It does not write files.
|
|
36
|
+
|
|
37
|
+
## 2. Options
|
|
38
|
+
|
|
39
|
+
Implementer is invoked with arguments. It does not read an input file. It does
|
|
40
|
+
not read configuration from the workspace.
|
|
41
|
+
|
|
42
|
+
| Argument | Content |
|
|
43
|
+
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `--id` | Task id. Copied onto every Trace. |
|
|
45
|
+
| `--intention` | Passed through to Builder and Gates. Implementer does not interpret it. |
|
|
46
|
+
| `--definition-of-done` | Passed through to Builder and Gates. Implementer does not evaluate it. |
|
|
47
|
+
| `--workspace` | Absolute path. Must already exist. Working directory of Builder and Gates. The only directory this process treats as files. |
|
|
48
|
+
| `--builder -- <argv…>` | Builder command. Non-empty. **May be omitted**: an Attempt is then its Gate sequence alone, and nothing is produced. |
|
|
49
|
+
| `--builder-timeout-ms` | Positive integer. **Required.** Wall clock of one producer run. Nothing else bounds it. |
|
|
50
|
+
| `--repair-builder -- <argv…>` | Optional producer for a pass that has something to resolve. Needs a `--builder` to fall back to. |
|
|
51
|
+
| `--repair-builder-timeout-ms` | Optional. Wall clock of that producer. Absent: `--builder-timeout-ms`. |
|
|
52
|
+
| `--gate <id> -- <argv…>` | One Gate. Repeatable; order is the sequence. **May be omitted** (empty sequence). Each `id` unique. Command non-empty. |
|
|
53
|
+
| `--gate-timeout-ms` | Positive integer. **Required before each `--gate`.** Wall clock of that Gate. |
|
|
54
|
+
| `--max-attempts` | Positive integer. Attempts this invocation may start. |
|
|
55
|
+
| `--report` | Optional. Failure report passed to the producer on Attempt 1 (and kept for retries). |
|
|
56
|
+
| `--report-from` | Optional. Which Gate produced `--report`, when a Gate did. |
|
|
57
|
+
| `--context` | Optional. Larger piece of work this Task belongs to, passed through to the producer. |
|
|
58
|
+
| `--stage` | Optional. `unit` or `assembly`. Passed to Gates. Absent: Gates receive `unit`. |
|
|
59
|
+
| `--on-status -- <argv…>` | Optional. Command to run each time Status changes. Omitted: Status still goes to stdout. |
|
|
60
|
+
|
|
61
|
+
No other argument is read.
|
|
62
|
+
|
|
63
|
+
Malformed arguments, missing workspace, empty command, duplicate Gate ids, or a
|
|
64
|
+
non-positive bound → run outcome `invalid-invocation`. No Attempt.
|
|
65
|
+
|
|
66
|
+
A Task with neither a Builder nor a Gate has nothing to do and nothing to judge:
|
|
67
|
+
that is an empty invocation, and it validates.
|
|
68
|
+
|
|
69
|
+
## 3. Workflow
|
|
70
|
+
|
|
71
|
+
One invocation runs the whole Attempt loop.
|
|
72
|
+
|
|
73
|
+
An Attempt is one pass: Builder, then Gates from the start of the list, stopping
|
|
74
|
+
at the first non-pass. Numbered from 1. It starts when the Builder is spawned.
|
|
75
|
+
|
|
76
|
+
The run is `validated` only when **every** Gate passes in the **same** Attempt.
|
|
77
|
+
An empty list after a completed Builder is validation: Implementer adds no Gate
|
|
78
|
+
of its own.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Start Attempt 1.
|
|
82
|
+
Run Builder.
|
|
83
|
+
If Builder does not complete → end Attempt (§5).
|
|
84
|
+
Else run Gates in list order.
|
|
85
|
+
Empty list → Attempt `validated`.
|
|
86
|
+
First non-pass → Attempt ends with that verdict.
|
|
87
|
+
All pass → Attempt `validated`.
|
|
88
|
+
After an Attempt:
|
|
89
|
+
`validated` → run `validated`.
|
|
90
|
+
`fail-blocking` → run `escalated` (do not start another Attempt).
|
|
91
|
+
`interrupted` → run `interrupted`.
|
|
92
|
+
`fail-retryable` and no Attempts left
|
|
93
|
+
→ run `escalated`.
|
|
94
|
+
`fail-retryable` otherwise
|
|
95
|
+
→ next Attempt.
|
|
96
|
+
Builder receives `--report` with the failing Gate's
|
|
97
|
+
report (or the Builder failure detail if no Gate ran).
|
|
98
|
+
Gates restart from the first in the list.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
flowchart TD
|
|
103
|
+
START[Start Attempt] -->|a producer| B[Builder]
|
|
104
|
+
START -->|no producer: Gates alone| G
|
|
105
|
+
B -->|completed| G{Gates remaining?}
|
|
106
|
+
G -->|none| OK[Attempt validated]
|
|
107
|
+
G -->|yes| NEXT[Next Gate]
|
|
108
|
+
NEXT -->|pass| G
|
|
109
|
+
NEXT -->|fail-retryable| R{Attempts left?}
|
|
110
|
+
NEXT -->|fail-blocking| ESC[Run escalated]
|
|
111
|
+
B -->|fail-retryable, timed out| R
|
|
112
|
+
B -->|refused, or its own fail-blocking| ESC
|
|
113
|
+
B -->|interrupted| INT[Run interrupted]
|
|
114
|
+
NEXT -->|interrupted| INT
|
|
115
|
+
R -->|yes| AGAIN[Next Attempt: Builder with --report]
|
|
116
|
+
AGAIN --> B
|
|
117
|
+
R -->|no| ESC
|
|
118
|
+
OK --> DONE[Run validated]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Rules of the loop are a pure function of verdicts, Attempt count, and clocks:
|
|
122
|
+
no process spawn inside that function, so it is testable without a Builder.
|
|
123
|
+
|
|
124
|
+
## 4. Ceilings and interrupt
|
|
125
|
+
|
|
126
|
+
**Every child carries its own, and no other.** The producer takes
|
|
127
|
+
`--builder-timeout-ms` (or `--repair-builder-timeout-ms` when that producer
|
|
128
|
+
runs); each Gate takes the `--gate-timeout-ms` written before it. Reaching one
|
|
129
|
+
kills that child and ends the Attempt as `fail-retryable`, which the loop then
|
|
130
|
+
decides on like any other retryable failure.
|
|
131
|
+
|
|
132
|
+
**Nothing is derived from what another child left behind.** There is no Attempt
|
|
133
|
+
clock and no Task clock. A budget computed from a deadline is how a producer
|
|
134
|
+
that overran left its Gates a millisecond to answer in — they were killed on
|
|
135
|
+
the clock instead of judging the work, and the judgement was lost, not just the
|
|
136
|
+
build.
|
|
137
|
+
|
|
138
|
+
**What bounds a whole invocation** is therefore what the Project wrote, and it
|
|
139
|
+
is arithmetic anyone can do: `--max-attempts` times the producer's ceiling plus
|
|
140
|
+
the sum of the Gates'. Add the status hook, which is capped at 5s per call.
|
|
141
|
+
|
|
142
|
+
**Interrupt.** SIGINT and SIGTERM stop this invocation. Kill the child, end the
|
|
143
|
+
in-flight Attempt as `interrupted`, return `interrupted`. Do **not** delete
|
|
144
|
+
`--workspace`.
|
|
145
|
+
|
|
146
|
+
## 5. Builder
|
|
147
|
+
|
|
148
|
+
Working directory: `--workspace`. Implementer appends arguments to the Builder
|
|
149
|
+
command. It sets no extra environment variables of its own (the process still
|
|
150
|
+
inherits the environment).
|
|
151
|
+
|
|
152
|
+
| Argument | Content |
|
|
153
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `--id` | Task id |
|
|
155
|
+
| `--attempt` | Attempt number, decimal |
|
|
156
|
+
| `--intention` | The `--intention` given to Implementer |
|
|
157
|
+
| `--definition-of-done` | The `--definition-of-done` given to Implementer |
|
|
158
|
+
| `--report` | Previous Attempt's failure report (JSON `report` from that child's stdout), or `--report` given to Implementer. **Omitted** when neither exists. |
|
|
159
|
+
| `--report-from` | Present when a Gate produced that report. |
|
|
160
|
+
| `--context` | Present when `--context` was given to Implementer. |
|
|
161
|
+
|
|
162
|
+
Exit 0: the Builder finished its pass. Implementer does not inspect the
|
|
163
|
+
workspace.
|
|
164
|
+
|
|
165
|
+
| What happened | Attempt `ended` | `--report` on the next Builder, if any |
|
|
166
|
+
| ----------------------------------------------------------------- | -------------------- | -------------------------------------- |
|
|
167
|
+
| Exit 0 | completed; run Gates | — |
|
|
168
|
+
| Stdout is JSON `{ "outcome": "fail-blocking", "report": "..." }` | `fail-blocking` | that `report` |
|
|
169
|
+
| Stdout is JSON `{ "outcome": "fail-retryable", "report": "..." }` | `fail-retryable` | that `report` |
|
|
170
|
+
| Non-zero exit, no such JSON | `fail-retryable` | the end of stdout and stderr (8 KiB) |
|
|
171
|
+
| Killed by a clock | `fail-retryable` | which clock |
|
|
172
|
+
| Stop signal | `interrupted` | none |
|
|
173
|
+
| Cannot spawn (not found, not executable) | `fail-blocking` | why |
|
|
174
|
+
|
|
175
|
+
`outcome` on that optional JSON is only `fail-retryable` or `fail-blocking`.
|
|
176
|
+
Exit 0 is completion; there is no `pass` object. Other fields are ignored.
|
|
177
|
+
|
|
178
|
+
A Builder that cannot proceed returns `fail-blocking` with the reason in
|
|
179
|
+
`report`.
|
|
180
|
+
|
|
181
|
+
## 6. Gates
|
|
182
|
+
|
|
183
|
+
The sequence is the `--gate` arguments, in that order. Implementer does not add
|
|
184
|
+
Gates, does not reorder them, and does not skip one because an earlier Attempt
|
|
185
|
+
passed it.
|
|
186
|
+
|
|
187
|
+
Working directory: `--workspace`. Implementer appends arguments to the Gate
|
|
188
|
+
command. No `--report`: the Gate judges the workspace.
|
|
189
|
+
|
|
190
|
+
| Argument | Content |
|
|
191
|
+
| ---------------------- | ----------------------- |
|
|
192
|
+
| `--id` | Task id |
|
|
193
|
+
| `--attempt` | Attempt number, decimal |
|
|
194
|
+
| `--gate-id` | This Gate's `id` |
|
|
195
|
+
| `--intention` | Same as the Builder |
|
|
196
|
+
| `--definition-of-done` | Same as the Builder |
|
|
197
|
+
| `--stage` | `unit` or `assembly` (default `unit`) |
|
|
198
|
+
|
|
199
|
+
Stdout: one JSON object.
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"verdict": "pass",
|
|
204
|
+
"report": "optional; required when not pass"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| `verdict` | Meaning |
|
|
209
|
+
| ---------------- | -------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| `pass` | Next Gate, or validate if this was the last. |
|
|
211
|
+
| `fail-retryable` | Stop the sequence. New Attempt if budget remains; next Builder gets `--report` with this `report`. |
|
|
212
|
+
| `fail-blocking` | Stop the sequence. Escalate. No further Attempt. |
|
|
213
|
+
|
|
214
|
+
| What happened | Verdict used |
|
|
215
|
+
| --------------------------------- | ------------------------------------------------------------ |
|
|
216
|
+
| Valid JSON with a known `verdict` | that verdict; `report` as given (empty on `pass` if omitted) |
|
|
217
|
+
| Stdout is not that JSON | `fail-blocking` |
|
|
218
|
+
| Cannot spawn | `fail-blocking` |
|
|
219
|
+
| Killed by a clock | `fail-retryable`, report names the clock |
|
|
220
|
+
| Stop signal | Attempt `interrupted` |
|
|
221
|
+
|
|
222
|
+
A Gate that cannot return a verdict on the work returns `fail-blocking`. If it
|
|
223
|
+
must wait on something, it waits inside its own process until it can return
|
|
224
|
+
one of the three verdicts, or until `--gate-timeout-ms` kills it. Implementer
|
|
225
|
+
does not hold an Attempt open without consuming it.
|
|
226
|
+
|
|
227
|
+
## 7. Status
|
|
228
|
+
|
|
229
|
+
Status is the live snapshot of this invocation. It changes at phase boundaries
|
|
230
|
+
(Builder about to run, a Gate about to run, run outcome known). It is not a
|
|
231
|
+
history — that is the Trace. The `label` is meant to be shown as-is.
|
|
232
|
+
|
|
233
|
+
### Shape
|
|
234
|
+
|
|
235
|
+
| Field | Content |
|
|
236
|
+
| -------------- | --------------------------------------------------------------------------- |
|
|
237
|
+
| `phase` | `building`, `gating`, `validated`, `escalated`, `interrupted`, or `invalid` |
|
|
238
|
+
| `attempt` | Current Attempt number. Absent on `invalid`. |
|
|
239
|
+
| `max_attempts` | From `--max-attempts`. Absent on `invalid`. |
|
|
240
|
+
| `gate_id` | The Gate about to run. Present only when `phase` is `gating`. |
|
|
241
|
+
| `label` | Canonical display string. See below. |
|
|
242
|
+
|
|
243
|
+
### Label
|
|
244
|
+
|
|
245
|
+
Built by Implementer. Do not invent another spelling.
|
|
246
|
+
|
|
247
|
+
| `phase` | `label` | Example |
|
|
248
|
+
| ------------- | ------------------------------------ | ------------------------- |
|
|
249
|
+
| `building` | `building:attempt-<n>:<max>` | `building:attempt-1:3` |
|
|
250
|
+
| `gating` | `gating:<gate_id>:attempt-<n>:<max>` | `gating:lint:attempt-1:3` |
|
|
251
|
+
| `validated` | `validated:attempt-<n>:<max>` | `validated:attempt-2:3` |
|
|
252
|
+
| `escalated` | `escalated:attempt-<n>:<max>` | `escalated:attempt-3:3` |
|
|
253
|
+
| `interrupted` | `interrupted:attempt-<n>:<max>` | `interrupted:attempt-1:3` |
|
|
254
|
+
| `invalid` | `invalid` | `invalid` |
|
|
255
|
+
|
|
256
|
+
`<n>` and `<max>` are decimal, no padding. `<gate_id>` is the id from `--gate`.
|
|
257
|
+
|
|
258
|
+
### When it is announced
|
|
259
|
+
|
|
260
|
+
| Moment | `phase` |
|
|
261
|
+
| ----------------------------------------------------- | ------------ |
|
|
262
|
+
| Arguments or workspace unusable | `invalid` |
|
|
263
|
+
| Builder about to spawn | `building` |
|
|
264
|
+
| A Gate about to spawn | `gating` |
|
|
265
|
+
| Run outcome `validated` / `escalated` / `interrupted` | that outcome |
|
|
266
|
+
|
|
267
|
+
Announced on stdout as a JSON line (`event`: `status`) carrying the fields
|
|
268
|
+
above. If `--on-status` was given, Implementer also runs that command, cwd
|
|
269
|
+
unchanged (not the workspace), and appends:
|
|
270
|
+
|
|
271
|
+
| Argument | Content |
|
|
272
|
+
| ---------------- | ------------------------------------- |
|
|
273
|
+
| `--id` | Task id |
|
|
274
|
+
| `--label` | The label |
|
|
275
|
+
| `--phase` | The phase |
|
|
276
|
+
| `--attempt` | Present when the phase has an Attempt |
|
|
277
|
+
| `--max-attempts` | Present when the phase has an Attempt |
|
|
278
|
+
| `--gate-id` | Present when `phase` is `gating` |
|
|
279
|
+
|
|
280
|
+
`--on-status` does not count against Attempt or Task clocks. A non-zero exit,
|
|
281
|
+
a missing command, or a hang that Implementer must kill does **not** change
|
|
282
|
+
the run outcome. Visibility must not take the Task down.
|
|
283
|
+
|
|
284
|
+
## 8. Trace and progress
|
|
285
|
+
|
|
286
|
+
### Trace
|
|
287
|
+
|
|
288
|
+
What an Attempt leaves so the result can be understood without replaying.
|
|
289
|
+
|
|
290
|
+
| Field | Content |
|
|
291
|
+
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
292
|
+
| `task_id` | From `--id`. |
|
|
293
|
+
| `attempt` | Number, from 1. |
|
|
294
|
+
| `builder` | Input (intention, definition of done, previous `--report` if any) and result (completed / failed / timed out / interrupted / refused, plus detail). |
|
|
295
|
+
| `gates` | One entry per Gate **that ran**, in order: `id`, verdict, report. Gates not reached are absent. |
|
|
296
|
+
| `ended` | `validated`, `fail-retryable`, `fail-blocking`, or `interrupted`. |
|
|
297
|
+
|
|
298
|
+
`escalated` after at least one Attempt **must** carry that Attempt's Trace.
|
|
299
|
+
An escalation with no Trace when an Attempt existed is invalid.
|
|
300
|
+
`invalid-invocation` has no Trace.
|
|
301
|
+
|
|
302
|
+
### Progress
|
|
303
|
+
|
|
304
|
+
JSON lines on stdout. A `status` line is the live snapshot (§7). The other
|
|
305
|
+
lines rebuild the Trace.
|
|
306
|
+
|
|
307
|
+
| `event` | When |
|
|
308
|
+
| ------------------ | --------------------------- |
|
|
309
|
+
| `status` | Each time Status changes |
|
|
310
|
+
| `attempt-started` | Builder about to spawn |
|
|
311
|
+
| `builder-finished` | Builder ended or was killed |
|
|
312
|
+
| `gate-finished` | A Gate ended or was killed |
|
|
313
|
+
| `attempt-finished` | Attempt `ended` is known |
|
|
314
|
+
| `run-finished` | Run outcome is known |
|
|
315
|
+
|
|
316
|
+
Each line carries `task_id`, `attempt` when an Attempt exists, and enough
|
|
317
|
+
to rebuild the Trace without reading the workspace.
|
|
318
|
+
|
|
319
|
+
Implementer creates no file under `--workspace`. The Builder (and Gates, if
|
|
320
|
+
they must) may write there — that is the work. Implementer deletes nothing
|
|
321
|
+
there.
|
|
322
|
+
|
|
323
|
+
## 9. Run outcomes
|
|
324
|
+
|
|
325
|
+
Exactly one per invocation. The meaning is entirely inside this process.
|
|
326
|
+
|
|
327
|
+
| Outcome | Meaning |
|
|
328
|
+
| -------------------- | --------------------------------------------------------------------------------- |
|
|
329
|
+
| `invalid-invocation` | Arguments or workspace unusable. No Attempt. |
|
|
330
|
+
| `validated` | An Attempt ended `validated`. |
|
|
331
|
+
| `escalated` | `fail-blocking`, or a retryable failure with no Attempts left. |
|
|
332
|
+
| `interrupted` | Stop signal. |
|
|
333
|
+
|
|
334
|
+
No partial validation. Implementer never deletes `--workspace`.
|
|
335
|
+
|
|
336
|
+
## 10. Acceptance
|
|
337
|
+
|
|
338
|
+
1. No `--gate`, Builder exits 0 → `validated` after Attempt 1.
|
|
339
|
+
2. A `fail-retryable` Gate → new Attempt from the first Gate; Builder receives `--report` with that `report`.
|
|
340
|
+
3. A Gate that passed on Attempt 1 runs again on Attempt 2.
|
|
341
|
+
4. `fail-blocking` → `escalated`, no further Attempt, with a Trace.
|
|
342
|
+
5. `--max-attempts` exhausted on retryable failure → `escalated`, with a Trace.
|
|
343
|
+
6. SIGTERM during Builder or a Gate → `interrupted`, workspace still on disk.
|
|
344
|
+
7. Implementer never deletes the workspace, never requires a Gate, never writes an intention or report file.
|
|
345
|
+
8. The loop's decisions are unit-testable with no child process.
|
|
346
|
+
9. Entering the Builder on Attempt 1 of `--max-attempts 3` announces `label` `building:attempt-1:3` on stdout, and via `--on-status` when that argument is set.
|
|
347
|
+
10. `--on-status` exiting non-zero does not change a `validated` run.
|
|
348
|
+
|
|
349
|
+
How to build and run this Transformer: [README.md](./README.md).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-args.d.ts","sourceRoot":"","sources":["../../src/args/parse-args.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAwB,WAAW,EAAE,MAAM,aAAa,CAAC;AAqErE;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CA0RrD"}
|