autonomous-sdlc-harness 0.6.1 → 0.6.3

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.
@@ -13,7 +13,7 @@
13
13
  * existed. Every name — the directory, the file names, the template directory — is imported from
14
14
  * `remote/githubActions.ts`, which owns them.
15
15
  *
16
- * ## Five non-obvious choices, and where each comes from
16
+ * ## Six non-obvious choices, and where each comes from
17
17
  *
18
18
  * 1. **No configured value is rendered into either file; only this CLI's own version is.** The job
19
19
  * reads `harness.config.json` at run time, so a workflow frozen with a configured value would go
@@ -38,7 +38,25 @@
38
38
  * 5. **The two forge workflows — the trigger and the control workflow — are gated on
39
39
  * `forgeTriggerApplies`, carry no pin and are not upgraded,** because each calls the scripts on the
40
40
  * default branch and shares their re-run contract. Both are copied verbatim like
41
- * `harness-resume.yml` (choice 3) and never carry a `forceOverride`, upgrade mode included.
41
+ * `harness-resume.yml` (choice 3), and the trigger never carries a `forceOverride`, upgrade mode
42
+ * included. Neither is ever upgraded by pin; the control workflow's one exception is the
43
+ * byte-identical repair of choice 6.
44
+ * 6. **Any `init` replaces a `harness-control.yml` byte-identical to a release's copy GitHub could
45
+ * not parse ({@link UNPARSEABLE_CONTROL_RELEASES}), after a `.bak`.** The match is an exact
46
+ * SHA-256 over the LF-normalised bytes, not a parse: the package carries no YAML parser at run
47
+ * time (`.claude/context/conventions.md` → `## The stack…`, the no-runtime-dependency rule), and an
48
+ * exact match is the only copy known to be unedited, so it is the only one replacing loses nothing
49
+ * for. A copy still carrying {@link UNPARSEABLE_CONTROL_IF_LINE} but otherwise edited is kept and
50
+ * reported as `controlRepair: { kind: 'edited' }`. It runs on every `init`, not only under
51
+ * {@link UPGRADE_WORKFLOWS_FLAG}, because the file carries no pin for that mode to key on and an
52
+ * unparseable copy disables comment control whichever command the adopter reaches for. It is a
53
+ * per-request `forceOverride: 'always'`, not a new `WritePolicy`, on choice 4's precedent: the
54
+ * engine's `.bak`-then-replace is the operation, and the re-run contract stays `create-if-absent`.
55
+ * The repair's `.bak` is deliberately not in the managed `.gitignore` block: the repair happens
56
+ * once, and its `.bak` is meant to be seen and deleted.
57
+ *
58
+ * **This module owns {@link unparseableControlRoute}.** `init`'s warning and `doctor`'s failure both
59
+ * print it, and neither re-spells it.
42
60
  *
43
61
  * **Declared mirror.** `cli/templates/github/workflows/harness-run.yml`'s `Install the pinned plugin`
44
62
  * step spells the route {@link upgradeWorkflowsCommand} produces,
@@ -52,6 +70,7 @@
52
70
  * Every file is `create-if-absent`: the adopter tunes the cron, the timeouts and the runner, and a
