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.
- package/dist/commands/init.js +73 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +3 -2
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +1 -1
- package/dist/config/model.js.map +1 -1
- package/dist/core/writer.js +3 -2
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +262 -18
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +60 -2
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/remote/githubActions.js +59 -3
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/context/conventions.md +1 -1
- package/templates/claude/context/layer.md +1 -1
- package/templates/github/workflows/harness-control.yml +21 -5
- package/templates/github/workflows/harness-resume.yml +6 -5
- package/templates/github/workflows/harness-run.yml +143 -7
- package/templates/github/workflows/harness-trigger.yml +4 -1
- package/templates/scripts/autonomous-watcher.sh +121 -21
- package/templates/scripts/lib/harness-run-lib.sh +140 -13
- package/templates/scripts/push-branch.sh +69 -11
- package/templates/scripts/remote-run.sh +1428 -266
- package/templates/state-dir/user_reviews/README.md +1 -1
|
@@ -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
|
-
* ##
|
|
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
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"githubWorkflows.js","sourceRoot":"","sources":["../../src/generators/githubWorkflows.ts"],"names":[],"mappings":"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"}
|
|
@@ -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>]
|
|
64
|
-
*
|
|
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":"AAAA
|
|
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.
|
|
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`.
|
|
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
|
|
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
|
-
#
|
|
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:
|
|
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
|
|
38
|
-
#
|
|
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
|
|
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
|
|
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
|
|
63
|
-
#
|
|
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
|
-
#
|
|
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),
|
|
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
|
|
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:
|