Appearance
Aspect Status
Every Yggdrasil aspect has one of three statuses: draft, advisory, enforced. Status governs how results render, and it never changes a verdict's input hash or whether a recorded verdict stays valid and is re-used. Beyond rendering it has several operational effects. draft removes a pair from the expected set entirely (nothing is verified for it) and, through implies, propagates nothing at all — a draft implier never delivers the aspects it implies (see Draft is dormant). An enforced script-rule refusal on a pair's owning component — or, for a pair with no owning component, on its unit — makes yg check --approve skip that component's (or that unit's) reviewer pairs for the run, so a known-broken subject never bills the reviewer. And only enforced pairs gate positive closure: a node's source fingerprint and log baseline advance only once every enforced pair on it is freshly settled, while an advisory refusal never blocks closure. Otherwise it is the dial you turn as a rule matures: start silent, gather signal, then enforce.
Three statuses
| Status | A refusal renders as | An unverified pair renders as | Blocks yg check? |
|---|---|---|---|
draft | pair not expected | pair not expected | no |
advisory | warning | warning | no |
enforced | error | error | yes |
A third pair state follows status the same way the two columns above do: companion-error. When a reviewer rule's companion.mjs hook fails while the prompt-size gate assembles the pair, the pair cannot be built and aspect-companion-runtime-error is emitted — a warning under advisory, an error that blocks yg check under enforced. Read the table's two columns as "a status-sensitive finding," not as the only two that exist.
A pair whose effective status is draft is not expected — nothing is verified for it and no new verdict is written. (Effective status is the strictest status across every channel that attaches the aspect, so an aspect authored status: draft is still enforced wherever an attach site or an active implier raises it — see How effective status is computed.) A verdict already recorded for a pair survives a draft round-trip and stays in the lock — see Status and verdicts. advisory and enforced pairs are both verified and cached the same way; the status only changes severity. Severity follows status with one exception: advisory never blocks (whether a pair is refused or merely unverified — and a missing reviewer: section is reported at the strictest status among the reviewer pairs it leaves without a reviewer, so advisory reviewer rules alone never make it block) and enforced always blocks — but a prompt-too-large failure is an error regardless of status, so an advisory pair can still block yg check if its assembled prompt exceeds the resolved tier's max_prompt_chars. Status is the only thing that cannot soften it: like every other finding below, an oversized pair your change did not reach is listed as a warning when progressive mode is on, and yg check --full blocks on it again. On a project that names no branch — the default — it blocks on every run, advisory or not.
One project-level setting changes where an enforced finding blocks, and only there: when progressive mode is on — the project names a branch in progressive.reference — an enforced finding your change did not reach is listed as a warning instead of an error, and yg check --full blocks on it again. Status is unchanged by this: the pair is still enforced, still verified and cached the same way, and still blocks the moment a change reaches it. On a project that names no branch (the default), the table above is the whole story.
When to use each
draft— content.md / check.mjs is still being authored, or the rule is unclear. Zero cost, zero expected pairs. Use this while iterating on the rule text before any node has a real verdict. A draft aspect is still runnable viayg aspect-test(status never gates that diagnostic;--dry-runpreviews the prompt for free, a live run calls the reviewer).draftis also the only way to park an aspect without a provider key — it removes the pairs rather than leaving them red. For reviewer rules with acompanion.mjshook: when the aspect isdraft, the hook never runs duringyg check --approve;yg aspect-teststill runs it live. One consequence to know before you reach for the machine-readable output: a draft pair is dropped beforeyg check --jsonis projected, so a draft rule has no rows inpairs[]at all, and an empty join there means either "reaches nothing" or "is draft" with no way to tell the two apart. The surface that does enumerate a draft rule's reach isyg aspects --json --reach, whosereach.units[]lists a draft unit like any other.advisory— rule is complete; gather signal across the repo without blocking CI. Pairs are verified and cached normally; refusals and unverified pairs surface as warnings. Use this to measure how often a rule fires on real code before promoting it.enforced— rule is vetted; violations should block CI. Refusals and unverified pairs are errors that blockyg check.
A typical lifecycle: draft while authoring → advisory for one or two sprints to observe behavior → enforced once you have confidence the rule fires only on real violations.
Declaring status
status: can appear on the aspect itself (aspect-level default) and on every object-form attach site. The aspect-level default applies wherever the aspect attaches, and an attach site can only raise it: the effective status is the strictest of the aspect default and every site that attaches the aspect, and a site that declares a lower status is an aspect-status-downgrade error (see How effective status is computed). To run a rule softer in most places and stricter in a few, give the aspect the lower default and raise it at the sites that need more.
Aspect-level default
yaml
# .yggdrasil/aspects/audit-logging/yg-aspect.yaml
name: Audit Logging
description: "Every mutation emits an audit event"
status: advisory # default for every attach site
reviewer:
type: llmIf status: is omitted, the aspect defaults to enforced.
Per-node attach site
yaml
# .yggdrasil/model/orders/yg-node.yaml
aspects:
- id: audit-logging
status: enforced # promote on this node onlyBare-string entries (- audit-logging) inherit the aspect-level default. Object form ({ id, status }) is the only way to raise the status per site.
Architecture node-type default
yaml
# .yggdrasil/yg-architecture.yaml
node_types:
command:
aspects:
- id: cli-contract
status: enforcedFlow-level
yaml
# .yggdrasil/aspects/correlation-tracking/yg-aspect.yaml
status: advisory # soft everywhere by default
# .yggdrasil/flows/checkout/yg-flow.yaml
aspects:
- id: correlation-tracking
status: enforced # promote for the checkout participantsThe flow raises the status for its participants. The reverse, a flow that declares advisory for an aspect whose default is enforced (the default when the aspect sets no status:), is a downgrade and fails validation.
Port-level
yaml
# .yggdrasil/model/payments/yg-node.yaml
ports:
charge:
aspects:
- id: idempotency
status: enforcedLaw on a node type
A rule attached to one node is the agent's own to raise. A rule that reaches a node type — listed under the type in yg-architecture.yaml, or implied by a rule the type lists — governs every file of that type, today's and every later one. That is shared vocabulary, and the user admits it:
- The agent writes the rule
advisory. It shows inyg checkand blocks nothing, so the work goes on. - The user decides. If they admit it, the admission goes into the rule's own log:
yg log add --aspect <id> --ratify --by '<who>' --reason '<what was admitted>'. The command reads every type the rule reaches and the rule's current version from the graph (details). - Then the rule may be set
enforced. Status is not part of the version, so this needs no second admission; changing what the rule demands does.
yg check reports type-law-unratified for a rule standing enforced on a type without a ratification of its current version for that type. It is reported, as a blocking error, where yg-config.yaml sets type_law: { ratification: true } — the default for a new project, and what yg init --upgrade turns on once after recording the law an existing graph already had (configuration); without that setting no ratification is asked for. Without an admission the rule simply stays advisory; nothing forces the question.
How effective status is computed
For each (node, aspect) pair, the resolver collects every channel that attaches the aspect (after when filtering), then takes the maximum:
effective_status = max(declared in each channel)where draft < advisory < enforced. The strictest status wins.
If an attach site explicitly declares a status lower than the strictest of the aspect-level default and every other channel, the validator emits aspect-status-downgrade as an error. The message names the site that declares the lower status and where the higher one comes from (the aspect default, another site, or both). The rule is bump up OK, downgrade is an error: a node can promote an aspect to enforced, but it cannot quietly weaken what the architecture or a flow demands.
To resolve a downgrade error: remove the explicit lower status from the attach site, or, if the rule really should be softer everywhere, lower the aspect-level default (and any other site that declares the higher status).
This max() computation and the downgrade check apply to the cascading attach channels 1–6 (own, ancestor, own type, ancestor type, flows, ports). Channel 7 (implies) does not declare a status: — it carries status_inherit: instead, described in the next section.
Implies propagation
Terminology note: In the
implies:mechanism, aspects implied by another aspect are sometimes informally called "companions" in prose (as in "an implies bundle promotes its implied companions"). This is a different concept from companion files — the optionalcompanion.mjshook on a reviewer rule that resolves per-unit files for the reviewer. This page uses "implied sibling" or "implied aspect" to avoid ambiguity.
For aspect A that implies aspect B, propagation to a node depends on A's effective status on that node and the status_inherit: modifier on the implies edge.
Draft is dormant
If A's effective status on node N is draft, then B is not propagated via implies. Draft aspects have zero side effects. B may still arrive on N through any other channel.
Active implier — status_inherit modes
yaml
# .yggdrasil/aspects/audit-logging/yg-aspect.yaml
implies:
- id: diagnostic-logging
status_inherit: strictest # defaultTwo modes:
strictest(default) — B contributesmax(A_effective, B_default)to the cascade. An implies bundle promotes its implied siblings. If A runs asenforcedand B defaults toadvisory, B becomesenforcedon that node.own-default— B contributesB_defaultregardless of A. Use this to decouple propagation when an implied aspect should retain its own status even when reached through a stricter implier.
yaml
implies:
- id: diagnostic-logging
status_inherit: own-default # keep B at its own defaultBare-string implies entries (implies: [diagnostic-logging]) inherit the strictest default.
Status and verdicts
Status is deliberately not part of a pair's input hash. Verdicts survive every status flip — flipping advisory ↔ enforced (or a full draft round-trip and back) never re-runs the reviewer; the recorded verdict carries forward as long as the pair's real inputs are unchanged.
What each transition does:
draft → advisory/enforced— the aspect's pairs become expected. If they were never verified, they appear asunverifieduntilyg check --approvefills them. If they already hold a valid verdict (adraftround-trip), they are instantly re-used — no reviewer call.advisory → enforced— re-uses the existing verdict; nothing re-runs. But if that verdict is a refusal, the pair now renders as an error instead of a warning, so a passing check can turn red. Promote intentionally and expect any recorded refusal to surface.enforced/advisory → draft— the aspect's pairs leave the expected set. Their lock entries are kept (verdicts survive), so returning to enforced re-uses them; garbage collection only prunes a pair that is genuinely gone (aspect detached, file deleted,whennow false), never one parked atdraft.
when is not status
The when predicate decides whether an aspect applies to a node. Status decides what happens when it applies. They are orthogonal:
when=false→ aspect is invisible on the node. No pair, no verdict, no display inyg context. Costs nothing.status=draft→ aspect applies but produces no expected pair. Appears inyg context, no reviewer call, no recorded verdict.
Use when to model applicability (this rule only applies to nodes that call an external service). Use status to model rule maturity (this rule is still being authored / observed / enforced).
See Conditional Aspects for the when predicate grammar.
See also
- Aspects — rules, and the seven ways one reaches your code
- The lock — how verdicts are stored
- Reviewers — how the reviewer is invoked
- Conditional Aspects —
whenpredicates - CLI Reference — issue codes emitted by
yg check