---
id: task-discovery
paths:
  - "src/gzkit/**"
  - "docs/design/adr/**"
  - ".gzkit/**"
description: Four-channel TASK attribution discovery taxonomy for governance traceability
---

<!-- rule-version: 0.2.0 -->

# TASK Discovery (gzkit)

> **Rule version:** `0.2.0` — OBPI-0.0.64-03: `gz task start --seq next|N` CLI surface implemented; subdivision sub-invariant now mechanically enforced via the new CLI surface rather than prose-only.

## Invariant

**Every unit of labor traceable to a TASK MUST surface that attribution through at least one of four discovery channels.** Layer-drift across the channels (different TASK IDs for the same logical unit) is itself a fail-close signature, not a tolerable inconsistency. The four-channel design ensures no single channel can become the silent bypass surface that GHI #553 named.

## The Four Channels

| Channel | Surface | Authoring contract | Discovery mechanism |
|---------|---------|--------------------|--------------------|
| Python `@advances` | Source functions in `src/gzkit/**` | `@advances("TASK-X.Y.Z-NN-MM-PP")` decorator on functions that materially advance the named TASK | Import-time decoration registers `TaskAttributionRecord`; module-level registry queried via `get_task_registry()` (ADR-0.0.64 / OBPI-02) |
| Frontmatter `tasks:` | Structured artifacts (OBPI briefs, ADR packages where applicable) | `tasks: list[str]` YAML frontmatter field listing TASK IDs this artifact advances | YAML parser walks frontmatter; schema enforcement deferred to OBPI-0.0.64-04 (`gz validate --task-envelope-coherence`) |
| Commit trailer | Git commit messages | `Task: TASK-X.Y.Z-NN-MM-PP` trailer in the final paragraph | `parse_task_trailers()` in `gzkit.tasks`; validated by `gz validate --commit-trailers` |
| Ledger `task_id` | Worklog event types in `.gzkit/ledger.jsonl` | Optional `task_id: str | None` field on the 8 worklog event types (OBPI-0.0.64-01) | JSON deserialization via `gzkit.events`; validator scope per OBPI-04 |

## Convention: Python `@advances`

```python
from gzkit.tasks import advances

@advances("TASK-0.0.64-02-01-01")
def my_function() -> None:
    """Function body unchanged — the decorator is metadata-only."""
    ...
```

**Decoration-time fail-close:**

- Invalid TASK ID format → `ValueError` at import (typos cannot ship)
- Unknown parent REQ → `ValueError` at import (TASK IDs whose `REQ-X.Y.Z-NN-MM` parent isn't in any extracted brief cannot ship)

The validation surface mirrors `@covers`'s precedent: typos that would silently pass at runtime instead block at import.

## Convention: Frontmatter `tasks:`

```yaml
---
id: OBPI-0.0.64-02-advances-decorator-and-discovery-convention
parent: ADR-0.0.64-task-envelope-and-planning-decomposition
tasks:
  - TASK-0.0.64-02-01-01
  - TASK-0.0.64-02-02-01
---
```

The `tasks:` channel is the structured-artifact equivalent of the `@advances` decorator — it declares which TASKs an artifact advances when the artifact itself is the deliverable (vs. when source functions are). Use this channel on briefs and ADR-package frontmatter when the work is documentation-shaped rather than code-shaped.

Schema enforcement for `tasks:` (rejecting malformed TASK IDs and unknown parents) is on the OBPI-0.0.64-04 work surface; this rule documents the channel.

## Convention: Commit trailer

```
fix(tasks): wire @advances decorator (GHI #553)

Task: TASK-0.0.64-02-01-01
```

The commit trailer is the freeform fallback for labor that does not produce a Python function or a structured artifact — chore work, sync ceremonies, documentation drift fixes. The `gz validate --commit-trailers` validator already enforces this surface; the `parse_task_trailers()` function accepts both the formal four-tier ID and the slug-form `TASK-<slug>-#<ghi>` for direct-fix work outside OBPI scope.

## Convention: Ledger `task_id`

The 8 worklog event types in `src/gzkit/events.py` carry an optional `task_id: str | None = None` field (OBPI-0.0.64-01). Worklog events emitted under an active TASK SHOULD populate this field. The validator signature for missing `task_id` under an active TASK is on the OBPI-0.0.64-04 work surface.

## Subdivision sub-invariant

A single REQ may have multiple TASKs (labor-subdivision via `seq=01`, `seq=02`, …). When work subdivides, each labor unit MUST get its own TASK ID — not a shared coarse-default-bucket TASK. The `seq` component is the subdivision axis; the OBPI pipeline mints `seq=01` per REQ as the coarse-default bucket, and operators/agents must `gz task start --seq next` to subdivide further.

Default-bucket-only OBPIs (every REQ has only `seq=01`) without a `req_atomic: list[str]` exemption in brief frontmatter are themselves a fail-close signature (OBPI-04 validator scope).

## Layer-drift fail-close

When a single logical unit of labor surfaces across multiple channels with different TASK IDs — `@advances` decorator names TASK-A, frontmatter `tasks:` names TASK-B, commit trailer names TASK-C — the divergence IS the signal. The OBPI-04 validator will fail Heavy lane closeouts on layer-drift; Lite lane warns.

## Do Not

- Do not use `@advances` as a comment marker (`# @advances: TASK-...`) — the decorator is the contract; comments have no AST node, no decoration-time validation, no typo defense
- Do not pre-register full TASK IDs in a closed set — TASKs are minted by the pipeline at runtime; validation is via the parent REQ
- Do not coarse-bucket subdivisible labor under a single `seq=01` TASK — subdivision via `seq=02`, `seq=03`, … is the mechanism, not an option
- Do not silently rewrite TASK IDs across channels to "make the validator happy" — layer-drift is the signal, not the defect to suppress

## Related

- ADR-0.0.64-task-envelope-and-planning-decomposition (parent)
- OBPI-0.0.64-01-task-id-worklog-schema-additive (Ledger `task_id` channel)
- OBPI-0.0.64-04-gz-validate-task-envelope-coherence (validator)
- `.gzkit/rules/skill-surface-sync.md` (rule version-discipline convention)
- `src/gzkit/tasks.py` (`@advances`, `TaskAttributionRecord`, registry)