53
71
  * re-run keeps that edit; `--force` replaces each after a `.bak` (`core/writer.ts`'s re-run table).
54
72
  */
73
+ import { createHash } from 'node:crypto';
55
74
  import { existsSync, readFileSync } from 'node:fs';
56
75
  import { join } from 'node:path';
57
76
  import { forgeTriggerApplies, remoteExecutionApplies } from '../config/model.js';
@@ -75,6 +94,40 @@ export function upgradeWorkflowsCommand(version) {
75
94
  * space. Its citation is spelled as `harness-run.yml`'s `upgrade_route` spells it.
76
95
  */
77
96
  export const IN_FLIGHT_RUNS_NOTE = "An upgrade reaches only the runs dropped after it is pushed: each run's branch carries the workflows it was cut with, so a run already in flight finishes on the version it started with, and moving one on purpose is a separate step (docs/remote-execution.md, section 7, Upgrading).";
97
+ /**
98
+ * Each release whose shipped `harness-control.yml` GitHub could not parse, to the SHA-256 hex of
99
+ * that file's LF-normalised bytes (choice 6). The 0.6.1 digest is of git blob
100
+ * `1b4f0fc33200d876e4089ebe4013120e48a45335`, `cli/templates/github/workflows/harness-control.yml`
101
+ * from `a4ae3c8` through `31a2d55`.
102
+ */
103
+ export const UNPARSEABLE_CONTROL_RELEASES = {
104
+ '0.6.1': 'dd014dc14bf19947182f4c95fa0bf011aba04fda354d9a6fa94242e86082bfa5',
105
+ };
106
+ /** The job-level `if:` line, trimmed, that made 0.6.1's `harness-control.yml` unparseable. */
107
+ const UNPARSEABLE_CONTROL_IF_LINE = "if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@sdlc-harness') && !contains(github.event.comment.body, '<!-- sdlc-harness')) || (github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'issues' && contains(join(github.event.issue.labels.*.name, ','), 'sdlc-harness: ')) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')";
108
+ /**
109
+ * The one producer of the route from an unparseable `harness-control.yml` to the fixed one. Complete
110
+ * sentences ending in one `.`, ASCII only, no line break.
111
+ */
112
+ export function unparseableControlRoute(version) {
113
+ const pinned = pinnedCliCommand(version);
114
+ const releases = Object.keys(UNPARSEABLE_CONTROL_RELEASES).join(' or ');
115
+ return (`Run \`${pinned} init\`, which replaces a harness-control.yml that ${releases} wrote and nobody edited, keeping the previous copy as a .bak; ` +
116
+ `for an edited copy, write the control job's if: as a folded block scalar (if: >- with the expression on the next line, indented), ` +
117
+ `or run \`${pinned} init --force\`, which regenerates every generated file after a .bak; ` +
118
+ `then commit the file and push it to the repository's default branch.`);
119
+ }
120
+ /** Classify an existing control workflow's text against {@link UNPARSEABLE_CONTROL_RELEASES}. */
121
+ function classifyControl(existing) {
122
+ const normalised = existing.replace(/\r\n/g, '\n');
123
+ const digest = createHash('sha256').update(normalised, 'utf8').digest('hex');
124
+ const release = Object.keys(UNPARSEABLE_CONTROL_RELEASES).find((name) => UNPARSEABLE_CONTROL_RELEASES[name] === digest);
125
+ if (release !== undefined)
126
+ return { kind: 'replaced', release };
127
+ if (normalised.split('\n').some((line) => line.trim() === UNPARSEABLE_CONTROL_IF_LINE))
128
+ return { kind: 'edited' };
129
+ return undefined;
130
+ }
78
131
  const CRON_LINE = /^\s*- cron: /;
79
132
  /** The schedule a `- cron:` line carries, quotes stripped. */
80
133
  function cronExpression(line) {
@@ -143,6 +196,7 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
143
196
  { absolute: resumePath, repoPath: WORKFLOW_RESUME_PATH },
144
197
  ];
145
198
  const trigger = forgeTriggerApplies(config);
199
+ let controlRepair;
146
200
  if (trigger) {
147
201
  const triggerPath = join(repoRoot, ...WORKFLOW_TRIGGER_PATH.split('/'));
148
202
  plan.add({
@@ -153,11 +207,14 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
153
207
  });
154
208
  workflows.push({ absolute: triggerPath, repoPath: WORKFLOW_TRIGGER_PATH });
155
209
  const controlPath = join(repoRoot, ...WORKFLOW_CONTROL_PATH.split('/'));
210
+ if (existsSync(controlPath))
211
+ controlRepair = classifyControl(readFileSync(controlPath, 'utf8'));
156
212
  plan.add({
157
213
  path: controlPath,
158
214
  policy: 'create-if-absent',
159
215
  content: readTemplate(`${WORKFLOW_TEMPLATE_DIR}/${WORKFLOW_CONTROL_FILE}`),
160
216
  label: `workflow ${WORKFLOW_CONTROL_FILE}`,
217
+ ...(controlRepair?.kind === 'replaced' ? { forceOverride: 'always' } : {}),
161
218
  });
162
219
  workflows.push({ absolute: controlPath, repoPath: WORKFLOW_CONTROL_PATH });
163
220
  }
@@ -166,6 +223,7 @@ export function writeGithubWorkflows({ repoRoot, config, plan, upgrade = false,
166
223
  workflows,
167
224
  trigger,
168
225
  ...(upgradeResult === undefined ? {} : { upgrade: upgradeResult }),
226
+ ...(controlRepair === undefined ? {} : { controlRepair }),
169
227
  };
170
228
  }
171
229
  //# sourceMappingURL=githubWorkflows.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"githubWorkflows.js","sourceRoot":"","sources":["../../src/generators/githubWorkflows.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"githubWorkflows.js","sourceRoot":"","sources":["../../src/generators/githubWorkflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAsB,MAAM,oBAAoB,CAAC;AACrG,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAE5D,4GAA4G;AAC5G,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;AACvD,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC;AACvE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAC9B,0RAA0R,CAAC;AAE7R;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAqC;IAC5E,OAAO,EAAE,kEAAkE;CAC5E,CAAC;AAEF,8FAA8F;AAC9F,MAAM,2BAA2B,GAC/B,gnBAAgnB,CAAC;AAEnnB;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxE,OAAO,CACL,SAAS,MAAM,sDAAsD,QAAQ,iEAAiE;QAC9I,oIAAoI;QACpI,YAAY,MAAM,wEAAwE;QAC1F,sEAAsE,CACvE,CAAC;AACJ,CAAC;AAQD,iGAAiG;AACjG,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAC5D,CAAC,IAAI,EAAE,EAAE,CAAC,4BAA4B,CAAC,IAAI,CAAC,KAAK,MAAM,CACxD,CAAC;IACF,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IAChE,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,2BAA2B,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAClH,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,SAAS,GAAG,cAAc,CAAC;AA+CjC,8DAA8D;AAC9D,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAAC,QAAgB,EAAE,IAAuB;IAC1D,OAAO,QAAQ;SACZ,KAAK,CAAC,IAAI,CAAC;SACX,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,EACnC,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,OAAO,GAAG,KAAK,GACQ;IACvB,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE9F,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAE7C,IAAI,aAA0C,CAAC;IAC/C,IAAI,aAAa,GAAG,YAAY,CAAC,GAAG,qBAAqB,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACrF,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,mBAAmB,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC;QACxF,IAAI,IAAmC,CAAC;QACxC,IAAI,QAAQ,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7E,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACrB,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;gBACjC,aAAa,GAAG,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;YACD,aAAa,GAAG,QAAQ,KAAK,aAAa,CAAC;QAC7C,CAAC;QACD,aAAa,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,WAAW,GAAG,GAAG,qBAAqB,IAAI,iBAAiB,EAAE,CAAC;IACpE,IAAI,CAAC,GAAG,CAAC;QACP,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,cAAc,CACrB,YAAY,CAAC,WAAW,CAAC,EACzB,EAAE,UAAU,EAAE,OAAO,EAAE,EACvB,EAAE,QAAQ,EAAE,yBAAyB,WAAW,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAC9E;QACD,KAAK,EAAE,YAAY,iBAAiB,EAAE;QACtC,GAAG,CAAC,aAAa,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClF,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC;QACP,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,kBAAkB;QAC1B,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,YAAY,oBAAoB,EAAE;QACzC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG;QAChB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE;QAClD,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,oBAAoB,EAAE;KACzD,CAAC;IAEF,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,aAA4B,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC,GAAG,CAAC;YACP,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,YAAY,CAAC,GAAG,qBAAqB,IAAI,qBAAqB,EAAE,CAAC;YAC1E,KAAK,EAAE,YAAY,qBAAqB,EAAE;SAC3C,CAAC,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC,CAAC;QAE3E,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACxE,IAAI,UAAU,CAAC,WAAW,CAAC;YAAE,aAAa,GAAG,eAAe,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QAChG,IAAI,CAAC,GAAG,CAAC;YACP,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,kBAAkB;YAC1B,OAAO,EAAE,YAAY,CAAC,GAAG,qBAAqB,IAAI,qBAAqB,EAAE,CAAC;YAC1E,KAAK,EAAE,YAAY,qBAAqB,EAAE;YAC1C,GAAG,CAAC,aAAa,EAAE,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpF,CAAC,CAAC;QACH,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,SAAS;QACT,OAAO;QACP,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;QAClE,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC;KAC1D,CAAC;AACJ,CAAC"}
@@ -6,7 +6,10 @@
6
6
  * the repository secret and variable names, the two artifact names and the `gh` test-seam variable
7
7
  * and the rendered CLI-version pin are declared here once; the generator that writes the workflows and the `doctor` checks that grade
8
8
  * them read these constants rather than retyping a literal, and so does `generators/repoRoot.ts`,
9
- * which spells the two workflow `.bak` ignore rules whatever `execution.target` says.
9
+ * which spells the two workflow `.bak` ignore rules whatever `execution.target` says. It owns the
10
+ * {@link RUN_ACTORS_VARIABLE} allow-list's semantics as well as its name — {@link effectiveRunActors}
11
+ * and {@link runActorAdmitted} — and `remote-run.sh` and `harness-run.yml`'s gate step each restate
12
+ * that rule in shell.
10
13
  *
11
14
  * Except in `generators/repoRoot.ts`, nothing here is consulted unless `config/model.ts` →
12
15
  * `remoteExecutionApplies(config)` is true: every other consumer tests that switch first. That one
@@ -60,8 +63,9 @@ export const COMMAND_HANDLE = '@sdlc-harness';
60
63
  export const COMMAND_VERBS = ['answer', 'pause', 'resume', 'stop', 'clear', 'status'];
61
64
  /**
62
65
  * The prefix of the hidden line every harness comment carries; the whole line is
63
- * `<!-- sdlc-harness event=<event> branch=<branch>[ question=<n>] -->`. A comment containing the
64
- * prefix anywhere is never a command.
66
+ * `<!-- sdlc-harness event=<event> branch=<branch>[ question=<n>][ engine=<engine>] -->`, where
67
+ * `engine=` is carried only by a `reply` posted after a successful dispatch. A comment containing
68
+ * the prefix anywhere is never a command.
65
69
  */
66
70
  export const COMMENT_MARKER = '<!-- sdlc-harness';
67
71
  /** The `review.state` that starts a review round, lowercase as the webhook payload carries it. */
@@ -117,6 +121,58 @@ export function triggerFallbackLabel(text) {
117
121
  }
118
122
  /** Repository variable: comma-separated bot logins that may start a run; unset or empty admits none. */
119
123
  export const TRIGGER_ALLOWED_BOTS_VARIABLE = 'HARNESS_TRIGGER_ALLOWED_BOTS';
124
+ /** Repository variable: comma-separated GitHub logins allowed to start, command, answer and review a run; `*` admits every writer; unset admits the owner of a user-owned repository alone, and nobody in an organisation-owned one. */
125
+ export const RUN_ACTORS_VARIABLE = 'HARNESS_RUN_ACTORS';
126
+ /** The {@link RUN_ACTORS_VARIABLE} entry that admits every collaborator with write access. */
127
+ export const RUN_ACTORS_EVERY_WRITER = '*';
128
+ /**
129
+ * Who a {@link RUN_ACTORS_VARIABLE} value admits. The value is split on `,`, each entry trimmed and
130
+ * empty entries dropped. An entry {@link RUN_ACTORS_EVERY_WRITER} anywhere gives `every-writer`;
131
+ * otherwise any entries give `listed`, as written and in order. No entries — `undefined`, `''`, only
132
+ * whitespace or only commas — counts as unset: an owner whose `type` is exactly `User` with a
133
+ * non-empty login gives `owner`, and anything else (an `Organization`, an unknown type, no owner)
134
+ * gives `nobody`. Pure — the caller reads the variable and the owner.
135
+ */
136
+ export function effectiveRunActors(value, owner) {
137
+ const entries = (value ?? '')
138
+ .split(',')
139
+ .map((entry) => entry.trim())
140
+ .filter((entry) => entry !== '');
141
+ if (entries.includes(RUN_ACTORS_EVERY_WRITER))
142
+ return { kind: 'every-writer' };
143
+ if (entries.length > 0)
144
+ return { kind: 'listed', logins: entries };
145
+ if (owner !== undefined && owner.type === 'User' && owner.login !== '')
146
+ return { kind: 'owner', login: owner.login };
147
+ return { kind: 'nobody', owner };
148
+ }
149
+ /**
150
+ * Whether `login` is admitted by `actors`, compared case-insensitively. An empty login is never
151
+ * admitted, `nobody` admits no one, and `every-writer` admits any non-empty login — whether that login
152
+ * has write access is GitHub's to decide, not this function's.
153
+ */
154
+ export function runActorAdmitted(actors, login) {
155
+ if (login === '')
156
+ return false;
157
+ const wanted = login.toLowerCase();
158
+ switch (actors.kind) {
159
+ case 'every-writer':
160
+ return true;
161
+ case 'listed':
162
+ return actors.logins.some((entry) => entry.toLowerCase() === wanted);
163
+ case 'owner':
164
+ return actors.login.toLowerCase() === wanted;
165
+ case 'nobody':
166
+ return false;
167
+ }
168
+ }
169
+ /**
170
+ * Whether a workflow's text reads `vars.HARNESS_RUN_ACTORS`; `false` marks a copy written before the
171
+ * allow-list existed. Pure — the caller reads the file.
172
+ */
173
+ export function carriesRunActors(workflowText) {
174
+ return workflowText.includes(`vars.${RUN_ACTORS_VARIABLE}`);
175
+ }
120
176
  /** The `repository_dispatch` `event_type` the trigger workflow listens to. */
121
177
  export const TRIGGER_DISPATCH_EVENT_TYPE = 'harness-task';
122
178
  /** Repository secret for subscription billing. */
@@ -1 +1 @@
1
- {"version":3,"file":"githubActions.js","sourceRoot":"","sources":["../../src/remote/githubActions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"githubActions.js","sourceRoot":"","sources":["../../src/remote/githubActions.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autonomous-sdlc-harness",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "The outer loop of the autonomous SDLC harness: init, doctor, config and daemon management for the harness Claude Code plugin, plus opt-in local docs retrieval.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "firu-daniel",
@@ -10,7 +10,7 @@
10
10
  - How a change flows through those layers, and which direction a dependency is allowed to point.
11
11
  - The order files are created in when a feature is added, so a half-built feature is still a coherent tree.
12
12
  - Where a new responsibility goes when it fits no file that already exists.
13
- - Logging, error handling, and the testing bar a change clears before it counts as done — including **the one command that runs a single test file on its own**, or the statement that this project has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed.
13
+ - Logging, error handling, and the testing bar a change clears before it counts as done — including **the one command that runs a single test file on its own**, or the statement that this project has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed. Name it as one command run from the repository root — never `cd <dir> && …`, never piped, and never with its output redirected to a file — because a unit runs it unattended, where any of those shapes can be refused and nobody can approve it. Do not quote a terminal run's output as what a run prints: a runner may print a different format when its output is captured rather than shown on a terminal.
14
14
  - **The commit-message policy**, which is the whole of what the committer agent reads from this document: the subject-prefix vocabulary a caller draws its prefix from, whether the first word after the prefix is capitalized, the subject-length cap, the fixed-form subjects the flow emits, and the attribution-trailer policy: whether a trailer naming the agent as an author is required, forbidden or optional. The harness takes no position on that trailer, because the rule is actively reversed between projects: some forbid one outright, others require it on every agent-assisted commit, and unanswered here it has no default to fall back on. **The fixed-form subjects are derived here, never copied from a list** — run ``grep -rnE '(chore|feat|fix|refactor|docs|test): [A-Za-z][^"'"'"'`]*(<branch>|\$branch|\$\{branch\}|<entry-id>|K passing)' "${CLAUDE_PLUGIN_ROOT}" <scripts_dir>``, substituting `<scripts_dir>` with this repository's `scriptsDir` value from `harness.config.json` before running it, and write the policy so that **every** subject that command emits is exempt from the capitalisation rule and from the subject-length cap — **minus two classes the probe reaches but no commit point owns**: a subject the surrounding text tells the flow **not** to emit (its own line says it is forbidden), and a subject quoted as a worked example by a guard, a verification document or a prose restatement of another home's string. A subject with no emitting home is not a fixed form; check the line the probe emitted before exempting it. Each exempted subject is passed byte-for-byte by the commit point that owns it, so a policy that re-cases or truncates one breaks that commit point. `review_plan_file` and `ui_test_pass` are the committer's own commit modes and the dispatching flow files quote both, so changing either one is a two-file edit; the flow's other commit points own theirs the same way. The probe reaches **branch-scoped** subjects only — its alternation requires a `<branch>` / `$branch` / `${branch}` / `<entry-id>` / `K passing` token — so a fixed subject carrying none of them falls outside it, and widening the probe is part of the cost of adding one. **Per commit class, which prefix a commit of that class takes** — the vocabulary above names the set, this names the selection, and a policy giving only the set leaves a caller nothing to read: answer it for every class the flows commit in, at minimum a commit **fixing existing work**, the class the review-fix loops commit in, and a commit **adding new work**, the class a story task's commit falls in; and answer as well whether **any class carries no prefix at all, and which class that is**, which is what the committer's `commit_prefix` argument spells as the literal `none`. Designate by class and never by file kind — the classes are the flows', the tokens are yours.
15
15
  - **How this project stays in step with its reference implementation, if it has one** — which behaviour must match it exactly, which parts may deviate and why, and how a review finding cites the source it compared against. A project with a single implementation says so here and leaves `phases.parity` off; the section is optional, not missing.
16
16
 
@@ -9,7 +9,7 @@
9
9
  - What this layer is responsible for and — just as usefully — what it is not, so a task that has drifted into it is recognisable.
10
10
  - What it may depend on, and which direction a dependency between layers is allowed to point.
11
11
  - Naming and file-layout rules that apply inside it.
12
- - What "done" means here: the tests to write, the checks to run, the bar a review holds the change to — including **the one command that runs a single test file of this layer on its own**, or the statement that this layer has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed.
12
+ - What "done" means here: the tests to write, the checks to run, the bar a review holds the change to — including **the one command that runs a single test file of this layer on its own**, or the statement that this layer has none. The harness runs the full test command once per Run gates phase, never per change, so a unit that wrote a test runs only that file, through this command. Where the test wrapper's line accepts a path, `bash <scriptsDir>/test.sh <file>` is the command to name, because it is already allow-listed. Name it as one command run from the repository root — never `cd <dir> && …`, never piped, and never with its output redirected to a file — because a unit runs it unattended, where any of those shapes can be refused and nobody can approve it. Do not quote a terminal run's output as what a run prints: a runner may print a different format when its output is captured rather than shown on a terminal.
13
13
 
14
14
  **One generic example — the shape a rule takes here**
15
15
 
@@ -9,12 +9,16 @@
9
9
  # create-if-absent: from then on it is yours to tune, and a re-run of `init`
10
10
  # keeps your copy. `init --upgrade-workflows` does NOT re-render it: it carries
11
11
  # no version pin and calls the scripts on the default branch, so it shares
12
- # their re-run contract — `init --force` replaces it after a `.bak`. It is a
12
+ # their re-run contract — `init --force` replaces it after a `.bak`. The one
13
+ # exception: any `init` replaces, after a `.bak`, a copy that is byte for byte
14
+ # the one 0.6.1 wrote, which GitHub could not parse; an edited copy of that one
15
+ # is kept and named in `init`'s output. It is a
13
16
  # file of its own, apart from harness-trigger.yml, so you can disable comment
14
17
  # control alone: disabling a workflow disables every event in it.
15
18
  #
16
19
  # WHAT IT DOES. One job runs `remote-run.sh control`, which does everything
17
- # else: it authorises the actor, finds the run's branch, carries out the
20
+ # else: it authorises the actor — their write access, then the allow-list
21
+ # HARNESS_RUN_ACTORS — finds the run's branch, carries out the
18
22
  # command or places the review round, and replies. Every decision lives in that
19
23
  # script; this file carries only the events, the permissions, a prefilter and
20
24
  # the concurrency group.
@@ -54,6 +58,11 @@
54
58
  # edited copy of this filter cannot bypass one. That `contains()` compares
55
59
  # case-insensitively is GitHub's documented behaviour, not verified here; a
56
60
  # mixed-case handle the filter missed would only go unanswered, never obeyed.
61
+ # The `if:` is a folded block scalar (`>-`) because its expression carries
62
+ # `: ` (the state-label prefix `'sdlc-harness: '`), which a plain scalar reads
63
+ # as a mapping indicator. 0.6.1 shipped it plain, so GitHub could not parse
64
+ # the file and ran it for no event (docs/development.md, Gate 12, Round 7,
65
+ # finding 1).
57
66
  #
58
67
  # THE PERMISSIONS, declared because the default token may be read-only
59
68
  # (docs/github-integration-research.md, T2):
@@ -112,7 +121,7 @@
112
121
  # of `||` does not exit the shell, and `$?` is the script's status.
113
122
  #
114
123
 
115
- # TWO RULES EVERY EDIT KEEPS.
124
+ # THREE RULES EVERY EDIT KEEPS.
116
125
  # * Event text and variables reach a shell line only through `env:` and the
117
126
  # event file, never through a GitHub expression inside `run:` — a comment
118
127
  # body is written by whoever posted it (script injection).
@@ -120,6 +129,10 @@
120
129
  # renders its workflows with the CLI's double-brace token renderer, which
121
130
  # reads a letter straight after the braces as a token; this file carries
122
131
  # no token.
132
+ # * A value carrying `: ` or ` #` is never a plain scalar: write it as a
133
+ # block scalar (`>-`) or quote it. A plain scalar reads `: ` as a mapping
134
+ # indicator, which leaves the file unparseable, and ` #` as a comment,
135
+ # which silently truncates the value (THE PREFILTER above).
123
136
  #
124
137
  # DECLARED MIRRORS — a rename on either side is an edit to both:
125
138
  # cli/src/remote/githubActions.ts WORKFLOW_CONTROL_FILE, COMMAND_HANDLE
@@ -127,7 +140,8 @@
127
140
  # (`<!-- sdlc-harness`), REVIEW_ROUND_STATE
128
141
  # (`changes_requested`), HARNESS_RUNNER,
129
142
  # HARNESS_REMOTE_STOP,
130
- # HARNESS_TRIGGER_ALLOWED_BOTS
143
+ # HARNESS_TRIGGER_ALLOWED_BOTS,
144
+ # HARNESS_RUN_ACTORS
131
145
  # cli/src/config/model.ts DEFAULTS.scriptsDir
132
146
  # harness-run.yml the concurrency group
133
147
  # `harness-review-<branch>`, held by its
@@ -171,7 +185,8 @@ defaults:
171
185
 
172
186
  jobs:
173
187
  control:
174
- if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@sdlc-harness') && !contains(github.event.comment.body, '<!-- sdlc-harness')) || (github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'issues' && contains(join(github.event.issue.labels.*.name, ','), 'sdlc-harness: ')) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')
188
+ if: >-
189
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@sdlc-harness') && !contains(github.event.comment.body, '<!-- sdlc-harness')) || (github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'issues' && contains(join(github.event.issue.labels.*.name, ','), 'sdlc-harness: ')) || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || (github.event_name == 'delete' && github.event.ref_type == 'branch')
175
190
  runs-on: ${{ vars.HARNESS_RUNNER || 'ubuntu-latest' }}
176
191
  concurrency:
177
192
  group: ${{ github.event_name == 'pull_request_review' && format('harness-review-{0}', github.event.pull_request.head.ref) || format('harness-control-{0}', github.run_id) }}
@@ -181,6 +196,7 @@ jobs:
181
196
  HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
182
197
  HARNESS_REMOTE_SLUG: ${{ github.repository }}
183
198
  HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
199
+ HARNESS_RUN_ACTORS: ${{ vars.HARNESS_RUN_ACTORS }}
184
200
  steps:
185
201
  - name: Check out the default branch
186
202
  uses: actions/checkout@v5
@@ -34,12 +34,13 @@
34
34
  # cost nothing.
35
35
  #
36
36
  # WHAT IT READS.
37
- # Secrets: HARNESS_PUSH_URL (optional notifications: the poller's own failed
38
- # notice when it cannot resume a run).
37
+ # Secrets: HARNESS_PUSH_URL (optional notifications: the poller's own notice
38
+ # when it cannot resume a run or cannot download its bundle).
39
39
  # Variables: HARNESS_RUNNER, HARNESS_REMOTE_STOP (any value: the tick
40
40
  # dispatches nothing), HARNESS_MAX_CHAIN (the automatic-dispatch limit),
41
41
  # HARNESS_POLL_MAX_DISPATCH_FAILURES (failed re-dispatches of one paused
42
- # run before the poller gives up on it; 3 when unset) and
42
+ # run, or consecutive failed downloads of one run's listed bundle, before
43
+ # the poller gives up on it; 3 when unset) and
43
44
  # HARNESS_POLL_GIVE_UP_AFTER_MINUTES (minutes after the usage reset past
44
45
  # which it gives up; 360 when unset).
45
46
  # harness.config.json, at run time: `scriptsDir` and `stateDir`. No
@@ -53,8 +54,8 @@
53
54
  # workflow-level `permissions:` block sets every permission it does not list to
54
55
  # `none`.
55
56
  #
56
- # WHAT IT WRITES. The `harness-poll-state` artifact: the failed-dispatch count
57
- # `poll` carries to the next tick, kept 7 days.
57
+ # WHAT IT WRITES. The `harness-poll-state` artifact: the failed-dispatch and
58
+ # failed-download counts `poll` carries to the next tick, kept 7 days.
58
59
  #
59
60
  # THE RULE EVERY EDIT KEEPS. Every GitHub expression has a space after its
60
61
  # opening braces, and every value reaches a shell line through `env:`. `init`
@@ -34,7 +34,8 @@
34
34
  # HARNESS_REMOTE_STOP, CLAUDE_CODE_OAUTH_TOKEN,
35
35
  # ANTHROPIC_API_KEY, HARNESS_PUSH_URL,
36
36
  # HARNESS_GIT_TOKEN, the artifact harness-state,
37
- # HARNESS_CLI_VERSION
37
+ # HARNESS_CLI_VERSION, HARNESS_RUN_ACTORS,
38
+ # HARNESS_TRIGGER_ALLOWED_BOTS
38
39
  # cli/src/generators/githubWorkflows.ts --upgrade-workflows
39
40
  # cli/src/generators/projectSettings.ts the marketplace and plugin key
40
41
  # autonomous-sdlc-harness@autonomous-sdlc-harness
@@ -59,8 +60,11 @@
59
60
  # the two never place a round at once; and the settledness test reads "the
60
61
  # `run` job is completed", which no step inside that job could see. Under
61
62
  # `!cancelled()` a failed `run` job still reaches it, and the run's state bundle
62
- # decides whether the run ended or only paused or parked; a cancelled or
63
- # stopped run skips it. The group name mirrors harness-control.yml's, and
63
+ # decides whether the run ended or only paused or parked. A `run` job GitHub
64
+ # cancelled before any step ran, for example one no runner acquired, reaches it
65
+ # too, and `collect` reports that run once (remote-run.sh's header, its
66
+ # `collect` paragraph), because the job itself could report nothing; a workflow
67
+ # run cancelled or stopped as a whole skips it. The group name mirrors harness-control.yml's, and
64
68
  # RUN_JOB_NAME in remote-run.sh mirrors the `run` job's key (DECLARED MIRRORS
65
69
  # above). It reads no secret: its own token pushes the round, dispatches it and
66
70
  # comments. It checks out the default branch, as harness-control.yml's review
@@ -82,6 +86,26 @@
82
86
  # default branch, its own ref being gone, and the stop check matches the
83
87
  # `harness stop <branch>` title across every branch's runs, under any ref.
84
88
  #
89
+ # THE RUN-ACTOR GATE. The first step of `run` and of `collect`, byte-identical
90
+ # in both, refuses a `github.triggering_actor` the repository variable
91
+ # HARNESS_RUN_ACTORS does not admit, so the Run workflow form, `gh workflow run`
92
+ # and a re-run by someone not on the list launch nothing; a refused `run` sets
93
+ # no SCRIPTS_DIR, so its `always()` and `!cancelled()` steps skip too. The list:
94
+ # split on `,`, each entry trimmed, empty entries dropped, matched
95
+ # case-insensitively, `*` admitting every writer; an empty list admits the owner
96
+ # of a user-owned repository alone, and nobody otherwise.
97
+ # * A step, not a job: Re-run failed jobs and Re-run job do not re-run an
98
+ # upstream job that succeeded, so a gate job would never see the re-runner's
99
+ # triggering_actor (docs/team-accounts-research.md, G2).
100
+ # * `github-actions[bot]` passes: every harness dispatch is made with
101
+ # GITHUB_TOKEN and names it. Measured 2026-10-05 for the trigger, the comment
102
+ # commands and `collect`; inferred for the `remote-run.sh continue` chain and
103
+ # the harness-resume.yml poller, which Gate 12 observation (xv) confirms.
104
+ # * Not closed: a writer who edits this file on a branch can remove the gate
105
+ # or dispatch as the bot. That writer can already read the secret (G7).
106
+ # * Resolved before it: the job-level HARNESS_PUSH_URL secret, at job start.
107
+ # The credential secrets and HARNESS_GIT_TOKEN are read only by later steps.
108
+ #
85
109
  # ACTION PINS.
86
110
  # actions/checkout@v5
87
111
  # actions/setup-node@v5
@@ -102,11 +126,13 @@
102
126
  # Secrets: CLAUDE_CODE_OAUTH_TOKEN and/or ANTHROPIC_API_KEY (one is required;
103
127
  # billing follows the API key when both are set), HARNESS_GIT_TOKEN (optional:
104
128
  # a personal or App token, so the job's pushes trigger your own CI, which
105
- # GITHUB_TOKEN pushes never do; it also opens the run's draft pull request,
106
- # so your CI runs on it without an approval click), HARNESS_PUSH_URL
129
+ # GITHUB_TOKEN pushes never do; it also opens the run's draft pull request
130
+ # when the run starts, and `deliver` opens one only when none is open at
131
+ # completion, so your CI runs on it without an approval click), HARNESS_PUSH_URL
107
132
  # (optional notifications).
108
133
  # Variables: HARNESS_RUNNER, HARNESS_REMOTE_STOP (any value stops every job
109
- # before it launches anything), HARNESS_STEP_TIMEOUT_MINUTES,
134
+ # before it launches anything), HARNESS_RUN_ACTORS (THE RUN-ACTOR GATE),
135
+ # HARNESS_STEP_TIMEOUT_MINUTES,
110
136
  # HARNESS_SELF_PAUSE_AFTER_MINUTES, HARNESS_MAX_CHAIN, and the watcher
111
137
  # tunables listed in the `run` job's `env:` under their own names. An unset
112
138
  # variable arrives empty and the script reading it applies its own default.
@@ -119,7 +145,7 @@
119
145
  # actions to dispatch, poll and enable the poller
120
146
  # issues to comment and set labels on the issue and the pull request,
121
147
  # which both use the issues API
122
- # pull-requests to open the draft pull request and label it
148
+ # pull-requests to open the run's pull request, update it and label it
123
149
  #
124
150
  # WHY THE REPORT STEP MAY FAIL. `Open the pull request and report` runs
125
151
  # `remote-run.sh deliver` after the push, so the pull request's head carries the
@@ -127,6 +153,17 @@
127
153
  # this file without `init --force` still runs scripts with no `deliver` verb,
128
154
  # and a finished run must not be marked failed for its report.
129
155
  #
156
+ # WHY THE OPEN STEP RUNS ONLY WHEN chain IS 0. `Open the draft pull request`
157
+ # runs `remote-run.sh open` before the harness, so the run's draft pull request
158
+ # exists from its first job. `chain` counts automatic dispatches since the last
159
+ # user action, so 0 is a job a person's action started: the first job, a
160
+ # resume, an answer, a round. An automatic continuation therefore never retries
161
+ # a create that failed, and the attempts per run stay bounded by people's
162
+ # actions; `deliver`'s fallback at completion is the last attempt, and its
163
+ # `completed` comment names the error. It is `continue-on-error` for the report
164
+ # step's reason above: scripts older than this file carry no `open` verb, and a
165
+ # run must not fail for its pull request.
166
+ #
130
167
  # THE SELF-PAUSE POINT, MEASURED. On a hosted runner the job drops its own PAUSE
131
168
  # REMOTE_SELF_PAUSE_AFTER_SECS after it started, the run yields at its next
132
169
  # dispatch boundary, and `remote-run.sh continue` chains a new job.
@@ -278,6 +315,7 @@ jobs:
278
315
  HARNESS_INPUT_PARK_LOOP_CLEAR: ${{ inputs.park_loop_clear }}
279
316
  HARNESS_INPUT_CHAIN: ${{ inputs.chain }}
280
317
  HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
318
+ HARNESS_RUN_ACTORS: ${{ vars.HARNESS_RUN_ACTORS }}
281
319
  HARNESS_MAX_CHAIN: ${{ vars.HARNESS_MAX_CHAIN }}
282
320
  STALL_WARN_SECS: ${{ vars.STALL_WARN_SECS }}
283
321
  STALL_KILL_SECS: ${{ vars.STALL_KILL_SECS }}
@@ -299,6 +337,51 @@ jobs:
299
337
  HARNESS_STEP_ALLOWANCE_MINUTES: '30'
300
338
  HARNESS_SELF_PAUSE_MARGIN_MINUTES: '90'
301
339
  steps:
340
+ - name: Refuse an actor not on HARNESS_RUN_ACTORS
341
+ env:
342
+ IN_TRIGGERING_ACTOR: ${{ github.triggering_actor }}
343
+ IN_OWNER: ${{ github.repository_owner }}
344
+ IN_OWNER_TYPE: ${{ github.event.repository.owner.type }}
345
+ run: |
346
+ actor="$IN_TRIGGERING_ACTOR"
347
+ if [ "$actor" = 'github-actions[bot]' ]; then
348
+ echo "@$actor passes: every harness dispatch is made with GITHUB_TOKEN and names it."
349
+ exit 0
350
+ fi
351
+ lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
352
+ want=$(lower "$actor")
353
+ rest="${HARNESS_RUN_ACTORS-},"
354
+ listed=""
355
+ while [ -n "$rest" ]; do
356
+ entry="${rest%%,*}"
357
+ rest="${rest#*,}"
358
+ entry="${entry#"${entry%%[![:space:]]*}"}"
359
+ entry="${entry%"${entry##*[![:space:]]}"}"
360
+ [ -n "$entry" ] || continue
361
+ listed=1
362
+ if [ "$entry" = '*' ]; then
363
+ echo "@$actor passes: HARNESS_RUN_ACTORS carries *, which admits every writer."
364
+ exit 0
365
+ fi
366
+ if [ -n "$want" ] && [ "$(lower "$entry")" = "$want" ]; then
367
+ echo "@$actor passes: listed in HARNESS_RUN_ACTORS."
368
+ exit 0
369
+ fi
370
+ done
371
+ if [ -n "$listed" ]; then
372
+ why="@${actor:-(no actor)} is not listed in it."
373
+ elif [ "$IN_OWNER_TYPE" = User ] && [ -n "$IN_OWNER" ]; then
374
+ if [ -n "$want" ] && [ "$(lower "$IN_OWNER")" = "$want" ]; then
375
+ echo "@$actor passes: HARNESS_RUN_ACTORS is unset, which admits the owner of this user-owned repository alone."
376
+ exit 0
377
+ fi
378
+ why="it is unset, which admits the owner of this user-owned repository, @$IN_OWNER, alone, and @${actor:-(no actor)} is not that owner."
379
+ else
380
+ why="it is unset, and this repository has no single owner to admit (its owner type is ${IN_OWNER_TYPE:-unreadable}), so it admits nobody."
381
+ fi
382
+ echo "::error::@${actor:-(no actor)} dispatched or re-ran this run, and the repository variable HARNESS_RUN_ACTORS does not admit them: $why Nothing was launched and no credential was read. Add the login to the comma-separated repository variable HARNESS_RUN_ACTORS, or set it to * for every writer (docs/remote-execution.md, section 7 step 4, and section 11)."
383
+ exit 1
384
+
302
385
  - name: Compute the time budget
303
386
  env:
304
387
  IN_RUNNER_ENVIRONMENT: ${{ runner.environment }}
@@ -461,6 +544,13 @@ jobs:
461
544
  if: env.HARNESS_STOPPED != '1'
462
545
  run: bash "$SCRIPTS_DIR/remote-run.sh" restore "$HARNESS_INPUT_BRANCH" --resume "$HARNESS_INPUT_RESUME"
463
546
 
547
+ - name: Open the draft pull request
548
+ if: env.HARNESS_STOPPED != '1' && inputs.chain == 0
549
+ continue-on-error: true
550
+ env:
551
+ HARNESS_PR_TOKEN: ${{ secrets.HARNESS_GIT_TOKEN }}
552
+ run: bash "$SCRIPTS_DIR/remote-run.sh" open "$HARNESS_INPUT_BRANCH"
553
+
464
554
  - name: Run the harness
465
555
  if: env.HARNESS_STOPPED != '1'
466
556
  timeout-minutes: ${{ fromJSON(env.HARNESS_STEP_TIMEOUT_MINUTES || env.HARNESS_HOSTED_JOB_LIMIT_MINUTES) }}
@@ -522,7 +612,53 @@ jobs:
522
612
  HARNESS_INPUT_BRANCH: ${{ inputs.branch }}
523
613
  HARNESS_REMOTE_STOP: ${{ vars.HARNESS_REMOTE_STOP }}
524
614
  HARNESS_TRIGGER_ALLOWED_BOTS: ${{ vars.HARNESS_TRIGGER_ALLOWED_BOTS }}
615
+ HARNESS_RUN_ACTORS: ${{ vars.HARNESS_RUN_ACTORS }}
525
616
  steps:
617
+ - name: Refuse an actor not on HARNESS_RUN_ACTORS before collecting
618
+ env:
619
+ IN_TRIGGERING_ACTOR: ${{ github.triggering_actor }}
620
+ IN_OWNER: ${{ github.repository_owner }}
621
+ IN_OWNER_TYPE: ${{ github.event.repository.owner.type }}
622
+ run: |
623
+ actor="$IN_TRIGGERING_ACTOR"
624
+ if [ "$actor" = 'github-actions[bot]' ]; then
625
+ echo "@$actor passes: every harness dispatch is made with GITHUB_TOKEN and names it."
626
+ exit 0
627
+ fi
628
+ lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; }
629
+ want=$(lower "$actor")
630
+ rest="${HARNESS_RUN_ACTORS-},"
631
+ listed=""
632
+ while [ -n "$rest" ]; do
633
+ entry="${rest%%,*}"
634
+ rest="${rest#*,}"
635
+ entry="${entry#"${entry%%[![:space:]]*}"}"
636
+ entry="${entry%"${entry##*[![:space:]]}"}"
637
+ [ -n "$entry" ] || continue
638
+ listed=1
639
+ if [ "$entry" = '*' ]; then
640
+ echo "@$actor passes: HARNESS_RUN_ACTORS carries *, which admits every writer."
641
+ exit 0
642
+ fi
643
+ if [ -n "$want" ] && [ "$(lower "$entry")" = "$want" ]; then
644
+ echo "@$actor passes: listed in HARNESS_RUN_ACTORS."
645
+ exit 0
646
+ fi
647
+ done
648
+ if [ -n "$listed" ]; then
649
+ why="@${actor:-(no actor)} is not listed in it."
650
+ elif [ "$IN_OWNER_TYPE" = User ] && [ -n "$IN_OWNER" ]; then
651
+ if [ -n "$want" ] && [ "$(lower "$IN_OWNER")" = "$want" ]; then
652
+ echo "@$actor passes: HARNESS_RUN_ACTORS is unset, which admits the owner of this user-owned repository alone."
653
+ exit 0
654
+ fi
655
+ why="it is unset, which admits the owner of this user-owned repository, @$IN_OWNER, alone, and @${actor:-(no actor)} is not that owner."
656
+ else
657
+ why="it is unset, and this repository has no single owner to admit (its owner type is ${IN_OWNER_TYPE:-unreadable}), so it admits nobody."
658
+ fi
659
+ echo "::error::@${actor:-(no actor)} dispatched or re-ran this run, and the repository variable HARNESS_RUN_ACTORS does not admit them: $why Nothing was launched and no credential was read. Add the login to the comma-separated repository variable HARNESS_RUN_ACTORS, or set it to * for every writer (docs/remote-execution.md, section 7 step 4, and section 11)."
660
+ exit 1
661
+
526
662
  - name: Check out the default branch for collecting
527
663
  uses: actions/checkout@v5
528
664
  with: