Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
6.6 KiB
Gate predicates (reference)
Diátaxis quadrant: Reference. This is the canonical specification of the capability gate
check.predicateevaluation path. For a step-by-step authoring guide, see How-to: add a command-exit-zero gate.
A capability gate's check block carries exactly one of three shapes
(query, predicate, agentVerdict), enforced by the registry validator
(capability-validator.cjs:validateGate). This page documents the
predicate shape and the kinds the built-in evaluator recognises.
Declaration
"gates": [
{
"point": "<loop-point>",
"check": {
"predicate": {
"kind": "<kind>",
"<kind-specific fields>"
}
},
"when": "<config-key>",
"blocking": true,
"onError": "halt"
}
]
The gate envelope (point, when, blocking, onError) follows the standard
contract documented in ADR-0894 (capability declaration format) and the
Loop Host Contract glossary entry in CONTEXT.md. This page covers only
check.predicate.
Evaluation path
- The loop-resolver (
msd-tools loop render-hooks <point>) renders the active gate hook (including itscheck.predicatedeclaration) to the workflow. - The workflow gate-dispatch reads the hook in-context and, when the
checkshape ispredicate, runs:msd_run check predicate --predicate '<predicate JSON>' [--phase-dir …] [--phase-number …] [--phase-req-ids …] --raw check-command-router.cts:cmdCheckPredicateparses the predicate, builds the production subprocess binding, and callsgate-predicate-evaluator.cjs:evaluatePredicate, which dispatches bypredicate.kind.- The evaluator returns the standard gate envelope:
{ "block": <bool>, "message": "<string>", "details": { … } } - The workflow applies the two-step gate contract unchanged:
- Step 1 — if the check command itself failed (non-zero exit, e.g. a
malformed predicate / unknown kind), route per
onError(haltorskip). - Step 2 — if the command succeeded, a
blocking: truegate halts onblock: true; an advisory gate showsmessageand continues.
- Step 1 — if the check command itself failed (non-zero exit, e.g. a
malformed predicate / unknown kind), route per
Built-in kinds
command-exit-zero
Runs a declared command in a bounded sh -c subprocess; exit 0 → pass,
non-zero → block, timeout → block. See ADR-2008 for the full sandbox
contract.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
kind |
string | yes | — | Must be "command-exit-zero" |
command |
string | yes | — | The shell command. Non-empty, ≤ 4096 chars |
timeout |
number | no | 30 |
Positive finite number, seconds |
Interpolation. Before execution, three placeholders are substituted from
the gate context; all others are left untouched for sh to interpret:
| Placeholder | Source | Workflow flag |
|---|---|---|
${PHASE_NUMBER} |
the active phase number | --phase-number |
${PHASE_DIR} |
the active phase directory | --phase-dir |
${PHASE_REQ_IDS} |
the phase's requirement ids | --phase-req-ids |
An undefined placeholder interpolates to the empty string.
--phase-dir is confined to the project. The value is validated to resolve
inside the project root before any predicate is evaluated; one that escapes is
rejected as a usage error rather than evaluated. This applies to both kinds —
artifact-frontmatter-equals resolves its artifact under that directory, and
command-exit-zero interpolates it into ${PHASE_DIR} — so an unconfined value
would let a blocking gate return block: false on evidence from a directory
the caller chose (#4354). An absolute path inside the project is still accepted;
absolute is not a synonym for escaping. ${PHASE_DIR} always interpolates the
resolved absolute path, even when --phase-dir was given as a relative
value — a command relying on ${PHASE_DIR} staying relative must not assume
that.
Sandbox. cwd = project root; env = inherited from the MSD process; killed (SIGTERM) on timeout. The command runs as the user, on the user's machine — there is no sandbox boundary vs. the user's own shell. See ADR-2008 "Trust model".
Result mapping.
| Command outcome | block |
message |
|---|---|---|
| exit 0 | false |
command exited 0 |
| exit N (non-zero) | true |
command exited N: <stderr/stdout tail, ≤2000 chars> |
| timeout (SIGTERM) | true |
command timed out after <s>s: <tail> |
sh missing (ENOENT, exit 127) |
true |
command exited 127: sh: not found |
Validation errors (throw → check-command failure → Step-1 / onError).
- Missing, non-string, empty, or whitespace-only
command. commandlonger than 4096 chars.timeoutpresent but not a positive finite number.- Unknown
kind.
artifact-frontmatter-equals
Reads a Markdown file with YAML frontmatter from the current phase directory (or falls back to the project root for project-level artifacts) and compares a field's value to the declared expectation. The value is matched using loosely typed string comparison or exact matching, where numeric expectations will safely match stringified numeric frontmatter values.
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
kind |
string | yes | — | Must be "artifact-frontmatter-equals" |
artifact |
string | yes | — | Suffix or exact filename (e.g. WINDOWS.md) |
field |
string | yes | — | Frontmatter key to read |
equals |
any | yes | — | Expected value (compared with string coercion) |
Result mapping.
| Command outcome | block |
message |
|---|---|---|
Value matches equals |
false |
Frontmatter field "<field>" matches expected value (<expected>) |
| Value mismatch | true |
Frontmatter field "<field>" in <artifact> is <actual>, expected <expected> |
| Artifact file not found | true |
Artifact matching <artifact> not found in <targetDir> |
Validation errors (throw → check-command failure → Step-1 / onError).
- Missing or empty
artifactstring. - Missing or empty
fieldstring. - Missing
equalsvalue. - File read or YAML parsing failure (I/O errors).
Extensibility
The evaluator dispatches through a KIND_TABLE. Adding a new built-in kind is
a one-line registration in gate-predicate-evaluator.cts — no workflow changes
required, since the workflow dispatches any check.predicate to the same
msd_run check predicate subcommand.
Related
- ADR-2008 — full decision record.
- How-to: add a command-exit-zero gate.
- ADR-0894 — capability declaration format.
src/gate-predicate-evaluator.cts,src/check-command-router.cts.