@manhquy/flow-skill 0.5.1 → 0.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/package.json +1 -1
- package/skills/flow/SKILL.md +12 -5
- package/skills/flow/_templates/02-scope.md +9 -0
- package/skills/flow/_templates/03-prd.md +11 -0
- package/skills/flow/_templates/05-contract.md +8 -0
- package/skills/flow/_templates/card.md +4 -0
- package/skills/flow/eval/fixtures/converge/conv-01/cards/C-001.md +21 -0
- package/skills/flow/eval/fixtures/converge/conv-01/flow/03-prd.md +7 -0
- package/skills/flow/eval/fixtures/converge/conv-01/src/app.py +3 -0
- package/skills/flow/eval/fixtures/converge/gap-01/cards/C-001.md +21 -0
- package/skills/flow/eval/fixtures/converge/gap-01/cards/C-002.md +21 -0
- package/skills/flow/eval/fixtures/converge/gap-01/flow/03-prd.md +8 -0
- package/skills/flow/eval/fixtures/converge/gap-01/flow/05-contract.md +7 -0
- package/skills/flow/eval/fixtures/converge/gap-01/src/app.py +11 -0
- package/skills/flow/eval/fixtures/converge/manifest.tsv +3 -0
- package/skills/flow/law/CLAUDE.md +7 -0
- package/skills/flow/references/artifact-lifecycle.md +58 -0
- package/skills/flow/references/clarify.md +191 -0
- package/skills/flow/references/command-dispatch.md +3 -1
- package/skills/flow/references/concierge.md +2 -0
- package/skills/flow/references/converge.md +109 -0
- package/skills/flow/references/flow-catalog.tsv +2 -0
- package/skills/flow/references/gate-rules.md +32 -0
- package/skills/flow/references/mode-work.md +11 -1
- package/skills/flow/references/stage-state-machine.md +9 -1
- package/skills/flow/runner/attestations.sh +69 -26
- package/skills/flow/runner/flow.sh +420 -5
- package/skills-manifest.json +14 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Converge — append-only remainder cards (plan vs present code)
|
|
2
|
+
|
|
3
|
+
Flow can prove a *card* is not hollow (Verify + world-state Evidence). It cannot prove the
|
|
4
|
+
*plan* was fully expressed in the card tree. `converge` closes that gap: assess present code
|
|
5
|
+
against the plan, then **append** remainder cards for whatever is still missing. It never
|
|
6
|
+
rewrites history and never touches application code.
|
|
7
|
+
|
|
8
|
+
`converge` is the **flow-back closer** named in `references/artifact-lifecycle.md`.
|
|
9
|
+
|
|
10
|
+
## When
|
|
11
|
+
- After the originally planned cards are `done` but you want to confirm the codebase actually
|
|
12
|
+
expresses every PRD `FRn`, contract interface, and constitution `INV-n`.
|
|
13
|
+
- Mid-cycle when code drifted ahead of (or behind) the plan and you need to reconcile.
|
|
14
|
+
- Not a `next` gate. Opt-in. The runner half only *appends* cards from a payload you produce.
|
|
15
|
+
|
|
16
|
+
## Operating constraints (non-negotiable)
|
|
17
|
+
- **Append-only.** Never edit, renumber, or delete an existing `cards/C-*.md`.
|
|
18
|
+
- **Never touch application code.** `converge` emits cards; a human/`auto` builds them later.
|
|
19
|
+
- **Byte-identical when converged.** If nothing remains, write nothing and print `CONVERGED`.
|
|
20
|
+
- **Assess present code, not a diff.** Read what the tree does now vs what the plan requires;
|
|
21
|
+
this is not a git-diff or branch comparison.
|
|
22
|
+
- **Transactional.** Either every remainder card is written, or none is (no partial append).
|
|
23
|
+
|
|
24
|
+
## Intent inventory (what you load)
|
|
25
|
+
From the plan: PRD `## Features` (`FRn` + observable result), `05-contract.md` interfaces
|
|
26
|
+
(request/response/failure shapes, access), constitution `INV-n` MUST rows, the card set and
|
|
27
|
+
their claimed scope. This is the set of promises the code must keep.
|
|
28
|
+
|
|
29
|
+
## Code-scope map (what you check it against)
|
|
30
|
+
Read the source the cards claim to have built. For each promise, decide whether the present
|
|
31
|
+
code keeps it. Judge behaviour and interfaces, not line counts.
|
|
32
|
+
|
|
33
|
+
## Gap taxonomy (closed set)
|
|
34
|
+
- **missing** — a promised `FRn` / interface / MUST has no implementation in the tree.
|
|
35
|
+
- **partial** — implemented but incomplete against its observable result / failure shape.
|
|
36
|
+
- **contradicts** — the code does something the plan/contract forbids or specifies differently.
|
|
37
|
+
- **unrequested** — code surface exists that no `FRn`/interface/decision asked for.
|
|
38
|
+
|
|
39
|
+
## Severity (closed set)
|
|
40
|
+
- **CRITICAL** — a constitution MUST or a P1/baseline `FRn` is missing or contradicted.
|
|
41
|
+
- **HIGH** — a non-baseline `FRn` or a named contract interface missing/partial.
|
|
42
|
+
- **MED / LOW** — partials with a workaround; cosmetic contradictions.
|
|
43
|
+
|
|
44
|
+
## Disposition
|
|
45
|
+
- `missing` / `partial` / `contradicts` → a remainder card (`implements:` the `FRn`/interface,
|
|
46
|
+
`## Scope` citing the source-ref + gap-type, `deps:` on the last related card).
|
|
47
|
+
- `unrequested` → a **review card** (or a `DEBT` line), never an instruction to delete code.
|
|
48
|
+
|
|
49
|
+
## Present the findings first
|
|
50
|
+
Emit a findings table (id, gap-type, severity, source-ref, one-line) to the operator BEFORE
|
|
51
|
+
any card is written. Then hand the payload to the runner, which appends the cards.
|
|
52
|
+
|
|
53
|
+
## Payload schema (`flow-converge/v1`)
|
|
54
|
+
Write findings to `.flow/converge-pending.md` (run-state, gitignored — so a `CONVERGED` run
|
|
55
|
+
never dirties `cards/`), then run `/flow converge` (or `flow.sh converge --file <path>`). The
|
|
56
|
+
runner is transactional: it appends every card or none. Format — a header, then one `---`-fenced
|
|
57
|
+
block per finding:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
schema: flow-converge/v1
|
|
61
|
+
findings: 2
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
gap-type: missing
|
|
65
|
+
severity: HIGH
|
|
66
|
+
implements: FR2
|
|
67
|
+
source-ref: 03-prd.md:FR2
|
|
68
|
+
title: implement the mark-task-done endpoint
|
|
69
|
+
deps: C-002
|
|
70
|
+
allowed: src/app.py
|
|
71
|
+
---
|
|
72
|
+
gap-type: unrequested
|
|
73
|
+
severity: LOW
|
|
74
|
+
implements: none
|
|
75
|
+
source-ref: src/app.py:debug_dump
|
|
76
|
+
title: the debug_dump surface no feature asked for
|
|
77
|
+
deps:
|
|
78
|
+
allowed: src/app.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Each block: `gap-type` (missing|partial|contradicts|unrequested — the runner rejects anything
|
|
82
|
+
else and writes nothing), `title` (required), and optional `severity`/`implements`/`deps`/
|
|
83
|
+
`source-ref`/`allowed`. `unrequested` becomes a **review** card (`implements: none`) — never a
|
|
84
|
+
delete instruction. Zero findings (or no payload) → the runner prints `CONVERGED` and writes
|
|
85
|
+
nothing. Appended cards ship with `[FILL]` Verify/Done-evidence (they are not built yet).
|
|
86
|
+
|
|
87
|
+
## Converge challenge
|
|
88
|
+
|
|
89
|
+
*(Sliced by the converge eval judge. This section is the criteria a reviewer applies when
|
|
90
|
+
asked whether a codebase still owes work against its plan.)*
|
|
91
|
+
|
|
92
|
+
A codebase is **converged** only when every promise in the plan is kept by the present code:
|
|
93
|
+
|
|
94
|
+
- Every PRD `FRn` has code that produces its stated observable result — not a stub, not a
|
|
95
|
+
handler that returns the shape without the behaviour, not a sibling feature standing in.
|
|
96
|
+
- Every contract interface exists with its request/response **and failure** shape.
|
|
97
|
+
- Every constitution MUST is honoured.
|
|
98
|
+
- No code contradicts a stated decision, and no unrequested surface is silently load-bearing.
|
|
99
|
+
|
|
100
|
+
Report a **GAP** when at least one promise is not kept by the present code — most commonly a
|
|
101
|
+
declared `FRn` that the source never implements while its card is marked done. Report
|
|
102
|
+
**CONVERGED** only when the present code keeps every declared promise. When the evidence is
|
|
103
|
+
ambiguous, prefer **GAP** — an unflagged missing feature is the failure this check exists to
|
|
104
|
+
catch. A card marked done is not evidence the feature exists; read the source.
|
|
105
|
+
|
|
106
|
+
## Done when
|
|
107
|
+
- [ ] Findings table presented before any write
|
|
108
|
+
- [ ] Remainder cards appended (or `CONVERGED` printed and nothing written)
|
|
109
|
+
- [ ] No existing card edited; no application code touched
|
|
@@ -16,3 +16,5 @@ adr-decision-ask stage-04 next offers the native persona-debate ritual before lo
|
|
|
16
16
|
contract-hardening-ask stage-05 contract offers the native edge-case decomposition ritual to harden the seam before the Contract gate ck-scenario flow
|
|
17
17
|
security-review-ask security-class-card check runs the native STRIDE ritual on every security-class card (guaranteed baseline) ck-security flow
|
|
18
18
|
loop-fix-ask build-verify-numeric-gap status offers the native loop protocol (flow.sh loop-prep/loop-log) when a fix needs more than one attempt against a numeric target ck-loop flow
|
|
19
|
+
open-decision-ask stage-02-or-03-or-05 clarify lists ## Open decisions bullets; skill offers references/clarify.md write-back (opt-in, never a next prereq) - flow
|
|
20
|
+
close-the-gap-ask cards-done-verify-plan converge assess present code vs plan per references/converge.md, then append-only remainder cards (transactional; CONVERGED writes nothing) - flow
|
|
@@ -79,6 +79,15 @@ GO/KILL, no FILL.
|
|
|
79
79
|
- **Offer the forge-idea ritual (opt-in-with-prompt — `references/forge-idea.md`)** when a
|
|
80
80
|
GO/KILL call is genuinely close. It informs the decision; it never decides it — the
|
|
81
81
|
operator still calls GO or KILL.
|
|
82
|
+
- **Assumption vs open decision:** read `## Assumptions`. A bullet that encodes product
|
|
83
|
+
law (who can access what, tenancy, retention, billing, enforcement owner) with no
|
|
84
|
+
operator/ADR authority is an **open decision or a stop**, not a silent default — move
|
|
85
|
+
it under `## Open decisions` (a line like `- [ ] which tenant key?`) or halt. Do not
|
|
86
|
+
invent auth/tenancy/retention/billing to clear `[FILL]`.
|
|
87
|
+
- **Too many open decisions** is a semantic judgment (there is no mechanical cap): if
|
|
88
|
+
the section is a pile, assume or cut rather than interview everything. Offer the
|
|
89
|
+
clarify ritual (`references/clarify.md`, opt-in, never a `next` prereq) when any
|
|
90
|
+
open decision remains.
|
|
82
91
|
|
|
83
92
|
## Stage 03 — PRD
|
|
84
93
|
Mechanical: filled from stage 02, numeric success metric, pain&gain table, no FILL.
|
|
@@ -90,6 +99,14 @@ Mechanical: filled from stage 02, numeric success metric, pain&gain table, no FI
|
|
|
90
99
|
named observation) AND name the v1 feature that kills it? Does *every v1 feature* kill at
|
|
91
100
|
least one pain? Orphans on either side = scope drift.
|
|
92
101
|
- Could a stranger build v1 from this without asking the operator anything?
|
|
102
|
+
- Read Features and NFRs as a reviewer of English, not of code: any unquantified adjective (fast/secure/intuitive) or an `FRn` with no named failure/empty case is a rewrite, not a vibe.
|
|
103
|
+
- **Assumption vs open decision:** read `## Assumptions`. A bullet that encodes product
|
|
104
|
+
law (who can access what, tenancy, retention, billing, enforcement owner) with no
|
|
105
|
+
operator/ADR authority is an **open decision or a stop**, not a silent default — move
|
|
106
|
+
it under `## Open decisions` or halt. Do not invent policy to clear `[FILL]`.
|
|
107
|
+
- **Too many open decisions** is a semantic judgment (no mechanical cap): assume or
|
|
108
|
+
cut rather than interview a pile. Offer the clarify ritual (`references/clarify.md`,
|
|
109
|
+
opt-in, never a `next` prereq) when any open decision remains.
|
|
93
110
|
|
|
94
111
|
## Stage 04 — ADR
|
|
95
112
|
Mechanical: each decision has why + rejected, NOT-doing list, covers storage/auth/deploy.
|
|
@@ -118,6 +135,15 @@ cli=command+flags+output/exit, library=public function+args+return, skill=comman
|
|
|
118
135
|
the cli equivalent: `--out` vs `--output`)?
|
|
119
136
|
- Is the access/effects column real for every interface (web: public/token/admin · non-web:
|
|
120
137
|
writes/side-effects or "none")? Do NOT let a web product blank the access column.
|
|
138
|
+
- Read each write interface as a reviewer of English, not of code: if the failure shape is missing or Access/effects is a vibe word (secure/authenticated/restricted), the seam is not written yet.
|
|
139
|
+
- **Assumption vs open decision:** access/effects or shared shapes that encode product
|
|
140
|
+
law (tenancy, auth, retention, billing) with no operator/ADR authority are an **open
|
|
141
|
+
decision or a stop**, not a silent default — add a `## Open decisions` bullet or halt.
|
|
142
|
+
(Contract has no `## Assumptions` section; do not invent one.) Do not invent
|
|
143
|
+
tenancy/auth from convenience to clear `[FILL]`.
|
|
144
|
+
- **Too many open decisions** is a semantic judgment (no mechanical cap): assume or
|
|
145
|
+
cut rather than interview a pile. Offer the clarify ritual (`references/clarify.md`,
|
|
146
|
+
opt-in, never a `next` prereq) when any open decision remains.
|
|
121
147
|
- One-way rule: this file is planning source of truth. For web the served spec
|
|
122
148
|
(`/openapi.json`) is the runtime artifact of the SAME contract (amend file -> code -> spec).
|
|
123
149
|
For non-web there is no served spec — the no-drift check is the per-type done-evidence.
|
|
@@ -143,6 +169,12 @@ Mechanical: no FILL, valid status, required sections, if done -> verify boxes ch
|
|
|
143
169
|
evidence non-empty.
|
|
144
170
|
**Challenge:**
|
|
145
171
|
- Is the scope ONE thing? If it's two, split the card.
|
|
172
|
+
- **Independent test:** if the field is empty, missing on a value card, or is
|
|
173
|
+
"unit tests pass" / "code merged", split or rewrite. A value card names a
|
|
174
|
+
user-visible proof ("resident files a ticket at /new and sees it on /tickets").
|
|
175
|
+
Scaffold/CI/contract-test/e2e may say `infra` or `none`. Mechanical leftover
|
|
176
|
+
`[FILL]` fails `check` only while the heading remains (the heading is not a
|
|
177
|
+
required `cmd_check` section — §1.6 is cut).
|
|
146
178
|
- Does the diff touch only `## Allowed files`? Drift outside = stop, amend the card first.
|
|
147
179
|
- Do request/response shapes match `flow/05-contract.md` exactly? No improvised shapes.
|
|
148
180
|
- For UI cards: reviewed against `law/DESIGN.md` (tokens, affordance ladder, object-first,
|
|
@@ -21,10 +21,15 @@
|
|
|
21
21
|
in work mode — scope is the operator's call, and killing here is cheap.
|
|
22
22
|
4. **Draft 03 -> 05.** PRD (numeric metric, pain->feature mapping), ADR (delegate to
|
|
23
23
|
`architect`/`bmad-create-architecture`), Contract (the seam; consider `bmad-spec` kernel).
|
|
24
|
-
Run each gate.
|
|
24
|
+
Run each gate. After drafting 02 / 03 / 05, leftover bullets under `## Open decisions`
|
|
25
|
+
are the **only *scheduled*** reason to ask more (via `/flow clarify` + `references/clarify.md`);
|
|
26
|
+
the assumption challenge after a mechanical PASS can still halt (see Rules below).
|
|
27
|
+
Never invent auth, tenancy, retention, or billing to clear `[FILL]`.
|
|
25
28
|
5. **Deliver the card set as one summary.** After stage 05 passes, run `/flow card` to
|
|
26
29
|
create the cards, fill their scopes, and present the whole set + build order + which are
|
|
27
30
|
parallel-safe (`/flow ready`) in a single summary for the operator to review.
|
|
31
|
+
Group cards under PRD `FRn` (the user-visible story), not under layer (all models, then
|
|
32
|
+
all services). Every value card fills `## Independent test`.
|
|
28
33
|
|
|
29
34
|
## Rules that still hold in work mode
|
|
30
35
|
- Every gate must pass mechanically (`flow.sh`) AND semantically (`gate-rules.md`). Drafting
|
|
@@ -33,5 +38,10 @@
|
|
|
33
38
|
- Research must be real (opened competitors, quoted complaints with links) — no fabrication.
|
|
34
39
|
This is the highest-risk stage for an AI author; verify every quote has a source.
|
|
35
40
|
- Scope honesty: do not grade-launder your own C features to B. Call C a C, justify the path.
|
|
41
|
+
- After 02 / 03 / 05, leftover open decisions are the only *scheduled* reason to ask more (via
|
|
42
|
+
clarify). Even with none, the assumption-vs-open-decision challenge after a mechanical PASS
|
|
43
|
+
can still halt or add a bullet (a policy-encoding `## Assumptions` line with no operator/ADR
|
|
44
|
+
authority is an open decision, not a silent default). Never invent auth / tenancy / retention
|
|
45
|
+
/ billing to clear FILL.
|
|
36
46
|
- Done is still world-state evidence; you cannot mark a card done without real proof.
|
|
37
47
|
- Write durable records (intake/decision/story) as you go, same as teach mode.
|
|
@@ -32,7 +32,7 @@ honored outcome, not a failure of the flow.
|
|
|
32
32
|
| 03 PRD | `flow/03-prd.md` | numeric success metric, pain&gain mapping table, user-action->observable-result features |
|
|
33
33
|
| 04 ADR | `flow/04-adr.md` | decisions with why+rejected covering storage/auth/deploy, NOT-doing list |
|
|
34
34
|
| 05 Contract | `flow/05-contract.md` | every feature->endpoint, request+response shapes, auth per endpoint |
|
|
35
|
-
| Card | `cards/C-NNN.md` | one-thing scope, deps, allowed files, verify steps, named done-evidence, pasted evidence when done |
|
|
35
|
+
| Card | `cards/C-NNN.md` | one-thing scope, Independent test (user-visible sentence or infra/none), deps, allowed files, verify steps, named done-evidence, pasted evidence when done |
|
|
36
36
|
|
|
37
37
|
## Shipping stages live inside cards
|
|
38
38
|
Build -> Review -> Deploy -> Verify-live are NOT `/flow next` stages. They happen inside
|
|
@@ -40,6 +40,14 @@ each card: the `## Verify` checklist (run for real) and `## Evidence` (world-sta
|
|
|
40
40
|
Card sequence (scaffold/CI -> vertical slice -> backend -> contract-tests -> UI mock ->
|
|
41
41
|
frontend -> e2e) is governed by `law/CLAUDE.md`.
|
|
42
42
|
|
|
43
|
+
## After a change (artifact lifecycle)
|
|
44
|
+
|
|
45
|
+
Unlock order is not a mutation policy. Default: **living plan + append-only
|
|
46
|
+
cards** — amend `flow/*.md` in place; never rewrite issued cards; a v2 cycle
|
|
47
|
+
is new cards + a plan amendment, not a `specs/` tree. The three named models
|
|
48
|
+
(living / cycle-forward / flow-back) and the converge closer live in
|
|
49
|
+
`references/artifact-lifecycle.md`.
|
|
50
|
+
|
|
43
51
|
## Runner contract
|
|
44
52
|
- `flow.sh` exit `0` = pass/advanced, `1` = gate fail or usage error.
|
|
45
53
|
- Reads/writes `flow/`, `cards/`, `MODE`, `RETRO.md`, and seeds `DESIGN.md` once, under `FLOW_PROJECT_ROOT` (default `$PWD`).
|
|
@@ -534,17 +534,20 @@ _att_card_semantic_fingerprint() { # $1 card file $2 base $3 head -> prints fp;
|
|
|
534
534
|
head="$(_att_full_commit "$head")" || return 1
|
|
535
535
|
tree="$(git -C "$ROOT" rev-parse "${head}^{tree}" 2>/dev/null | tr -d '\r\n')" || return 1
|
|
536
536
|
proj="$(_att_card_contract_projection "$file")"
|
|
537
|
-
# changed paths base..head excluding card path
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
537
|
+
# changed paths base..head excluding card path.
|
|
538
|
+
# Bash 3.2 + set -u: avoid `read st p rest` (unbound rest) and case R*|C* inside
|
|
539
|
+
# command substitution (macOS /bin/bash syntax error). Use name-only only.
|
|
540
|
+
path_manifest="$(
|
|
541
|
+
git -C "$ROOT" diff-tree -r --name-only "$base" "$head" 2>/dev/null \
|
|
542
|
+
| while IFS= read -r p || [ -n "$p" ]; do
|
|
543
|
+
p="$(printf '%s' "$p" | tr -d '\r')"
|
|
544
|
+
[ -z "$p" ] && continue
|
|
545
|
+
[ "$p" = "cards/${id}.md" ] && continue
|
|
546
|
+
mode_oid="$(git -C "$ROOT" ls-tree "$head" -- "$p" 2>/dev/null | awk '{print $1" "$3}')"
|
|
547
|
+
[ -n "$mode_oid" ] || continue
|
|
548
|
+
printf '%s %s\n' "$p" "$mode_oid"
|
|
549
|
+
done | sort
|
|
550
|
+
)"
|
|
548
551
|
body="base=${base}"$'\n'"head=${head}"$'\n'"tree=${tree}"$'\n'"proj<<"$'\n'"${proj}"$'\n'">>"$'\n'"paths<<"$'\n'"${path_manifest}"$'\n'">>"$'\n'
|
|
549
552
|
fmt="$(_att_object_format)"
|
|
550
553
|
oid="$(printf '%s' "$body" | git -C "$ROOT" hash-object --stdin 2>/dev/null | tr -d '\r\n')"
|
|
@@ -680,49 +683,73 @@ revision_oracle_fingerprint: ${_ATT_W_revision_oracle_fingerprint}
|
|
|
680
683
|
}
|
|
681
684
|
|
|
682
685
|
# ---------- process supervisor ----------
|
|
686
|
+
# Prefer setsid (Linux). macOS often lacks setsid: fall back to best-effort kill of
|
|
687
|
+
# the launched PID (not a full session). Live-auto residual is documented for that case.
|
|
688
|
+
_att_has_setsid() { command -v setsid >/dev/null 2>&1; }
|
|
689
|
+
|
|
683
690
|
_att_supervisor_capable() {
|
|
684
691
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-unsupported" ]; then return 1; fi
|
|
685
692
|
if [ "${FLOW_ATTEST_SUPERVISOR:-}" = "force-ok" ]; then return 0; fi
|
|
686
|
-
command -v setsid >/dev/null 2>&1 || return 1
|
|
687
693
|
if [ "${_ATT_SUP_CAP_CACHED:-}" = "1" ]; then
|
|
688
694
|
[ "${_ATT_SUP_CAP_OK:-0}" = "1" ]; return $?
|
|
689
695
|
fi
|
|
690
|
-
_ATT_SUP_CAP_CACHED=1
|
|
696
|
+
_ATT_SUP_CAP_CACHED=1
|
|
697
|
+
_ATT_SUP_CAP_OK=0
|
|
691
698
|
local outf pid pgid
|
|
692
699
|
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-cap.$$")"
|
|
693
700
|
: > "$outf"
|
|
694
|
-
|
|
701
|
+
if _att_has_setsid; then
|
|
702
|
+
( cd "${ROOT:-/tmp}" || exit 127; exec setsid sleep 30 ) >"$outf" 2>&1 &
|
|
703
|
+
else
|
|
704
|
+
( cd "${ROOT:-/tmp}" || exit 127; exec sleep 30 ) >"$outf" 2>&1 &
|
|
705
|
+
fi
|
|
695
706
|
pid=$!
|
|
696
707
|
sleep 0.15 2>/dev/null || true
|
|
697
708
|
pgid="$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')"
|
|
698
709
|
[ -n "$pgid" ] || pgid="$pid"
|
|
699
|
-
|
|
710
|
+
if _att_has_setsid; then
|
|
711
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
712
|
+
else
|
|
713
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
714
|
+
fi
|
|
700
715
|
sleep 0.25 2>/dev/null || true
|
|
701
716
|
if kill -0 "$pid" 2>/dev/null; then
|
|
702
|
-
|
|
717
|
+
if _att_has_setsid; then
|
|
718
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
719
|
+
else
|
|
720
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
721
|
+
fi
|
|
703
722
|
sleep 0.1 2>/dev/null || true
|
|
704
723
|
fi
|
|
705
724
|
wait "$pid" 2>/dev/null || true
|
|
706
725
|
rm -f "$outf" 2>/dev/null || true
|
|
707
|
-
kill -0 "$pid" 2>/dev/null
|
|
726
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
727
|
+
return 1
|
|
728
|
+
fi
|
|
708
729
|
_ATT_SUP_CAP_OK=1
|
|
709
730
|
return 0
|
|
710
731
|
}
|
|
732
|
+
|
|
711
733
|
_att_run_supervised() {
|
|
712
734
|
local timeout_s="${FLOW_ATTEST_TIMEOUT_S:-30}" cap="${FLOW_ATTEST_OUT_CAP:-65536}"
|
|
713
735
|
local comb="${FLOW_ATTEST_COMBINED_CAP:-98304}" grace_s="${FLOW_ATTEST_GRACE_S:-2}"
|
|
714
|
-
local outf errf start end rc=0 pid="" pgid="" i
|
|
736
|
+
local outf errf start end rc=0 pid="" pgid="" i use_setsid=0
|
|
715
737
|
_ATT_RUN_RC="spawn-error"; _ATT_RUN_CODE=127; _ATT_RUN_OUT=""; _ATT_RUN_MS=0
|
|
716
738
|
if ! _att_supervisor_capable; then _ATT_RUN_RC="spawn-error"; return 1; fi
|
|
717
739
|
case "$timeout_s" in ''|*[!0-9]*|0) timeout_s=30 ;; esac
|
|
718
740
|
case "$cap" in ''|*[!0-9]*|0) cap=65536 ;; esac
|
|
719
741
|
case "$comb" in ''|*[!0-9]*|0) comb=98304 ;; esac
|
|
720
742
|
case "$grace_s" in ''|*[!0-9]*) grace_s=2 ;; esac
|
|
743
|
+
_att_has_setsid && use_setsid=1
|
|
721
744
|
outf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-out.$$")"
|
|
722
745
|
errf="$(mktemp 2>/dev/null || echo "${TMPDIR:-/tmp}/.att-err.$$")"
|
|
723
746
|
: > "$outf"; : > "$errf"
|
|
724
747
|
start="$(_att_now_s)"
|
|
725
|
-
|
|
748
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
749
|
+
( cd "$ROOT" || exit 127; exec setsid "$@" ) >"$outf" 2>"$errf" &
|
|
750
|
+
else
|
|
751
|
+
( cd "$ROOT" || exit 127; exec "$@" ) >"$outf" 2>"$errf" &
|
|
752
|
+
fi
|
|
726
753
|
pid=$!
|
|
727
754
|
i=0
|
|
728
755
|
while [ "$i" -lt 20 ]; do
|
|
@@ -735,9 +762,15 @@ _att_run_supervised() {
|
|
|
735
762
|
[ -n "$pgid" ] || pgid="$pid"
|
|
736
763
|
( sleep "$timeout_s" 2>/dev/null
|
|
737
764
|
if kill -0 "$pid" 2>/dev/null; then
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
765
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
766
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
767
|
+
sleep "$grace_s" 2>/dev/null
|
|
768
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
769
|
+
else
|
|
770
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
771
|
+
sleep "$grace_s" 2>/dev/null
|
|
772
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
773
|
+
fi
|
|
741
774
|
echo timeout > "${outf}.to"
|
|
742
775
|
fi ) &
|
|
743
776
|
local wd=$!
|
|
@@ -746,9 +779,15 @@ _att_run_supervised() {
|
|
|
746
779
|
os="$(wc -c < "$outf" 2>/dev/null | tr -d ' ')"
|
|
747
780
|
es="$(wc -c < "$errf" 2>/dev/null | tr -d ' ')"
|
|
748
781
|
if [ "${os:-0}" -gt "$cap" ] || [ "${es:-0}" -gt "$cap" ] || [ $(( ${os:-0} + ${es:-0} )) -gt "$comb" ]; then
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
782
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
783
|
+
kill -TERM -"$pgid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
784
|
+
sleep 1
|
|
785
|
+
kill -KILL -"$pgid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
|
|
786
|
+
else
|
|
787
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
788
|
+
sleep 1
|
|
789
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
790
|
+
fi
|
|
752
791
|
echo cap > "${outf}.cap"; break
|
|
753
792
|
fi
|
|
754
793
|
sleep 0.1 2>/dev/null || sleep 1
|
|
@@ -757,7 +796,11 @@ _att_run_supervised() {
|
|
|
757
796
|
wait "$pid" 2>/dev/null; rc=$?
|
|
758
797
|
kill "$wd" 2>/dev/null; wait "$wd" 2>/dev/null
|
|
759
798
|
kill "$capw" 2>/dev/null; wait "$capw" 2>/dev/null
|
|
760
|
-
|
|
799
|
+
if [ "$use_setsid" -eq 1 ]; then
|
|
800
|
+
kill -KILL -"$pgid" 2>/dev/null || true
|
|
801
|
+
else
|
|
802
|
+
kill -KILL "$pid" 2>/dev/null || true
|
|
803
|
+
fi
|
|
761
804
|
end="$(_att_now_s)"
|
|
762
805
|
_ATT_RUN_MS=$(( (end - start) * 1000 )); [ "$_ATT_RUN_MS" -lt 0 ] && _ATT_RUN_MS=0
|
|
763
806
|
if [ -f "${outf}.cap" ]; then _ATT_RUN_RC="output-cap"; _ATT_RUN_CODE=1; rm -f "${outf}.cap"
|