This skill teaches you to declare task completion gates in the target repository's harness.yaml : attach local tests, set manual sign-off checkpoints, configure dual control and special approval, and exempt built-in gates. All fields and behaviors are governed by the current source code—the contract is in packages/kernel/src/domain/completion-contract.ts, YAML reading/writing and validation are in packages/kernel/src/domain/settings.ts, per-gate determination is in packages/kernel/src/domain/closeout-readiness.ts(judgeGateWitnesses), adapter execution and manual witness admission are in packages/daemon/src/repo-cell-witness-adapters.ts. Before changing configuration, read the parts of these files you need; do not write from memory.
Model: Declaration × Mapping × Freezing
A completion gate is assembled from three layers; if any layer is missing, it does not work:
| Layer | Who declares | Where |
|---|---|---|
| Task declares gate id | The profile of the preset used by the task completionGates |
Preset package (built-in see packages/preset/assets/software-coding/presets/*/preset.json) |
| Repository maps witness | settings.gates.<gateId> → adapter |
This repository harness.yaml |
| Freeze at submission | submit freezes the resolution result together with adapterOptions into completionContract |
travels with the submitted cut; changing YAML does not affect a cut under review |
Decision rules (fail-closed):
- The task declares a gate but
settings.gateshas no mapping for it → submit reportsgate_mapping_invalid, not a silent skip. - Mapped to
none→ the requirement is removed; no false pass is minted. - The repository maps a gate id that no task declares → legal but inert; it produces no requirements.
- Unknown adapter, unknown field, imprecise field set → error upon reading settings.
The built-in preset declares only two gate ids:ci and code-doc-reconciliation. To use a custom id (such as lint、signoff、merged-to.main), the task side must have a preset that declares it: package it according to preset-creator (write the custom id into the profile's completionGates ), install it withha preset install --source <包目录> , and use it withha task create --preset <id> . Changing only settings.gates without changing the preset means the custom gate will not be required by any task.
Two compile-time elimination rules (effective at task creation, before any mapping):
settings.ci.workflowsis an empty array (or the entireci:section is missing) → preset resolution removescifrom the task's completionGateIds (atpackages/preset/src/preset-runtime.tsprofile.completionGateIds). If a repository without GitHub CI wants tests witnessed locally, use a custom gate id with a preset; do not rely on acimapping.workKind: docstask → bothciandcode-doc-reconciliationare eliminated (packages/preset/src/preset-bootstrap.tswithoutCodeDeliveryGates)。
settings.gates syntax
Write under harness.yaml of settings: . Indentation-sensitive:gates: two spaces, gate id four spaces, fields six spaces; field line order is arbitrary; values may be followed by comments.
settings:
ci:
workflows: [rewrite-ci] # github-actions 适配器共用的 workflow 注册表
gates:
ci: # 块形式:下面六空格字段
adapter: github-actions
appliesTo: code
branch: main
event: push
coverage: descendant
selection: newest
lint: # 自定义 id(需 preset 声明)
appliesTo: code
adapter: local-command
command: npm ci && npm run lint
signoff:
appliesTo: submission
adapter: manual-attest
code-doc-reconciliation: none # none 只能内联,后面不许再跟字段
Parsing rules (settings.ts readGateSettings/gateSettingsSchema/gateWitnessMappingIssues):
- gate id pattern
^[A-Za-z0-9][A-Za-z0-9/_.@-]*$: no spaces or colons. Write a merged gate asmerged-to.main, notmerged-to:main。 - Inline values may only be
none;ci: github-actions; this kind of inline form directly reportssettings.gates cannot read line。 - Each adapter's field set isexactyes; one more or one fewer is an error
must declare exactly:
| adapter | Required fields (exactly these) | Optional governance fields |
|---|---|---|
github-actions |
appliesTo branch event coverage selection |
mandatorySignoff allowOverride |
local-command |
appliesTo command |
mandatorySignoff allowOverride |
manual-attest |
appliesTo |
None (see below) |
none |
None (can only be inline) | None |
-
mandatorySignoff/allowOverrideis a governance modifier field and is not counted in the exact set above: it may only be written ongithub-actionsandlocal-command, and writing it onmanual-attestornonewill reportcannot declare mandatorySignoff or allowOverride(gates with a human witness no longer need these two modifiers). The value must be a YAML booleantrue/false; other forms (such asyes) are rejected by the schema; onlytrueenters the frozen contract, and writingfalseis equivalent to not writing it. -
github-actions's workflow list is not written in the gate mapping; it uniformly comes fromsettings.ci.workflows(inline array[a, b], names without.yml); mapping github-actions while workflows is empty → parse error. -
branch/eventvalues are restricted by^[A-Za-z0-9][A-Za-z0-9/_.@-]*$;coveragerecognizes onlyexact(the run's head SHA must be exactly the commit SHA) ordescendant(the run's head SHA has the commit SHA as an ancestor);selectionrecognizes onlynewest。 -
duplicate fields within the same gate, six-space fields appearing before the gate line, unknown lines →
cannot read line。 -
#acts as a comment on any line and is stripped—the command value cannot contain#(command: echo a#bwill becomeecho a)。 -
code-doc-reconciliationis a Harness internal checker and can only be mapped tonone; mapping any adapter reports an error. -
gatesis a repository-owned setting;ha settings update's input fields do not include gates; manually editingharness.yamlis the official channel, and rewriting other settings will not touch the gates section. After editing, useha settings showto read back and verify.
appliesTo selection
There are only three enum values (completion-contract.ts gateAppliesTo), which take effect according to the shape of the commit cut:
| Value | When it applies | Typical use |
|---|---|---|
submission |
Applies to every commit cut | Manual sign-off, checks on the deliverable itself |
code |
cut has a delivery commit (commitSha non-empty) |
Tests, lint, build, CI, merge gate |
artifacts |
cut has at least one accepted artifact anchor | Artifact sign-off, report review |
Verdict semantics: a gate that does not apply is recorded on that cut as not_applicable, does not block, and requires no evidence. A mixed delivery (commit + artifact) carries both kinds of gates at the same time.
Practical rules:
local-commandrequires a commit cut (it needs to materialize the commit into a sandbox); on an artifact-only delivery it will reportwitness_unavailable. For pure documentation/artifact deliveries usemanual-attest。- When a repository has both code tasks and documentation tasks, scope the code gates as
appliesTo: code, so documentation tasks will not be blocked; use delivery-level checkpoints (such as owner sign-off) assubmission。
Scenario: local-command (local command witness)
Treat a shell command as the witness for a gate. Execution semantics (repo-cell-witness-adapters.ts collectLocalCommand):
- Trigger timing:
task-submitandtask-completebefore the write queue; it is recollected every time until the gate already has an accepted pass witness bound to the current cut (or has been specially approved by the owner as waived — waived gates are no longer recollected). The previous fail is not frozen as final — after fixing, rerunningtask completewill collect a new pass. - Sandbox: the daemon usesthe exact commitwith
git archiveto unpack into a brand-new temporary directory and then run the command. What the command sees is the content at commit time, not the working tree; in the directory there isno.git. The temporary directory is deleted after use, and no state is retained between two runs (whatever dependencies were installed will not remain). - Command form:
sh -c "<command> 2>&1", stdout and stderr are merged. - Environment variables: only
PATHplus the following three; all other environment from the parent process (HOME, node/npm configuration, etc.) is not passed at all:
| Variable | Value |
|---|---|
HARNESS_WITNESS_CUT |
SHA of the witnessed commit |
HARNESS_WITNESS_GATE |
gate id |
HARNESS_WITNESS_REPO |
source repository root (containing .git), used for repository-level observation (such as merge ancestor checks) |
- The exit code is the verdict:
0= pass; nonzero = fail (at complete, blocking completion withinvalid_proof; when the gate declaresallowOverride, a fail is first recorded as an official receipt, and the blocker is changed to the special approval command); startup failure, being killed by a signal, or exceeding 10 minutes =witness_unavailable(evidence unavailable, neither pass nor fail; the task remains stuck on gate unmet). - Staleness prevention: the observation is bound to
commitSha + submissionDigest; after the commit is amended or the contract changes, the old observation is invalidated and must be recollected. - Reachability: the commit must exist in a repository readable by this node (the repository root or the worktree dispatched for this task). If a worker commits in a worktree but does not make it readable by the daemon, it will report
witness_unavailable。 - Trace retention: provenance records
exit N; output sha256:…; tail "…"(output summary + the last about 500 characters); the full output is not stored.
Rules for writing commands:
- Bring your own dependency installation (the sandbox is new every time):
npm ci && npm test、python3 -m venv .venv && …。 - Do not rely on HOME-type caches. Measured in a minimal environment: Go must explicitly
export GOCACHE="$PWD/.gocache", otherwise it reportsGOCACHE is not defined and $HOME is not defined; Rust is advised to explicitlyexport CARGO_HOME="$PWD/.cargo-home"(when there are dependencies, the registry also goes through it). - Checks that need to read git history (merge gates, tag checks) should use
git -C "$HARNESS_WITNESS_REPO"; do not expect the working directory to contain.git。 - The timeout is 10 minutes: when full installation + full testing does not fit in one command, split it into multiple gates or have the command create its own cache directory.
Merge gate example(the merged-to form of decision dec_59FA45A407F850E2B167A192D7, effective after declaration with a custom gate id and preset):
gates:
merged-to.main:
appliesTo: code
adapter: local-command
command: git -C "$HARNESS_WITNESS_REPO" merge-base --is-ancestor "$HARNESS_WITNESS_CUT" refs/heads/main
Behavior: at commit time main does not yet include this cut → a fail is collected (does not block submit; the verdict is judged at complete); after merging into main, rerun task complete → pass。
Scenario: manual-attest (manual sign-off checkpoint)
The adapter itself collects no observation: the witness is written by a human through the CLI.
ha task attest <task-id> --gate <gate-id> --result pass --note "审阅意见"
--resultonly recognizespassorfail;--noteis optional and will go into the evidence's rawResult;--modedefaults toapprove。- Preconditions (reject if any is missing): the task's current iteration has a committed execution; the gate is inFrozen contractin; the adapter declared by this gate is
manual-attest(for a gate declared asgithub-actionswith nomandatorySignoff, attesting will be rejected withinvalid_command- a manual pass cannot substitute for the declared witness source);appliesTomatches this cut (a code gate cannot sign off on an artifact-only cut). - The evidence provenance is
source: human, bound to the execution identity at that time. A runtime session with an executor can never sign off as a manual identity (actor_unauthorized) - only a human principal is recognized for sign-off. It does not replace the Review/Consent process, it is only a gate witness.
Suitable for: sign-off checkpoints in environments without automation, manual review of artifact-only deliveries, owner approval before go-live.
Scenario: mandatorySignoff (automatic witness + mandatory manual sign-off, dual control)
Add to the github-actions or local-command gate: mandatorySignoff: true:
gates:
ci:
adapter: github-actions
appliesTo: code
branch: main
event: push
coverage: descendant
selection: newest
mandatorySignoff: true
Behavior (judgeGateWitnesses): after the automatic witness passes, the gate status is signoff_missing rather than passed- the blocker for complete directly provides the sign-off command:
ha task attest <task-id> --gate ci --result pass
- Sign-off goes through the same
task attestentry point,--modedefaults toapprove; it requires that this cut already hasan accepted automatic pass, otherwise it rejectsinvalid_transition(runha task completefirst to persist the witness). approve cannot carry--rationale(that is an override field,invalid_field), available--note。 - Any human principal can sign off (not limited to the owner); a runtime executor cannot.
- Sign-off and the automatic witness are two independent evidence slots (lanes): a human sign-off does not overwrite the machine record, and machine re-collection does not overwrite the human sign-off.
manual-attestandnonegates cannot declare this field.
Scenario: allowOverride (owner break-glass special approval)
Add to the github-actions or local-command gate: allowOverride: true, leaving the task owner an emergency channel of "a recorded failure can be exempted":
gates:
e2e:
adapter: local-command
appliesTo: code
command: npm run e2e
allowOverride: true
Special approval command (provided by the blocker for complete and by the rejection message when collection fails):
ha task attest <task-id> --gate e2e --result pass --mode override --rationale "<为什么豁免,trim 后至少 10 字符>"
Admission and semantics (attestGateWitness / judgeGateWitnesses, verified item by item against the source code):
- The gate's frozen contract must have declared
allowOverride- adding it to YAML after submission does not affect the cut under review (invalid_command)。 - Only the task owner (the principal corresponding to
createdBy) can grant special approval; othersactor_unauthorized; a runtime executor cannot grant special approval. - Two exemption shapes:there is a recorded automatic fail -> the special approval is bound precisely to that receipt (recorded in the evidence as
waivedReceiptId: <receipt id>);this cut has no automatic receipt at all(runner unreachable, collection timeout, etc., no witness ever produced) ->waivedReceiptIdexplicitly recordsnull, meaning "exempt the absent automatic witness". When this cut already has an automatic pass or other witness, override rejectsinvalid_transition- a real pass does not need special approval. - Any machine receipt that arrives later invalidates the old special approval: new fail -> the gate returns to
failed, and the new receipt must be specially approved again; new pass ->passed(if the gate is also mandatorySignoff, it returns tosignoff_missing, requiring an independent approve). A gate that has been waived is no longer re-collected (local-command is not re-run, CI is not re-pulled), but already recorded observations are re-judged as usual, and new receipts are persisted as usual. - override never counts as signoff: a manual witness with override does not participate in the mandatorySignoff determination; but a gate that has been specially approved is recorded on the whole as
waived(satisfied state, not shown as passed), and no longer needs a separate sign-off. - Special approval is also bound to
commitSha + submissionDigest: after the cut is amended or the contract changes, evidence must be re-collected. allowOverrideA gate's automatic fail will first be persisted as a formal receipt (leaving the owner an exemptible receipt), and then complete's blocker gives the special-approval command; when collection itself fails (gh unreachable, command timeout), the rejection message likewise includes an override hint.
GUI corresponding entry: the daemon registers task.attest GUI action(repo.task.attest / bridge method taskAttest / POST /api/tasks/:taskId/attest), payload is the closed shape { taskId, gateId, result, mode?, note?, rationale? }—the override's rationale must go in rationale(putting it in note will be rejected for missing rationale); the renderer button's show/hide rules (only failed/missing and frozen-contract allowOverride gates show special approval,waived is displayed as specially approved release rather than passed) are implemented on the GUI side.
Scenario: none (explicit exemption)
gates:
ci: none
code-doc-reconciliation: none
none removes that gate requirement declared by the task: nothing runs, no pass is minted, and it no longer appears in the completion determination. It is an explicit declaration—"no mapping written" and "none written" are two different things: the former is a configuration error for an already-declared gate, the latter is the repository's ruling.code-doc-reconciliation can only be removed with none .
Common combinations: an internal tools repo doesn't want to be blocked by code-doc reconciliation → code-doc-reconciliation: none; a repo without GitHub CI → ci.workflows: [](the preset side will simply not declare cito the task, no need to write none).
Scenario: github-actions (the default witness for the built-in ci gate)
ci is the only built-in declared gate and by default goes through GitHub Actions. See the syntax section above for the mapping syntax; key points:
- The workflow list always comes from
settings.ci.workflows, and the mapping has noworkflowsfield. coverage: descendantaccepts subsequent runs with the commit SHA as ancestor (the scenario where CI runs on the merge commit after a merge PR is submitted);exactrequires the run to be attached to the commit SHA.selection: newestis currently the only selection strategy (highest run/attempt preferred).- This repo's (harness-anything itself) baseline mapping is
ci → github-actions + rewrite-ci, for comparison.
Multi-language examples
examples/ below is the fully parseable harness.yaml(all pass readSettingsFacet validation; command recipes tested in a minimal PATH-only environment):
| File | Scenario |
|---|---|
examples/harness-node.yaml |
Node repo: GitHub CI witness ci + local command witness lint + merge gate |
examples/harness-python.yaml |
Python repo: no GitHub CI, tests all via local command witness |
examples/harness-go.yaml |
Go repo: local vet+test (explicit GOCACHE) |
examples/harness-rust.yaml |
Rust repo: local cargo test --locked (explicit CARGO_HOME) |
examples/harness-manual-only.yaml |
No automation environment: exempt built-in gates + manual signature checkpoint |
examples/harness-docs-only.yaml |
Pure docs repo: artifact delivery + manual review signature |
examples/harness-governance.yaml |
Governance fields: CI dual-control signoff (mandatorySignoff) + e2e special approval (allowOverride) |
Except for the Node example's ci , the custom gate ids in the examples all need a matching preset declaration to be required by the task (see the "Model" section).
Troubleshooting comparison
| Symptom | Meaning | Handling |
|---|---|---|
Task declares completion gate X, but harness.yaml settings.gates maps no witness for it |
The task declares a gate, but the repository has no mapping | Add the mapping or write X: none |
settings.gates.X with adapter Y must declare exactly: … |
The field set is imprecise | Complete or remove extra fields according to the field table |
settings.gates cannot read line: … |
Indentation error, inline value not none, duplicate field | Fix the YAML against the syntax section |
Gate X is witnessed by github-actions, but settings.ci.workflows is empty |
The workflows registry is empty | Fill in settings.ci.workflows or switch adapters |
witness_unavailable(local-command) |
Commit unreachable / artifact-only cut / command timeout or startup failure | Confirm the commit is in a repository readable by the daemon; for artifact delivery switch to manual-attest; check whether the command is stuck;allowOverride Under the gate, special approval may be granted as prompted |
Gate X receipt … reported fail(invalid_proof, when complete) |
The command exits nonzero or the witness observation is fail (the gate did not declare allowOverride) | Fix it to exit code 0 (or after merge/attest), then rerun task complete |
gate status signoff_missing |
The automatic witness has passed, but the mandatorySignoff gate lacks a human signoff | ha task attest <task> --gate <g> --result pass |
gate status waived |
The owner has granted special approval (satisfied state, not passed) | No action needed; note that a new automatic receipt will invalidate it |
invalid_command(attest) |
The gate is not in the frozen contract / the gate does not declare allowOverride / an automated gate does not declare mandatorySignoff yet approval is attempted | Check the frozen contract (ha task show); changing governance fields requires a new submit to take effect |
invalid_transition(attest) |
No submitted execution / appliesTo mismatch / no automatic pass at approval time / an automatic witness already exists at override time | First submit/complete so the witness is persisted; a genuine pass does not need special approval |
actor_unauthorized(attest) |
A session signoff with an executor / a non-owner attempts to override | Performed by a human principal; the override must be the task creator |
invalid_field(attest) |
The override is missing --result pass or the rationale is less than 10 characters / approve incorrectly carries --rationale |
Fix it according to the command form |
The task is stuck at incomplete, and the blocker is a gate |
That gate has no pass witness bound to the current cut | Run ha task show to see the gate status; add evidence according to the adapter |
| A custom gate is mapped but never takes effect | No task declares it | For the preset's completionGates , add that id |
Boundaries and cautions
- The contract is frozen with the submission: changing
harness.yamlonly affects subsequent submits; an in-review cut is judged by the frozen contract. After the cut is amended,commitSha + submissionDigestchanges, and old observations (including human signoffs and special approvals) are invalidated and must be recollected. - Historical conclusions retained through migration (preserved verdict: an old "accepted" with no bound evidence) are recognized only in lifecycle replay,and can never serve as a gate pass for the current cut—a new cut must collect evidence again or be signed off again.
- Overview of the four forms: pure automatic witness (default),
manual-attestpure human,mandatorySignoffautomatic + signoff dual control,allowOverrideautomatic failure can be specially approved. The two governance fields can be attached to the same gate (dual control + special approval); a gate that has been specially approved is recorded aswaiveda satisfied state.