yadflow 2.5.0 → 2.6.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/CHANGELOG.md +11 -2
- package/README.md +44 -22
- package/bin/yad.mjs +5 -0
- package/cli/doctor.mjs +1 -0
- package/cli/manifest.mjs +18 -2
- package/cli/ship.mjs +37 -0
- package/docs/index.html +44 -13
- package/package.json +2 -2
- package/skills/sdlc/config.yaml +7 -2
- package/skills/sdlc/install.sh +1 -1
- package/skills/sdlc/module-help.csv +7 -4
- package/skills/yad-checks/references/check-gates.md +58 -2
- package/skills/yad-checks/templates/checks/commit-message.sh +82 -0
- package/skills/yad-checks/templates/github/yad-checks.yml +27 -0
- package/skills/yad-checks/templates/github/yad-hub-checks.yml +36 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +20 -0
- package/skills/yad-checks/templates/gitlab/yad-hub-checks.gitlab-ci.yml +39 -0
- package/skills/yad-commit/SKILL.md +66 -0
- package/skills/yad-engineer-review/SKILL.md +86 -0
- package/skills/{yad-ship → yad-engineer-review}/references/ship-and-record.md +2 -2
- package/skills/{yad-ship → yad-engineer-review}/templates/.coderabbit.yaml +1 -1
- package/skills/yad-epic/references/state-schema.md +1 -1
- package/skills/yad-implement/SKILL.md +1 -1
- package/skills/yad-implement/references/implement-conventions.md +1 -1
- package/skills/yad-open-pr/SKILL.md +72 -0
- package/skills/yad-pr-template/templates/checks/pr-template.sh +62 -0
- package/skills/yad-pr-template/templates/checks/pr-title.sh +51 -0
- package/skills/yad-run/SKILL.md +2 -2
- package/skills/yad-run/references/run-loop.md +4 -4
- package/skills/yad-ship/SKILL.md +44 -66
- package/skills/yad-spec/SKILL.md +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-template gate.
|
|
3
|
+
# The PR/MR BODY must actually USE the committed template — i.e. carry its required sections so the
|
|
4
|
+
# review (and risk-route.sh) has the inputs it needs. This catches an empty / free-form description
|
|
5
|
+
# that bypassed the template.
|
|
6
|
+
# --profile code (default) — the code-repo task template (yad-pr-template templates/<platform>/):
|
|
7
|
+
# requires `## Summary`, `## Impact & Risk`, `## Checklist`, and a filled `Risk level:` (low|medium|high).
|
|
8
|
+
# --profile hub — the front-half artifact-review template (templates/hub/<platform>/):
|
|
9
|
+
# requires `## Artifact under review`, `## Impact & Risk (front-half)`, `## Checklist`, and a `Risk tags:` line.
|
|
10
|
+
# The body is passed as a FILE path (single positional arg); CI writes the event body to a temp file
|
|
11
|
+
# (GitHub: github.event.pull_request.body; GitLab: $CI_MERGE_REQUEST_DESCRIPTION).
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
PROFILE=code
|
|
15
|
+
ARGS=()
|
|
16
|
+
while [ $# -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--profile) PROFILE="${2:-code}"; shift 2 ;;
|
|
19
|
+
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
20
|
+
*) ARGS+=("$1"); shift ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-template]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
24
|
+
|
|
25
|
+
BODY="${ARGS[0]:-}"
|
|
26
|
+
if [ -z "$BODY" ] || [ ! -f "$BODY" ]; then
|
|
27
|
+
echo "FAIL [pr-template]: body file not found — pass the PR/MR description as a file path."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
rc=0
|
|
32
|
+
require_heading() {
|
|
33
|
+
if ! grep -qiE "^[[:space:]]*$1[[:space:]]*$" "$BODY"; then
|
|
34
|
+
echo "FAIL [pr-template]: missing section '${2}' — the PR/MR body does not use the template."
|
|
35
|
+
rc=1
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if [ "$PROFILE" = hub ]; then
|
|
40
|
+
require_heading '## Artifact under review' '## Artifact under review'
|
|
41
|
+
require_heading '## Impact & Risk \(front-half\)' '## Impact & Risk (front-half)'
|
|
42
|
+
require_heading '## Checklist' '## Checklist'
|
|
43
|
+
if ! grep -qiE '(\*\*)?Risk tags:' "$BODY"; then
|
|
44
|
+
echo "FAIL [pr-template]: missing 'Risk tags:' line (front-half Impact & Risk)."
|
|
45
|
+
rc=1
|
|
46
|
+
fi
|
|
47
|
+
else
|
|
48
|
+
require_heading '## Summary' '## Summary'
|
|
49
|
+
require_heading '## Impact & Risk' '## Impact & Risk'
|
|
50
|
+
require_heading '## Checklist' '## Checklist'
|
|
51
|
+
# Risk level must be present AND filled with a real value (not the <placeholder>).
|
|
52
|
+
rl="$(grep -iE '(\*\*)?Risk level:' "$BODY" | head -1 \
|
|
53
|
+
| sed -E 's/<!--.*$//; s/^[^:]*://; s/[*`]//g; s/^[[:space:]]*//; s/[[:space:]]*$//' || true)"
|
|
54
|
+
rl="$(printf '%s' "$rl" | tr 'A-Z' 'a-z' | grep -oE 'low|medium|high' | head -1 || true)"
|
|
55
|
+
if [ -z "$rl" ]; then
|
|
56
|
+
echo "FAIL [pr-template]: 'Risk level:' missing or not set to low|medium|high."
|
|
57
|
+
rc=1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
[ "$rc" = 0 ] && echo "PASS [pr-template]: body uses the ${PROFILE} template (required sections present)."
|
|
62
|
+
exit "$rc"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pr-title gate.
|
|
3
|
+
# The PR/MR TITLE must follow the convention for its repo kind:
|
|
4
|
+
# --profile code (default) — a Conventional-Commits subject "<type>: <description>", no trailing
|
|
5
|
+
# period (config.yaml build.pr_title_style: same_as_commit_subject; one task = one PR, the title is
|
|
6
|
+
# the squash-merge subject). Keep <type> in sync with cli/manifest.mjs COMMIT_TYPES.
|
|
7
|
+
# --profile hub — a front-half artifact-review title "review: <artifact> (EP-<slug>)", the shape
|
|
8
|
+
# `yad gate open` creates (cli/gate.mjs).
|
|
9
|
+
# The title is passed as the (single) positional arg; CI injects it from the event payload
|
|
10
|
+
# (GitHub: github.event.pull_request.title; GitLab: $CI_MERGE_REQUEST_TITLE).
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
PROFILE=code
|
|
14
|
+
ARGS=()
|
|
15
|
+
while [ $# -gt 0 ]; do
|
|
16
|
+
case "$1" in
|
|
17
|
+
--profile) PROFILE="${2:-code}"; shift 2 ;;
|
|
18
|
+
--profile=*) PROFILE="${1#*=}"; shift ;;
|
|
19
|
+
*) ARGS+=("$1"); shift ;;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
case "$PROFILE" in code|hub) ;; *) echo "FAIL [pr-title]: unknown --profile '$PROFILE' (code|hub)."; exit 1 ;; esac
|
|
23
|
+
|
|
24
|
+
TITLE="${ARGS[0]:-}"
|
|
25
|
+
if [ -z "$TITLE" ]; then
|
|
26
|
+
echo "FAIL [pr-title]: empty title — pass the PR/MR title as the argument."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
TYPES='feat|fix|docs|refactor|test|perf|build|ci|chore|revert'
|
|
31
|
+
|
|
32
|
+
if [ "$PROFILE" = hub ]; then
|
|
33
|
+
# review: <artifact> (EP-<slug>)
|
|
34
|
+
if printf '%s' "$TITLE" | grep -qE '^review: .+ \(EP-[a-z0-9-]+\)$'; then
|
|
35
|
+
echo "PASS [pr-title]: '${TITLE}' (profile: hub)"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
echo "FAIL [pr-title]: '${TITLE}' is not a hub review title 'review: <artifact> (EP-<slug>)'."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# code profile — Conventional-Commits subject (optional scope + breaking `!`), no trailing period.
|
|
43
|
+
if ! printf '%s' "$TITLE" | grep -qE "^(${TYPES})(\([a-z0-9._-]+\))?!?: .+"; then
|
|
44
|
+
echo "FAIL [pr-title]: '${TITLE}' is not '<type>(<scope>)?!?: <description>' (type one of: ${TYPES//|/, })."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
if printf '%s' "$TITLE" | grep -qE '\.$'; then
|
|
48
|
+
echo "FAIL [pr-title]: '${TITLE}' must not end with a period."
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
echo "PASS [pr-title]: '${TITLE}' (profile: code)"
|
package/skills/yad-run/SKILL.md
CHANGED
|
@@ -70,7 +70,7 @@ Walk the steps for `repo` starting at `from`/`currentStep`. For each step:
|
|
|
70
70
|
the next step, and **continue the loop** (this is the Step B auto-advance for `checks`).
|
|
71
71
|
- else (**`human_approve`**) → set the step `done`/`in_review`, **stop** and report
|
|
72
72
|
"waiting for a human at `<next-step>`".
|
|
73
|
-
5. **Always stop at `engineer-review`** (it is `locked`): hand off to `yad-
|
|
73
|
+
5. **Always stop at `engineer-review`** (it is `locked`): hand off to `yad-engineer-review` for the human merge
|
|
74
74
|
gate, which finalizes the trust verdict (confirm/override the provisional one).
|
|
75
75
|
|
|
76
76
|
### `action: set-dial` — earn (or revert) a step's automation
|
|
@@ -106,4 +106,4 @@ reversible (build plan §Safety). Report the new state and that `yad-status` wil
|
|
|
106
106
|
- The loop, the trust-verdict derivation, and the threshold predicate: `references/run-loop.md`.
|
|
107
107
|
- State/trust schemas: `../yad-epic/references/state-schema.md`.
|
|
108
108
|
- The steps it drives: `../yad-spec/`, `../yad-implement/`, `../yad-checks/`; the human gate it
|
|
109
|
-
hands off to: `../yad-
|
|
109
|
+
hands off to: `../yad-engineer-review/`.
|
|
@@ -14,7 +14,7 @@ spec → tasks → implement → checks → engineer-review(locked)
|
|
|
14
14
|
|
|
15
15
|
`spec` and `tasks` are the two legs of `yad-spec` (the heavy ceremony, then the atomic `tasks.md`).
|
|
16
16
|
`implement` is one atomic task via `yad-implement`. `checks` is `yad-checks (action: run)`.
|
|
17
|
-
`engineer-review` is the human gate at `yad-
|
|
17
|
+
`engineer-review` is the human gate at `yad-engineer-review` — always a stop, never automated.
|
|
18
18
|
|
|
19
19
|
## The loop (pseudocode)
|
|
20
20
|
|
|
@@ -40,7 +40,7 @@ while step is a back step (not engineer-review):
|
|
|
40
40
|
else: # human_approve
|
|
41
41
|
bs.step.status = "done"; persist; STOP and report "waiting for human at <next>"
|
|
42
42
|
|
|
43
|
-
# reached engineer-review: always stop, hand to yad-
|
|
43
|
+
# reached engineer-review: always stop, hand to yad-engineer-review (human gate, finalizes the verdict)
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
`ranBy` is `machine` when the *previous* step's effective dial caused this step to run without a human
|
|
@@ -62,7 +62,7 @@ per-step dial says. `engineer-review` and the five front states are covered by `
|
|
|
62
62
|
## Deriving signals & the provisional verdict
|
|
63
63
|
|
|
64
64
|
`signals` are the raw facts of the run; the **provisional `verdict`** is derived from them. The human
|
|
65
|
-
gate for each step (the engineer review at `yad-
|
|
65
|
+
gate for each step (the engineer review at `yad-engineer-review` for `implement`; spec acceptance for `spec`;
|
|
66
66
|
first-consume for `tasks`; the gate itself for `checks`) later confirms or overrides the verdict and
|
|
67
67
|
finalizes the entry — a human always has the last word on the trust signal.
|
|
68
68
|
|
|
@@ -114,7 +114,7 @@ Reverting (`to: human_approve`) is never gated — automation must be reversible
|
|
|
114
114
|
|
|
115
115
|
## What stays human, always
|
|
116
116
|
|
|
117
|
-
- `engineer-review` — the merge gate. `yad-run` always stops here and hands to `yad-
|
|
117
|
+
- `engineer-review` — the merge gate. `yad-run` always stops here and hands to `yad-engineer-review`.
|
|
118
118
|
- The five front states (`epic`, `architecture`, `ui-design`, `stories`, `test-cases`) — not in
|
|
119
119
|
`back_steps`, in `locked_steps`; the dial-setter refuses them.
|
|
120
120
|
- Any contract-surface change — halts the loop and routes back to the architecture gate, regardless of
|
package/skills/yad-ship/SKILL.md
CHANGED
|
@@ -1,86 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yad-ship
|
|
3
|
-
description: 'Build-half
|
|
3
|
+
description: 'Build-half helper of the gated SDLC — commit AND open the task PR/MR in one step. A thin orchestration over yad-commit then yad-open-pr: commit the staged atomic change by the conventions (Conventional-Commits subject, Task → Contract-Change → Co-Authored-By trailers, --ai footer, ≤3-file atomic guard), then push the branch and open the PR/MR from the committed template with the roster auto-assigned. The PR step runs ONLY if the commit lands (a failed commit, tripped guard, or --dry-run stops before pushing). Drives the `yad ship` CLI; never merges. Use when the user says "ship this task", "commit and open the PR", or "commit and raise the MR". (For the engineer review + merge, use yad-engineer-review.)'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# SDLC —
|
|
6
|
+
# SDLC — Commit + Open PR/MR (build-half helper)
|
|
7
7
|
|
|
8
|
-
**Goal:**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**nothing auto-advances**; the engineer owns the merge.
|
|
8
|
+
**Goal:** Do the two routine build-half hand-actions for ONE atomic task in a single step — **commit by
|
|
9
|
+
convention, then open the task PR/MR** — so an implemented diff becomes a reviewable PR/MR without two
|
|
10
|
+
separate invocations. It is a thin wrapper over `yad-commit` and `yad-open-pr`; it holds no logic of
|
|
11
|
+
its own and **never merges**. The engineer review + merge are Step E (`yad-engineer-review`).
|
|
13
12
|
|
|
14
13
|
## Conventions
|
|
15
14
|
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
15
|
+
- Run **inside the code repo** under `{project-root}/demo-repos/<repo>/` (or `--repo <name>`), on the
|
|
16
|
+
task branch with the atomic change **already staged** (`git add`).
|
|
17
|
+
- Inherits every convention of the two steps it wraps:
|
|
18
|
+
- Commit: subject `<type>: <lowercase imperative, no trailing period>`, fixed trailer order
|
|
19
|
+
`Task → Contract-Change → Co-Authored-By`, the `--ai` co-author footer, the ≤3-file atomic guard
|
|
20
|
+
(`../yad-commit/SKILL.md`).
|
|
21
|
+
- PR/MR: pushed branch, the committed template prefilled, title defaulting to the commit subject,
|
|
22
|
+
roster auto-assign, risk routing (`../yad-open-pr/SKILL.md`).
|
|
23
|
+
- **Order matters:** the PR/MR is opened **only if the commit lands**. A failed commit, a tripped
|
|
24
|
+
atomic guard, or `--dry-run` stops the step before anything is pushed.
|
|
25
25
|
|
|
26
26
|
## Inputs
|
|
27
27
|
|
|
28
|
-
- `
|
|
29
|
-
- `
|
|
30
|
-
-
|
|
31
|
-
|
|
28
|
+
- `type` / `message` — the commit type + subject (required), `--type <t> -m "<subject>"`.
|
|
29
|
+
- `ai` — co-author footer: `claude|copilot|cursor|coderabbit|none` (default `none`).
|
|
30
|
+
- `task` — Task trailer (optional; derived from the branch when omitted).
|
|
31
|
+
- `contractChange` — flag; marks the contract surface touched (commit trailer + PR escalation).
|
|
32
|
+
- `repo` / `risk` / `base` / `platform` / `title` — PR/MR options (see `yad-open-pr`).
|
|
32
33
|
|
|
33
34
|
## On Activation
|
|
34
35
|
|
|
35
|
-
### Step 1 —
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
a **second set of eyes, never the authority**: it cannot approve or merge. Where CodeRabbit can't run
|
|
39
|
-
(no remote), run an equivalent AI first-pass by hand and capture its notes. Record that the AI review
|
|
40
|
-
ran; surface its findings to the engineer. Do **not** treat AI approval as a gate.
|
|
36
|
+
### Step 1 — Confirm the staged atomic change
|
|
37
|
+
Confirm you are on the task branch (not the default branch) and the atomic change is staged within its
|
|
38
|
+
file boundary. Preview the commit with `--dry-run` if unsure.
|
|
41
39
|
|
|
42
|
-
### Step 2 —
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
### Step 2 — Commit + open in one step
|
|
41
|
+
Run from the repo root:
|
|
42
|
+
```
|
|
43
|
+
yad ship --type <type> -m "<subject>" [--ai <id>] [--task <id>] [--contract-change] \
|
|
44
|
+
[--repo <name>] [--risk <level>] [--title "<subject>"]
|
|
45
|
+
```
|
|
46
|
+
The CLI runs `yad commit` and, only if it succeeds, `yad open-pr` — committing the change, pushing the
|
|
47
|
+
branch, and opening the PR/MR with the template prefilled and reviewers auto-assigned.
|
|
49
48
|
|
|
50
|
-
### Step 3 —
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- **Record the ship** — append to `epics/<epic>/.sdlc/build-log.json`:
|
|
55
|
-
```json
|
|
56
|
-
{ "story": "<story>", "task": "<task>", "repo": "<repo>", "branch": "feat/<story>-<task>-…",
|
|
57
|
-
"pr": "<url|#>", "mergeCommit": "<sha>", "gates": ["spec-link","contract-check","build-test-lint"],
|
|
58
|
-
"ai_review": "coderabbit (advisory)", "engineer_review": [{"approver":"<name>","role":"<role>","domain":"<opt>"}],
|
|
59
|
-
"risk": "<low|medium|high>", "shippedAt": "<YYYY-MM-DD>" }
|
|
60
|
-
```
|
|
61
|
-
- **Update the story state** — when **every** task in `specs/<story>/tasks.md` has a ship record, set
|
|
62
|
-
the story frontmatter `status: shipped`; otherwise `status: in-build`. The chain
|
|
63
|
-
**epic → story → task → PR → mergeCommit** is now traceable end to end.
|
|
64
|
-
- **Finalize the trust verdict (Phase 4).** If this story has a `build-state/<story>.json` (it ran
|
|
65
|
-
through `yad-run`), the engineer **confirms or overrides** the provisional trust verdict that the
|
|
66
|
-
orchestrator derived for this run, and the final verdict is written to
|
|
67
|
-
`epics/<epic>/.sdlc/trust-log.json`. The human has the last word on the trust signal: a diff merged
|
|
68
|
-
as authored is `approved-unchanged`; one the engineer edited before merge is `approved-with-edits`;
|
|
69
|
-
a rejected one is `rejected`. This is the evidence that later earns a step its `machine_advance`
|
|
70
|
-
(it never weakens the merge gate — the engineer still owns the merge).
|
|
49
|
+
### Step 3 — Route + stop (no merge)
|
|
50
|
+
On `high` risk or a contract touch, run `bash checks/risk-route.sh <pr-body>` for the required
|
|
51
|
+
domain-owner reviewers. Report the commit + the PR/MR URL. The PR now runs the check gates (Step C);
|
|
52
|
+
the engineer review and merge are Step E (`yad-engineer-review`).
|
|
71
53
|
|
|
72
|
-
|
|
73
|
-
Report what shipped and the story's state. Do not advance anything else; the front-half `state.json`
|
|
74
|
-
stays as it was (`ready-for-build`). The build half is recorded in `build-log.json` + the story status.
|
|
54
|
+
## Hard rules
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- **
|
|
79
|
-
- **
|
|
80
|
-
- **Ship only after gates + engineer review.** No gate skipped; the human owns the merge.
|
|
81
|
-
- **Nothing auto-advances.** Step E records human decisions in files; it never machine-advances.
|
|
56
|
+
- **One staged atomic task = one commit = one PR/MR.** Never bundle; never open from the default branch.
|
|
57
|
+
- **No PR without a landed commit.** A failed/`--dry-run` commit stops the step before pushing.
|
|
58
|
+
- **High risk routes to domain owners** — the same escalation as the gate.
|
|
59
|
+
- **Shipping here never merges.** The human owns the merge in `yad-engineer-review`.
|
|
82
60
|
|
|
83
61
|
## Reference
|
|
84
|
-
- The
|
|
85
|
-
- The
|
|
86
|
-
- The
|
|
62
|
+
- The two steps this wraps: `../yad-commit/SKILL.md` and `../yad-open-pr/SKILL.md`.
|
|
63
|
+
- The gates the PR must pass: `../yad-checks/references/check-gates.md`.
|
|
64
|
+
- The engineer review + merge that follow: `../yad-engineer-review/SKILL.md`.
|
package/skills/yad-spec/SKILL.md
CHANGED
|
@@ -101,7 +101,7 @@ verdict is **anchored to the human who accepts the spec**, never self-graded:
|
|
|
101
101
|
`human_edited_spec: true`);
|
|
102
102
|
- the spec is rejected or the ceremony re-run → `rejected`.
|
|
103
103
|
`yad-run` records a provisional entry when the spec is generated; this acceptance finalizes it (same
|
|
104
|
-
pattern as the engineer review finalizing `implement` at `yad-
|
|
104
|
+
pattern as the engineer review finalizing `implement` at `yad-engineer-review`). Append the finalized entry to
|
|
105
105
|
`epics/<epic>/.sdlc/trust-log.json` (schema:
|
|
106
106
|
`../yad-epic/references/state-schema.md`). **Run standalone, no trust entry is written** — the
|
|
107
107
|
log measures orchestrated runs. `spec` stays `human_approve` until its slice clears the threshold;
|