⚠ Deprecated: `gz gates` will be removed in a future release. Use `gz closeout` instead.
  ✓ Gate 1 (ADR): PASS
(docs/design/adr/foundation/ADR-0.0.73-verification-layer-binding-audit/ADR-0.0.
73-verification-layer-binding-audit.md)
Gate 2 (TDD): uv run gz test
Running unit tests...

=== Operator-Verbatim Uncovered REQ Acceptance (GHI #587) ===
  OBPI:     OBPI-9.9.9-99-fixture
  ADR:      ADR-9.9.9-fixture
  Attestor: Jeffry Babb
  Waiving:  REQ-9.9.9-99-01
  Operator-verbatim authorization carried on the CLI via
--accept-uncovered-reason (canon-owner directive 2026-05-14).
Error: OBPI completion REQ-coverage gate failed (heavy/foundation policy).
  - uncovered: REQ-9.9.9-01-01
Recovery: add a `@covers(REQ-X.Y.Z-NN-MM)` test for each gap, or fix the failing
covering tests, then re-run completion.
Error: Signature (b): OBPI OBPI-0.0.64-04 closed with only seq=01 TASKs across
all REQs and no req_atomic exemption for: REQ-0.0.64-04-01. Subdivide via `gz
task start --seq next` or declare `req_atomic:` in brief frontmatter with inline
rationale.
--bypass-req-kind-discipline-once requires --bypass-reason
{
  "by_adr": [],
  "by_obpi": [],
  "entries": [],
  "summary": {
    "identifier": "OBPI-0.0.70-01-stop-hook-turn-end-feedback",
    "total_reqs": 0,
    "covered_reqs": 0,
    "uncovered_reqs": 0,
    "coverage_percent": 0.0,
    "behavior_uncovered_reqs": 0,
    "grandfathered_reqs": 0
  }
}

................................................................................
................................................................................
................................................................................
................................................................[1/1] Test
................................................................................
................................................................................
................................................................................
................................................................................
.........................../Users/jeff/Documents/Code/gzkit/src/gzkit/pipeline_r
untime.py:376: DeprecationWarning: Brief 'brief.md' lacks structured frontmatter
fields (allowlist, reqs, verification); loading as LegacyBriefShape. Migrate to
structured frontmatter per OBPI-0.0.37-04.
  parsed = parse_brief(brief_path)
..../Users/jeff/Documents/Code/gzkit/src/gzkit/pipeline_runtime.py:376:
DeprecationWarning: Brief 'brief.md' lacks structured frontmatter fields
(allowlist, reqs, verification); loading as LegacyBriefShape. Migrate to
structured frontmatter per OBPI-0.0.37-04.
  parsed = parse_brief(brief_path)
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
...............................................................................F
idelity validation failed : Surface weight limit exceeded
File not written.
...............scenario-reachability: registry absent (ADR-0.0.34); skipping
reachability check
.........scenario-reachability: registry absent (ADR-0.0.34); skipping
reachability check
scenario-reachability: registry absent (ADR-0.0.34); skipping reachability check
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
.............................................../Users/jeff/Documents/Code/gzkit/
src/gzkit/governance/brief_reconcile.py:131: DeprecationWarning: Brief
'brief.md' lacks structured frontmatter fields (allowlist, reqs, verification);
loading as LegacyBriefShape. Migrate to structured frontmatter per
OBPI-0.0.37-04.
  parsed = parse_brief(brief_path)
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
...............................scenario-reachability: registry absent
(ADR-0.0.34); skipping reachability check
......................scenario-reachability: registry absent (ADR-0.0.34);
skipping reachability check
................................................................................
............................scenario-reachability: registry absent (ADR-0.0.34);
skipping reachability check
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
............../Users/jeff/Documents/Code/gzkit/src/gzkit/pipeline_runtime.py:376
: DeprecationWarning: Brief 'OBPI-0.0.37-07-test.md' lacks structured
frontmatter fields (allowlist, reqs, verification); loading as LegacyBriefShape.
Migrate to structured frontmatter per OBPI-0.0.37-04.
  parsed = parse_brief(brief_path)
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
....................................................
----------------------------------------------------------------------
Ran 6312 tests in 79.311s

OK

Unit tests passed.

  ✓ Gate 2 (TDD): PASS
  ↳ Eval delta: skipped (no baselines) — 5 surfaces scored, overall 2.7/4.0
  → Gate 3 (Docs): uv run mkdocs build --strict

[31m │  ⚠  Warning from the Material for MkDocs team[0m
[31m │[0m
[31m │[0m  MkDocs 2.0, the underlying framework of Material for MkDocs,
[31m │[0m  will introduce backward-incompatible changes, including:
[31m │[0m
[31m │  × [0mAll plugins will stop working – the plugin system has been removed
[31m │  × [0mAll theme overrides will break – the theming system has been
rewritten
[31m │  × [0mNo migration path exists – existing projects cannot be upgraded
[31m │  × [0mClosed contribution model – community members can't report bugs
[31m │  × [0mCurrently unlicensed – unsuitable for production use
[31m │[0m
[31m │[0m  Our full analysis:
[31m │[0m
[31m │[0m
[4mhttps://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/[0m
[0m
INFO    -  Cleaning site directory
INFO    -  Building documentation to directory:
/Users/jeff/Documents/Code/gzkit/site
INFO    -  The following pages exist in the docs directory, but are not included
in the "nav" configuration:
  - AGENTS.md
  - config/gz-cookiecutter-python-stack.md
  - drafts/claude-code-inventory.md
  - drafts/claude-code-vs-codex-control-surface-parity.md
  - drafts/ddd-palantir-ontology-design-note.md
  - examples/presentations/index.md
  - examples/presentations/complete-series-script.md
  - examples/presentations/part1-script.md
  - examples/presentations/part2-script.md
  - examples/presentations/part3-script.md
  - examples/presentations/part4-script.md
  - examples/presentations/part5-script.md
  - examples/presentations/part6-script.md
  - examples/templates/req-template.md
  - governance/AGENTS.md
  - governance/advisory-rules-audit.md
  - governance/agent-contract-rationale.md
  - governance/agent-control-surface-fidelity-doctrine.md
  - governance/agent-control-surface-rendering-substrate.md
  - governance/agents-md-doctrine.md
  - governance/ai-governance-brief.md
  - governance/ai-governance-research-gaps.md
  - governance/ai-governance-research-literature.md
  - governance/ai-governance-research-practice.md
  - governance/ai-governance-research-regulation.md
  - governance/ai-governance-verification.md
  - governance/ai-governance.md
  - governance/ai-governance.provenance.md
  - governance/arb-middleware.md
  - governance/behavior-rules.md
  - governance/build-to-1.0-campaign-2026-06-10.md
  - governance/cross-platform-rationale.md
  - governance/defect-fix-routing.md
  - governance/distribution_baseline.md
  - governance/distribution_invariant_catalog.md
  - governance/foundation-triage-rubric.md
  - governance/four-phases-of-work.md
  - governance/harness-engineering-appraisal.md
  - governance/hexagonal-architecture.md
  - governance/historical-self-close-waivers.md
  - governance/layer-three-derived-views.md
  - governance/model-regression-taxonomy.md
  - governance/model-selection-rationale.md
  - governance/obpi-attestation.md
  - governance/operator-economy.md
  - governance/opus-tuning.md
  - governance/personas-catalog.md
  - governance/prime-directive.md
  - governance/prior-art-scaffold-firewall-2026-06-14.md
  - governance/req-scope-discipline.md
  - governance/return-to-health-plan-2026-05-30.md
  - governance/security-sensitivity-rationale.md
  - governance/skill-surface-sync-rationale.md
  - governance/skills-catalog.md
  - governance/tests-rationale.md
  - governance/token-block-doctrine.md
  - governance/tool-skill-runbook-rationale.md
  - governance/trust-doctrine.md
  - governance/ultraplan-brief.md
  - governance/vocabulary-config-first-exorcism-GHI-615.md
  - governance/work-phases-and-airlock.md
  - governance/GovZero/obpi-pipeline-runbook.md
  - governance/complexity/AGENTS.md
  - governance/complexity/authoring-guide-protocol.md
  - governance/complexity/complexity-doctrine-rationale.md
  - governance/complexity/complexity-thresholds-rationale.md
  - governance/complexity/distilled-characteristics-2026-05-04.md
  - governance/complexity/baselines/2026-05-04/baseline.summary.md
  - governance/research_sources/0900001680afb122.md
  -
governance/research_sources/1-introduction-the-imperative-of-public-values-in-ai
.md
  - governance/research_sources/1680afaeba.md
  -
governance/research_sources/a-closer-look-at-the-existing-risks-of-generative-ai
-mapping-the-who-what-and-how-of-real-world-inci.md
  - governance/research_sources/advancing-accountability-in-ai-en.md
  -
governance/research_sources/ai-risk-management-framework-japanese-translation.md
  -
governance/research_sources/anthropics-responsible-scaling-policy-version-30.md
  -
governance/research_sources/artificial-intelligence-risk-management-framework-ai
-rmf-10.md
  -
governance/research_sources/artificial-intelligence-risk-management-framework-ge
nerative-artificial-intelligence-profile.md
  - governance/research_sources/arxiv-240614713.md
  - governance/research_sources/arxiv-240714981.md
  -
governance/research_sources/auditing-work-exploring-the-new-york-city-algorithmi
c-bias-audit-regime.md
  - governance/research_sources/claude-4-system-card.md
  - governance/research_sources/hai-ai-index-report-2025-chapter3-final.md
  - governance/research_sources/hls20white20paper20final-v3.md
  -
governance/research_sources/introducing-the-oecd-ai-capability-indicators-en.md
  -
governance/research_sources/m-24-10-advancing-governance-innovation-and-risk-man
agement-for-agency-use-of-artificial-intelligenc.md
  -
governance/research_sources/m-25-21-accelerating-federal-use-of-ai-through-innov
ation-governance-and-public-trust.md
  -
governance/research_sources/m-25-22-driving-efficient-acquisition-of-artificial-
intelligence-in-government.md
  -
governance/research_sources/microsoft-word-ai-governance-tiimalasipaperi-arxivdo
cx.md
  - governance/research_sources/nistai600-1genai-profileipd.md
  - governance/research_sources/operator-system-card.md
  - governance/research_sources/preparedness-framework-v2.md
  - governance/research_sources/quantifying-detection-rates.md
  -
governance/research_sources/responsible-ai-governance-a-systematic-literature-re
view.md
  -
governance/research_sources/responsible-ai-governance-in-the-public-sector-expla
ining-contextual-dynamics-through-a-realist-synt.md
  - governance/research_sources/responsible-use-guide.md
  - governance/research_sources/steering-ais-future-en.md
  -
governance/research_sources/the-bureaucratic-challenge-to-ai-governance-an-empir
ical-assessment-of-implementation-at-us-federal.md
  -
governance/research_sources/the-governance-of-ai-companies-reconciling-purpose-w
ith-profits.md
  - harness-docs/GZK-GOV-007-ultrareview-and-gate-5.md
  - harness-docs/expanding-the-bitter-lesson-for-agentic-software-development.md
  - harness-docs/xhigh-measurement-protocol.md
  - releases/PATCH-v0.24.3.md
  - releases/PATCH-v0.25.10.md
  - releases/PATCH-v0.25.11.md
  - releases/PATCH-v0.25.12.md
  - releases/PATCH-v0.25.13.md
  - releases/PATCH-v0.25.14.md
  - releases/PATCH-v0.25.15.md
  - releases/PATCH-v0.25.16.md
  - releases/PATCH-v0.25.17.md
  - releases/PATCH-v0.25.18.md
  - releases/PATCH-v0.25.19.md
  - releases/PATCH-v0.25.7.md
  - releases/PATCH-v0.25.8.md
  - releases/PATCH-v0.25.9.md
  - releases/PATCH-v0.26.0.md
  - releases/PATCH-v0.26.1.md
  - releases/PATCH-v0.26.2.md
  - releases/PATCH-v0.26.3.md
  - releases/PATCH-v0.26.4.md
  - releases/PATCH-v0.26.5.md
  - releases/PATCH-v0.26.6.md
  - releases/PATCH-v0.27.0.md
  - releases/PATCH-v0.27.1.md
  - releases/PATCH-v0.28.1.md
  - superpowers/plans/2026-03-26-pipeline-reliability-improvements.md
  - superpowers/specs/2026-03-26-pipeline-reliability-improvements-design.md
  - user/concepts/reporter-architecture.md
  - user/concepts/subagent-pipeline.md
  - user/concepts/task-overview.md
  - user/manpages/adr-demote.md
  - user/manpages/adr-evaluate.md
  - user/manpages/adr-report.md
  - user/manpages/arb-advise.md
  - user/manpages/arb-coverage.md
  - user/manpages/arb-patterns.md
  - user/manpages/arb-ruff.md
  - user/manpages/arb-step.md
  - user/manpages/arb-ty.md
  - user/manpages/arb-typecheck.md
  - user/manpages/arb-validate.md
  - user/manpages/arb.md
  - user/manpages/brief-reconcile.md
  - user/manpages/check.md
  - user/manpages/chores-advise.md
  - user/manpages/chores-doctor.md
  - user/manpages/chores-propose-ghi.md
  - user/manpages/chores-show.md
  - user/manpages/chores.md
  - user/manpages/complexity-advise.md
  - user/manpages/complexity-distill.md
  - user/manpages/complexity-guide.md
  - user/manpages/content.md
  - user/manpages/context.md
  - user/manpages/drift.md
  - user/manpages/format.md
  - user/manpages/frontmatter-reconcile.md
  - user/manpages/governance-render.md
  - user/manpages/interview.md
  - user/manpages/issue-file.md
  - user/manpages/issue.md
  - user/manpages/justify.md
  - user/manpages/lint.md
  - user/manpages/obpi-audit.md
  - user/manpages/obpi-complete.md
  - user/manpages/obpi-lock-check.md
  - user/manpages/obpi-lock-claim.md
  - user/manpages/obpi-lock-list.md
  - user/manpages/obpi-lock-release.md
  - user/manpages/obpi-repudiate.md
  - user/manpages/obpi-withdraw.md
  - user/manpages/personas-drift.md
  - user/manpages/personas.md
  - user/manpages/plan.md
  - user/manpages/preflight.md
  - user/manpages/readiness-evaluate.md
  - user/manpages/roles.md
  - user/manpages/skill-list.md
  - user/manpages/skill-new.md
  - user/manpages/task-envelope-diagnose.md
  - user/manpages/task-fanout.md
  - user/manpages/test.md
  - user/manpages/tidy.md
  - user/manpages/typecheck.md
  - user/manpages/upgrade.md
  - user/manpages/validate.md
  - user/skills/_TEMPLATE.md
  - user/skills/airlineops-parity-scan.md
  - user/skills/ghi-author.md
  - user/skills/ghi-close.md
  - user/skills/ghi-triage.md
  - user/skills/gz-adr-autolink.md
  - user/skills/gz-adr-promote.md
  - user/skills/gz-adr-recon.md
  - user/skills/gz-adr-sync.md
  - user/skills/gz-advisor-qc.md
  - user/skills/gz-agent-sync.md
  - user/skills/gz-brief-reconcile.md
  - user/skills/gz-check-config-paths.md
  - user/skills/gz-chores.md
  - user/skills/gz-competitor-radar.md
  - user/skills/gz-complexity-distill.md
  - user/skills/gz-complexity-guide.md
  - user/skills/gz-content-compose.md
  - user/skills/gz-content-remember.md
  - user/skills/gz-context-diet.md
  - user/skills/gz-context.md
  - user/skills/gz-deps-upgrade.md
  - user/skills/gz-foundation-triage.md
  - user/skills/gz-governance.md
  - user/skills/gz-issue-file.md
  - user/skills/gz-justify.md
  - user/skills/gz-manage.md
  - user/skills/gz-migrate-semver.md
  - user/skills/gz-project.md
  - user/skills/gz-pythonic-pattern-apply.md
  - user/skills/gz-pythonic-pattern-detect.md
  - user/skills/gz-quality.md
  - user/skills/gz-skill-router.md
  - user/skills/gz-tech-debt-review.md
  - user/skills/gz-tidy.md
  - user/skills/gz-workflow.md
  - user/storybook/from-init-to-first-attested-release.md
INFO    -  Documentation built in 2.86 seconds

  ✓ Docs build: PASS
  ✓ Skill Audit: PASS (skills=64 blocking=0 warnings=0)
  ✓ Gate 3 (Docs): PASS
  → Gate 4 (BDD): uv run -m behave features/
USING RUNNER: behave.runner:Runner
Feature: gz adr audit-check same-commit-window @covers backfill heuristic
(ADR-0.0.23 / OBPI-0.0.23-05) # features/adr_audit_covers_backfill.feature:1
  As an auditor of a gzkit-governed project,
  I want gz adr audit-check to flag @covers(REQ-...) decorators introduced
  in the same commit as the REQ's closing receipt,
  so that the cosmetic-backfill anti-pattern from GHI #309 cannot silence
  the audit even when an agent drives the entire codebase end-to-end.
  Feature: gz adr audit-check same-commit-window @covers backfill heuristic
(ADR-0.0.23 / OBPI-0.0.23-05)  # features/adr_audit_covers_backfill.feature:1

  @REQ-0.0.23-05-09
  Scenario: Heavy-lane same-commit backfill exits 3 with the remediation hint
# features/adr_audit_covers_backfill.feature:13
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And the audit-thresholds file is present at "data/audit_thresholds.json"
# features/steps/adr_audit_covers_backfill_steps.py:36
    Given a heavy ADR with a same-commit @covers backfill exists for
OBPI-0.1.0-01-demo # features/steps/adr_audit_covers_backfill_steps.py:50
    When I run the gz command "adr audit-check ADR-0.1.0-f"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220
    And the output contains "covers-backfill finding"
# features/steps/gz_steps.py:225
    And the output contains "Invariant 6f"
# features/steps/gz_steps.py:225

Feature: gz adr promote --kind taxonomy enforcement (ADR-0.0.17 /
OBPI-0.0.17-03) # features/adr_promote.feature:1
  As an operator promoting a pool ADR,
  I want --kind to declare and enforce taxonomic intent at promotion time,
  so that pool->canonical promotion is explicit, atomic, and audit-recorded.
  Feature: gz adr promote --kind taxonomy enforcement (ADR-0.0.17 /
OBPI-0.0.17-03)  # features/adr_promote.feature:1

  @REQ-0.0.17-03-01
  Scenario: --kind appears in --help with three choices            #
features/adr_promote.feature:11
    Given the workspace is initialized                             #
features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists #
features/steps/gz_steps.py:282
    When I run the gz command "adr promote --help"                 #
features/steps/gz_steps.py:209
    Then the command exits with code 0                             #
features/steps/gz_steps.py:220
    And the output contains "--kind"                               #
features/steps/gz_steps.py:225
    And the output contains "foundation"                           #
features/steps/gz_steps.py:225
    And the output contains "feature"                              #
features/steps/gz_steps.py:225
    And the output contains "pool"                                 #
features/steps/gz_steps.py:225

  @REQ-0.0.17-03-01
  Scenario: --kind pool is rejected with exit 1
# features/adr_promote.feature:20
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.6.0
--kind pool" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "source"
# features/steps/gz_steps.py:225

  @REQ-0.0.17-03-02
  Scenario: --kind foundation rejects non-0.0.x semver
# features/adr_promote.feature:26
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.6.0
--kind foundation"      # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "0.0."
# features/steps/gz_steps.py:225
    And the file
"design/adr/foundation/ADR-0.6.0-sample-work/ADR-0.6.0-sample-work.md" does not
exist # features/steps/gz_steps.py:261

  @REQ-0.0.17-03-03
  Scenario: --kind feature rejects 0.0.x semver
# features/adr_promote.feature:33
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.0.18
--kind feature"           # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the file
"design/adr/pre-release/ADR-0.0.18-sample-work/ADR-0.0.18-sample-work.md" does
not exist # features/steps/gz_steps.py:261

  @REQ-0.0.17-03-04
  Scenario: validation failure leaves no governance artifact behind
# features/adr_promote.feature:39
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.6.0
--kind foundation"       # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the file
"design/adr/foundation/ADR-0.6.0-sample-work/ADR-0.6.0-sample-work.md" does not
exist  # features/steps/gz_steps.py:261
    And the file
"design/adr/pre-release/ADR-0.6.0-sample-work/ADR-0.6.0-sample-work.md" does not
exist # features/steps/gz_steps.py:261

  @REQ-0.0.17-03-05
  Scenario: promoted ADR carries kind: in frontmatter
# features/adr_promote.feature:46
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.0.18
--kind foundation --force"                      # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the file
"design/adr/foundation/ADR-0.0.18-sample-work/ADR-0.0.18-sample-work.md"
contains "kind: foundation"           # features/steps/gz_steps.py:240
    And the file
"design/adr/foundation/ADR-0.0.18-sample-work/ADR-0.0.18-sample-work.md"
contains "id: ADR-0.0.18-sample-work" # features/steps/gz_steps.py:240

  @REQ-0.0.17-03-06
  Scenario: --kind feature lands the promoted ADR in pre-release/
# features/adr_promote.feature:53
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.6.0
--kind feature --force" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the file
"design/adr/pre-release/ADR-0.6.0-sample-work/ADR-0.6.0-sample-work.md" exists
# features/steps/gz_steps.py:235

  @REQ-0.0.17-03-07
  Scenario: ledger artifact_renamed event records kind and semver
# features/adr_promote.feature:59
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a pool ADR "ADR-pool.sample-work" with target scope exists
# features/steps/gz_steps.py:282
    When I run the gz command "adr promote ADR-pool.sample-work --semver 0.6.0
--kind feature --force" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And ledger event "artifact_renamed" has field "kind" equal to "feature"
# features/steps/gz_steps.py:266
    And ledger event "artifact_renamed" has field "semver" equal to "0.6.0"
# features/steps/gz_steps.py:266
    And ledger event "artifact_renamed" has field "reason" equal to
"pool_promotion"                   # features/steps/gz_steps.py:266
    And ledger event "artifact_renamed" has field "new_id" equal to
"ADR-0.6.0-sample-work"            # features/steps/gz_steps.py:266

Feature: Advisor verdict <-> proof binding validator (ADR-0.0.29 /
OBPI-0.0.29-08) # features/advisor_proof_binding.feature:1
  As a governance maintainer,
  I want gate-time enforcement of the verdict <-> proof binding,
  so that no advisor diagnosis lands in fixtures or ledger events without
  the grounded proof its archetype + doctrinal-frame claims.
  Defense-in-depth backstop. Model-layer enforcement (OBPI-01) and
  engine-layer enforcement (OBPI-02) prevent empty-proof diagnoses at runtime;
  this validator catches any that nonetheless reach gate-time.
  @REQ-0.0.29-08-02
  Scenario: empty-proof fixture fails the validator with named file  #
features/advisor_proof_binding.feature:12
    Given an advisor diagnosis fixture "empty.json" with empty proof #
features/steps/advisor_proof_binding_steps.py:53
    And a conforming advisor diagnosis schema                        #
features/steps/advisor_proof_binding_steps.py:96
    When I run the gz command "validate --advisor-proof-binding"     #
features/steps/gz_steps.py:209
    Then the command exits with code 1                               #
features/steps/gz_steps.py:220
    And the output contains "tests/fixtures/advisor/empty.json"      #
features/steps/gz_steps.py:225

  @REQ-0.0.29-08-03
  Scenario: ledger event citing empty-proof diagnosis fails with named event id
# features/advisor_proof_binding.feature:20
    Given an advisor diagnosis fixture "diag.json" with id "diag-bad" and empty
proof # features/steps/advisor_proof_binding_steps.py:63
    And an intrinsic-complexity-attestation event "ica-evt-42" cites "diag-bad"
# features/steps/advisor_proof_binding_steps.py:77
    And a conforming advisor diagnosis schema
# features/steps/advisor_proof_binding_steps.py:96
    When I run the gz command "validate --advisor-proof-binding"
# features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "ica-evt-42"
# features/steps/gz_steps.py:225

Feature: Advisor timeout primitive — fail-open on timeout (ADR-0.0.29 /
OBPI-0.0.29-09) # features/advisor_timeout.feature:1
  As an operator whose pre-commit hook invokes the complexity advisor,
  I want the advisor to time out gracefully and never block my commit,
  so that a hanging analysis produces a logged warning instead of a wedged
terminal.
  @REQ-0.0.29-09-02
  Scenario: callable exceeds timeout and returns TimeoutTimedOut with elapsed_s
# features/advisor_timeout.feature:7
    Given a synthetic timeout environment with a slow callable exceeding 0.2s
timeout # features/steps/advisor_timeout_steps.py:13
    When I invoke run_with_timeout using default context
# features/steps/advisor_timeout_steps.py:26
    Then the result is a TimeoutTimedOut with elapsed_s > 0
# features/steps/advisor_timeout_steps.py:50
    And the result callable_name is "slow_callable"
# features/steps/advisor_timeout_steps.py:60

  @REQ-0.0.29-09-03
  Scenario: timeout logs failure entry to advisor-failures.jsonl
# features/advisor_timeout.feature:14
    Given a synthetic timeout environment with a slow callable exceeding 0.2s
timeout # features/steps/advisor_timeout_steps.py:13
    When I invoke run_with_timeout using auto-chain context
# features/steps/advisor_timeout_steps.py:37
    Then the log file contains a valid JSONL entry
# features/steps/advisor_timeout_steps.py:65
    And the log entry has callable_name "slow_callable"
# features/steps/advisor_timeout_steps.py:77
    And the log entry context invocation is "auto-chain"
# features/steps/advisor_timeout_steps.py:82
    And the log entry context file_paths is a list
# features/steps/advisor_timeout_steps.py:87

Feature: gz agent sync control-surfaces dual-direction propagation #
features/agent_sync.feature:1
  gz agent sync control-surfaces propagates .gzkit/<surface>/ to both
  src/gzkit/<surface>/ (wheel-shipping pkg copy) and ./<surface>/
  (vendor mirrors) in a single invocation (OBPI-0.0.32-08).
  @REQ-0.0.32-08-01
  Scenario: Sync propagates canonical skills to vendor mirrors  #
features/agent_sync.feature:7
    Given the workspace is initialized with agent surfaces      #
features/steps/gz_steps.py:79
    When I run the gz command "agent sync control-surfaces"     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    And the file ".claude/skills/gz-prd/SKILL.md" exists        #
features/steps/gz_steps.py:235

  @REQ-0.0.32-08-08
  Scenario: Post-sync validate --surfaces reports clean     #
features/agent_sync.feature:14
    Given the workspace is initialized with agent surfaces  #
features/steps/gz_steps.py:79
    When I run the gz command "agent sync control-surfaces" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                      #
features/steps/gz_steps.py:220
    When I run the gz command "validate --surfaces"         #
features/steps/gz_steps.py:209
    Then the command exits with code 0                      #
features/steps/gz_steps.py:220

  @REQ-0.0.32-08-04
  Scenario: Re-running sync on freshly-synced state is a no-op  #
features/agent_sync.feature:22
    Given the workspace is initialized with agent surfaces      #
features/steps/gz_steps.py:79
    When I run the gz command "agent sync control-surfaces"     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    When I run the gz command "agent sync control-surfaces"     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    And the output contains "Sync complete"                     #
features/steps/gz_steps.py:225

Feature: gz validate --agents-md-map-conformance external CLI surface
(OBPI-0.0.54-03) # features/agents_md_map_conformance.feature:1
  As a governance maintainer
  I want gz validate --agents-md-map-conformance and gz check to expose the
  map-not-encyclopedia validator as a registered, invokable CLI surface
  So that operator-facing enforcement of ADR-0.0.54 doctrine is a real verb,
  not just an internal audit function.
  Feature: gz validate --agents-md-map-conformance external CLI surface
(OBPI-0.0.54-03)  # features/agents_md_map_conformance.feature:1

  @REQ-0.0.54-03-02
  Scenario: gz validate --agents-md-map-conformance is a registered CLI scope
and runs cleanly on an initialized workspace  #
features/agents_md_map_conformance.feature:12
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run the gz command "validate --agents-md-map-conformance"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "agents_md_map_conformance"
# features/steps/gz_steps.py:225

  @REQ-0.0.54-03-04
  Scenario: gz check help surfaces the validator step name to operators  #
features/agents_md_map_conformance.feature:18
    Given the workspace is initialized                                   #
features/steps/gz_steps.py:67
    When I run the gz command "check --help"                             #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                   #
features/steps/gz_steps.py:220
    And the output contains "lint, format, typecheck, test"              #
features/steps/gz_steps.py:225

Feature: ARB self-reporting middleware # features/arb.feature:2
  As an agent producing verification evidence for Heavy-lane attestations
  I want a CLI surface that wraps QA commands and emits validated receipts
  So that attestation-enrichment claims can cite deterministic artifacts.
  @REQ-0.25.0-33-05
  Scenario: arb surface exposes every rule-declared verb  #
features/arb.feature:8
    When I run the gz command "arb --help"                #
features/steps/gz_steps.py:209
    Then the command exits with code 0                    #
features/steps/gz_steps.py:220
    And the output contains "ruff"                        #
features/steps/gz_steps.py:225
    And the output contains "step"                        #
features/steps/gz_steps.py:225
    And the output contains "ty"                          #
features/steps/gz_steps.py:225
    And the output contains "coverage"                    #
features/steps/gz_steps.py:225
    And the output contains "validate"                    #
features/steps/gz_steps.py:225
    And the output contains "advise"                      #
features/steps/gz_steps.py:225
    And the output contains "patterns"                    #
features/steps/gz_steps.py:225

  Scenario: arb validate returns zero-state cleanly on empty receipts dir  #
features/arb.feature:19
    When I run the gz command "arb validate --limit 5"                     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                     #
features/steps/gz_steps.py:220
    And the output contains "ARB Receipt Validation"                       #
features/steps/gz_steps.py:225
    And the output contains "Receipts scanned"                             #
features/steps/gz_steps.py:225

  Scenario: arb advise returns zero-state cleanly on empty receipts dir  #
features/arb.feature:25
    When I run the gz command "arb advise --limit 5"                     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                   #
features/steps/gz_steps.py:220
    And the output contains "ARB Advice"                                 #
features/steps/gz_steps.py:225
    And the output contains "Recommendations"                            #
features/steps/gz_steps.py:225
    And the output contains "No findings in recent receipts"             #
features/steps/gz_steps.py:225

  Scenario: arb patterns returns zero-state cleanly on empty receipts dir  #
features/arb.feature:32
    When I run the gz command "arb patterns --limit 5"                     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                     #
features/steps/gz_steps.py:220
    And the output contains "ARB Pattern Extraction Report"                #
features/steps/gz_steps.py:225

  Scenario: arb patterns compact mode emits single-line summary  #
features/arb.feature:37
    When I run the gz command "arb patterns --compact --limit 5" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                           #
features/steps/gz_steps.py:220
    And the output contains "arb patterns:"                      #
features/steps/gz_steps.py:225

  Scenario: arb validate JSON output is machine-readable      #
features/arb.feature:42
    When I run the gz command "arb validate --json --limit 5" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                        #
features/steps/gz_steps.py:220
    And the output contains "scanned"                         #
features/steps/gz_steps.py:225
    And the output contains "valid"                           #
features/steps/gz_steps.py:225
    And the output contains "unknown_schema"                  #
features/steps/gz_steps.py:225

Feature: Attestation receipt-binding gate (ADR-0.0.24) #
features/attestation_receipt_binding.feature:15
  As a governance maintainer relying on ARB receipts as proof of attestation
  I want gz validate --attestation-receipts and the gz obpi complete /
  gz adr emit-receipt gates to mechanically verify cited ARB receipts
  So that fabricated receipt IDs cannot pass for real evidence on heavy or
  foundation work, and lite-non-foundation work warns transparently.
  @REQ-0.0.24-01-01
  Scenario: Valid receipt resolves on heavy lane
# features/attestation_receipt_binding.feature:27
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And an arb step receipt
"arb-step-typecheck-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" with exit_status 0 exists
# features/steps/attestation_receipt_binding_steps.py:351
    When I run "gz validate --attestation-receipts 'typecheck clean (typecheck:
receipt arb-step-typecheck-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)' --lane heavy
--kind feature" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the output contains "resolved"
# features/steps/gz_steps.py:225

  @REQ-0.0.24-01-02
  Scenario: Missing receipt fails closed on heavy lane
# features/attestation_receipt_binding.feature:35
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I run "gz validate --attestation-receipts 'lint clean (lint: receipt
arb-ruff-deadbeef00000000000000000000beef)' --lane heavy --kind feature" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "no receipt file at"
# features/steps/gz_steps.py:225

  @REQ-0.0.24-01-03
  Scenario: Status mismatch fails closed
# features/attestation_receipt_binding.feature:43
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And an arb step receipt "arb-step-unittest-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
with exit_status 1 exists
# features/steps/attestation_receipt_binding_steps.py:351
    When I run "gz validate --attestation-receipts 'unittest fixtures (unittest:
receipt arb-step-unittest-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)' --lane heavy --kind
feature" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "exit_status=1"
# features/steps/gz_steps.py:225

  @REQ-0.0.24-01-04
  Scenario: Claim category mismatch fails closed
# features/attestation_receipt_binding.feature:51
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And an arb step receipt
"arb-step-typecheck-cccccccccccccccccccccccccccccccc" with exit_status 0 exists
# features/steps/attestation_receipt_binding_steps.py:351
    When I run "gz validate --attestation-receipts 'lint clean (lint: receipt
arb-step-typecheck-cccccccccccccccccccccccccccccccc)' --lane heavy --kind
feature" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "cited 'lint' but receipt is 'typecheck'"
# features/steps/gz_steps.py:225

  @REQ-0.0.24-01-05
  Scenario: Malformed receipt id is reported
# features/attestation_receipt_binding.feature:59
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I run "gz validate --attestation-receipts 'lint clean (lint: receipt
arb-ruff-ZZZZ-not-canonical)' --lane heavy --kind feature" #
features/steps/obpi_lock_steps.py:67
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And the output contains "malformed"
# features/steps/gz_steps.py:225

  @REQ-0.0.24-01-06
  Scenario: Zero receipts on heavy lane fails closed
# features/attestation_receipt_binding.feature:67
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I run "gz validate --attestation-receipts 'narrative-only attestation'
--lane heavy --kind feature" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.24-01-06
  Scenario: Zero receipts on lite-non-foundation passes warn-only
# features/attestation_receipt_binding.feature:74
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I run "gz validate --attestation-receipts 'narrative-only attestation'
--lane lite --kind feature" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.24-02-01
  Scenario: Heavy lane completion with valid receipt records meta-receipt-bind
# features/attestation_receipt_binding.feature:85
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-lane brief "OBPI-0.99.0-01-fixture" under feature ADR
"ADR-0.99.0-fixture" exists on disk
# features/steps/attestation_receipt_binding_steps.py:371
    And an arb step receipt "arb-step-unittest-dddddddddddddddddddddddddddddddd"
with exit_status 0 exists                                            #
features/steps/attestation_receipt_binding_steps.py:351
    And a pipeline marker for "OBPI-0.99.0-01-fixture" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete OBPI "OBPI-0.99.0-01-fixture" with attestation citing
"arb-step-unittest-dddddddddddddddddddddddddddddddd" using attestor-present #
features/steps/attestation_receipt_binding_steps.py:419
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the ledger contains an event with field "event" equal to
"audit_receipt_emitted" whose extra.receipt_event is "meta-receipt-bind"
# features/steps/attestation_receipt_binding_steps.py:479
    And the ledger contains an event with field "event" equal to
"obpi_receipt_emitted"
# features/steps/attestation_receipt_binding_steps.py:502

  @REQ-0.0.24-02-02 @REQ-0.0.24-04-03
  Scenario: Heavy lane completion with missing receipt fails closed and writes
no completion event                                           #
features/attestation_receipt_binding.feature:97
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-lane brief "OBPI-0.99.0-02-fixture" under feature ADR
"ADR-0.99.0-fixture-b" exists on disk                                  #
features/steps/attestation_receipt_binding_steps.py:371
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I complete OBPI "OBPI-0.99.0-02-fixture" with attestation citing
"arb-ruff-deadbeef00000000000000000000beef" using attestor-present #
features/steps/attestation_receipt_binding_steps.py:419
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the ledger does not contain an event for "OBPI-0.99.0-02-fixture" with
receipt_event "completed"                                     #
features/steps/attestation_receipt_binding_steps.py:537

  @REQ-0.0.24-02-03
  Scenario: Lite non-foundation completion with missing receipt warns and
proceeds                                                             #
features/attestation_receipt_binding.feature:106
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a lite-feature brief "OBPI-0.99.0-03-fixture" under feature ADR
"ADR-0.99.0-fixture-c" exists on disk                                  #
features/steps/attestation_receipt_binding_steps.py:376
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I complete OBPI "OBPI-0.99.0-03-fixture" with attestation citing
"arb-ruff-deadbeef11111111111111111111beef" without attestor-present #
features/steps/attestation_receipt_binding_steps.py:438
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the ledger contains an event for "OBPI-0.99.0-03-fixture" with
receipt_event "completed"                                               #
features/steps/attestation_receipt_binding_steps.py:514

  @REQ-0.0.24-02-04
  Scenario: Foundation kind lite lane completion with missing receipt fails
closed                                                           #
features/attestation_receipt_binding.feature:115
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a lite-foundation brief "OBPI-0.0.99-04-fixture" under foundation ADR
"ADR-0.0.99-fixture-d" exists on disk                          #
features/steps/attestation_receipt_binding_steps.py:381
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I complete OBPI "OBPI-0.0.99-04-fixture" with attestation citing
"arb-ruff-deadbeef22222222222222222222beef" using attestor-present #
features/steps/attestation_receipt_binding_steps.py:419
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the ledger does not contain an event for "OBPI-0.0.99-04-fixture" with
receipt_event "completed"                                     #
features/steps/attestation_receipt_binding_steps.py:537

  @REQ-0.0.24-02-05
  Scenario: Heavy ADR emit-receipt validated with missing receipt fails closed
# features/attestation_receipt_binding.feature:124
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy feature ADR "ADR-0.99.0-fixture-e" exists on disk
# features/steps/attestation_receipt_binding_steps.py:386
    And no arb receipts exist in the receipts root
# features/steps/attestation_receipt_binding_steps.py:363
    When I emit ADR receipt for "ADR-0.99.0-fixture-e" event "validated"
attestor "BDD User" attestation citing
"arb-ruff-deadbeef33333333333333333333beef" #
features/steps/attestation_receipt_binding_steps.py:456
    Then the command exits non-zero
# features/steps/gz_steps.py:215

  @REQ-0.0.24-04-01 @REQ-0.0.24-04-02
  Scenario: behave-req-tags validator passes when scenarios cover all REQs  #
features/attestation_receipt_binding.feature:137
    Given the workspace is the live repository                              #
features/steps/attestation_receipt_binding_steps.py:396
    When I run the gz command "validate --behave-req-tags"                  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                      #
features/steps/gz_steps.py:220

Feature: Complexity Guide Protocol # features/authoring_guide_protocol.feature:1
  The gzkit complexity guide protocol server implements a JSON-over-stdio
  LSP-style protocol for editor/IDE integration. Editor authors consume
  this contract to surface authoring-time complexity hints inline.
  @REQ-0.0.30-04-01 @REQ-0.0.30-04-02 @REQ-0.0.30-04-05 @REQ-0.0.30-04-07
  Scenario: Editor client completes handshake, analyze, and shutdown
# features/authoring_guide_protocol.feature:10
    Given a complexity guide protocol server is started
# features/steps/authoring_guide_protocol_steps.py:147
    When a client sends an initialize request with version "1.0"
# features/steps/authoring_guide_protocol_steps.py:155
    Then the server responds with protocol version "1.0" and supported
capabilities # features/steps/authoring_guide_protocol_steps.py:204
    When a client sends an analyze request for a Python fixture file
# features/steps/authoring_guide_protocol_steps.py:164
    Then the server responds with a hints list
# features/steps/authoring_guide_protocol_steps.py:220
    When a client sends a shutdown request
# features/steps/authoring_guide_protocol_steps.py:183
    Then the server exits cleanly
# features/steps/authoring_guide_protocol_steps.py:234

  @REQ-0.0.30-04-03
  Scenario: Analyze on a clean file returns an empty hints list            #
features/authoring_guide_protocol.feature:20
    Given a complexity guide protocol server is started                    #
features/steps/authoring_guide_protocol_steps.py:147
    When a client sends an analyze request for a clean Python fixture file #
features/steps/authoring_guide_protocol_steps.py:243
    Then the server responds with an empty hints list                      #
features/steps/authoring_guide_protocol_steps.py:262
    When a client sends a shutdown request                                 #
features/steps/authoring_guide_protocol_steps.py:183
    Then the server exits cleanly                                          #
features/steps/authoring_guide_protocol_steps.py:234

  @REQ-0.0.30-04-04
  Scenario: Malformed request envelope produces a named parse error  #
features/authoring_guide_protocol.feature:28
    Given a complexity guide protocol server is started              #
features/steps/authoring_guide_protocol_steps.py:147
    When a client sends a malformed envelope                         #
features/steps/authoring_guide_protocol_steps.py:272
    Then the server responds with parse error code -32700            #
features/steps/authoring_guide_protocol_steps.py:288

  @REQ-0.0.30-04-06
  Scenario: Major version mismatch produces a named version-mismatch error  #
features/authoring_guide_protocol.feature:34
    Given a complexity guide protocol server is started                     #
features/steps/authoring_guide_protocol_steps.py:147
    When a client sends an initialize request with version "99.0"           #
features/steps/authoring_guide_protocol_steps.py:155
    Then the server responds with version mismatch error code -32099        #
features/steps/authoring_guide_protocol_steps.py:298

Feature: OBPI Brief Reconciliation Engine (CIC-2) #
features/brief_reconcile.feature:5
  As a gzkit operator
  I want the reconciliation engine to detect drift between OBPI briefs and
project reality
  So that implementation begins and completes only when the brief matches
current project shape
  @wip @REQ-0.0.37-05-01
  Scenario: reconcile_brief returns a ReconcileResult with all five delta fields
# features/brief_reconcile.feature:14
    Given an OBPI brief file at "tests/fixtures/brief_reconcile/passing.md"
# None
    When I run reconcile_brief against the project root
# None
    Then the result is a ReconcileResult with brief_id
"OBPI-0.0.37-05-brief-reconcile-engine" # None
    And the result has an allowlist_delta field
# None
    And the result has a discovery_delta field
# None
    And the result has a verification_delta field
# None
    And the result has a req_count_delta field
# None
    And the result has a citation_delta field
# None
    And has_drift is False
# None

  @wip @REQ-0.0.37-05-02
  Scenario: allowlist dimension reports missing_on_disk for a non-existent
allowlisted path        # features/brief_reconcile.feature:29
    Given an OBPI brief that allowlists
"src/gzkit/governance/nonexistent_module.py"               # None
    When I run reconcile_brief against the project root
# None
    Then the allowlist_delta.missing_on_disk contains
"src/gzkit/governance/nonexistent_module.py" # None
    And has_drift is True
# None

  @wip @REQ-0.0.37-05-02
  Scenario: allowlist dimension reports missing_in_brief for a sibling module
imported by REQ tests  # features/brief_reconcile.feature:37
    Given an OBPI brief whose allowlist contains "src/gzkit/foo/a.py"
# None
    And a REQ test imports a sibling module "src/gzkit/foo/b.py" absent from the
allowlist           # None
    When I run reconcile_brief against the project root
# None
    Then the allowlist_delta.missing_in_brief contains "src/gzkit/foo/b.py"
# None
    And has_drift is True
# None

  @wip @REQ-0.0.37-05-02
  Scenario: missing_in_brief neighborhood filter excludes cross-cutting utility
imports    # features/brief_reconcile.feature:46
    Given an OBPI brief whose allowlist contains "src/gzkit/foo/a.py"
# None
    And a REQ test imports the cross-cutting utility "src/gzkit/traceability.py"
# None
    When I run reconcile_brief against the project root
# None
    Then the allowlist_delta.missing_in_brief does not contain
"src/gzkit/traceability.py" # None

  @wip @REQ-0.0.37-05-03
  Scenario: verification-verb dimension reports an unregistered gz verb
# features/brief_reconcile.feature:56
    Given an OBPI brief whose Verification section references an unregistered gz
verb # None
    When I run reconcile_brief against the project root
# None
    Then the verification_delta.unresolved_verbs contains that verb
# None
    And has_drift is True
# None

  @wip @REQ-0.0.37-05-03
  Scenario: a registered gz verb is not reported as unresolved
# features/brief_reconcile.feature:64
    Given an OBPI brief whose Verification section references a registered gz
verb # None
    When I run reconcile_brief against the project root
# None
    Then the verification_delta.unresolved_verbs does not contain that verb
# None

  @wip @REQ-0.0.37-05-04
  Scenario: req count dimension reports non-zero delta when REQ count does not
match acceptance criteria  # features/brief_reconcile.feature:73
    Given an OBPI brief with 2 REQUIREMENT lines and 1 acceptance criteria
checkbox                       # None
    When I run reconcile_brief against the project root
# None
    Then the req_count_delta.delta is non-zero
# None
    And has_drift is True
# None

  @wip @REQ-0.0.37-05-04
  Scenario: req count dimension reports zero delta when counts match
# features/brief_reconcile.feature:81
    Given an OBPI brief with 1 REQUIREMENT line and 1 acceptance criteria
checkbox # None
    When I run reconcile_brief against the project root
# None
    Then the req_count_delta.delta is 0
# None

  @wip @REQ-0.0.37-05-05
  Scenario: citation dimension reports stale citations for non-existent artifact
files              # features/brief_reconcile.feature:90
    Given an OBPI brief with a citation to "docs/does-not-exist-anywhere.md"
# None
    When I run reconcile_brief against the project root
# None
    Then the citation_delta.stale_citations contains a tuple with
"docs/does-not-exist-anywhere.md" # None
    And has_drift is True
# None

  @wip @REQ-0.0.37-05-05
  Scenario: citation dimension does not report citations whose artifact file
exists  # features/brief_reconcile.feature:98
    Given an OBPI brief with a citation to an artifact file that exists on disk
# None
    When I run reconcile_brief against the project root
# None
    Then the citation_delta.stale_citations is empty
# None

  @wip @REQ-0.0.37-05-06
  Scenario: gz validate --brief-reconcile exits 0 when no structured brief has
drift  # features/brief_reconcile.feature:107
    Given no structured OBPI brief in the corpus has drift
# None
    When I run the gz command "validate --brief-reconcile"
# None
    Then the exit code is 0
# None

  @wip @REQ-0.0.37-05-06
  Scenario: gz validate --brief-reconcile exits 3 when a structured brief has
drift  # features/brief_reconcile.feature:114
    Given a structured OBPI brief in the corpus has drift in at least one
dimension  # None
    When I run the gz command "validate --brief-reconcile"
# None
    Then the exit code is 3
# None

  @wip @REQ-0.0.37-05-06
  Scenario: gz validate --brief-reconcile does not escalate drift on legacy
briefs  # features/brief_reconcile.feature:121
    Given a legacy OBPI brief in the corpus has drift but no structured brief
does  # None
    When I run the gz command "validate --brief-reconcile"
# None
    Then the exit code is 0
# None

  @wip @REQ-0.0.37-05-06
  Scenario: gz validate --brief-reconcile reports which brief has drift  #
features/brief_reconcile.feature:128
    Given a structured OBPI brief in the corpus has drift                # None
    When I run the gz command "validate --brief-reconcile"               # None
    Then the output contains the brief ID                                # None
    And the output contains which dimension has drift                    # None

  @wip @REQ-0.0.37-05-07
  Scenario: the reconciliation engine writes no files                       #
features/brief_reconcile.feature:138
    Given an OBPI brief file at "tests/fixtures/brief_reconcile/passing.md" #
None
    When I run reconcile_brief against the project root                     #
None
    Then no files in the project are written or modified                    #
None

  @wip @REQ-0.0.37-05-07
  Scenario: the reconciliation engine emits no ledger events              #
features/brief_reconcile.feature:145
    Given the current ledger event count                                  # None
    And an OBPI brief file at "tests/fixtures/brief_reconcile/passing.md" # None
    When I run reconcile_brief against the project root                   # None
    Then the ledger event count is unchanged                              # None

  @wip @REQ-0.0.37-06-01
  Scenario: gz brief reconcile reports a clean brief and exits zero     #
features/brief_reconcile.feature:159
    Given a clean OBPI brief resolvable by id                           # None
    When I run "gz brief reconcile <OBPI-ID>"                           # None
    Then the command exits 0                                            # None
    And a brief_reconciled ledger event is emitted with has_drift false # None

  @wip @REQ-0.0.37-06-02
  Scenario: gz brief reconcile reports drift and exits three
# features/brief_reconcile.feature:167
    Given an OBPI brief whose allowlist names a non-existent path
# None
    When I run "gz brief reconcile <OBPI-ID>"
# None
    Then the command exits 3
# None
    And a brief_reconcile_drift_detected ledger event is emitted with the
per-dimension payload # None

  @wip @REQ-0.0.37-06-03
  Scenario: gz brief reconcile --apply requires --attestor  #
features/brief_reconcile.feature:175
    Given an OBPI brief resolvable by id                    # None
    When I run "gz brief reconcile <OBPI-ID> --apply"       # None
    Then the command exits non-zero                         # None
    And the error names "--apply requires --attestor"       # None

  @wip @REQ-0.0.37-06-04
  Scenario: gz brief reconcile --apply --attestor writes attested amendments
# features/brief_reconcile.feature:183
    Given a drifting OBPI brief resolvable by id
# None
    When I run "gz brief reconcile <OBPI-ID> --apply --attestor \"Jane Doe\""
# None
    Then the brief gains the reconciliation amendments
# None
    And a brief_reconciled ledger event is emitted with applied true and the
attestor name # None

  @wip @REQ-0.0.37-06-05
  Scenario: gz brief reconcile --apply --dry-run previews without writing
# features/brief_reconcile.feature:191
    Given a drifting OBPI brief resolvable by id
# None
    When I run "gz brief reconcile <OBPI-ID> --apply --attestor \"Jane Doe\"
--dry-run" # None
    Then the brief file is unchanged
# None
    And no applied brief_reconciled ledger event is emitted
# None

  @wip @REQ-0.0.37-06-06
  Scenario: the brief reconcile verb is registered  #
features/brief_reconcile.feature:199
    When I run "gz brief reconcile --help"          # None
    Then the command exits 0                        # None

  @wip @REQ-0.0.37-06-07
  Scenario: both reconciliation event types are schema-registered            #
features/brief_reconcile.feature:205
    Given the ledger event schema                                            #
None
    Then it declares "brief_reconciled" and "brief_reconcile_drift_detected" #
None

  @wip @REQ-0.0.37-06-08
  Scenario: the brief reconcile manpage exists with required sections
# features/brief_reconcile.feature:211
    Given the manpage "docs/user/manpages/brief-reconcile.md"
# None
    Then it contains NAME, SYNOPSIS, DESCRIPTION, OPTIONS, and EXAMPLES sections
# None

  @wip @REQ-0.0.37-07-02
  Scenario: Stage 1 blocks when no brief_reconciled receipt exists
# features/brief_reconcile.feature:221
    Given an OBPI with no brief_reconciled ledger event
# None
    When I launch "gz obpi pipeline <OBPI-ID>"
# None
    Then the command exits 3
# None
    And the output contains "Stage 2 entry blocked: no `brief_reconciled`
receipt" # None

  @wip @REQ-0.0.37-07-03
  Scenario: Stage 1 blocks when brief_reconciled receipt is stale
# features/brief_reconcile.feature:229
    Given an OBPI with a brief_reconciled receipt older than its allowed-path
mtimes # None
    When I launch "gz obpi pipeline <OBPI-ID>"
# None
    Then the command exits 3
# None
    And the output contains "Stage 2 entry blocked"
# None
    And the output contains "stale"
# None
    And the output contains "drifted path"
# None

  @wip @REQ-0.0.37-07-04
  Scenario: Stage 1 blocks when receipt has_drift is True
# features/brief_reconcile.feature:239
    Given an OBPI with a fresh brief_reconciled receipt whose has_drift is True
# None
    When I launch "gz obpi pipeline <OBPI-ID>"
# None
    Then the command exits 3
# None
    And the output contains "Stage 2 entry blocked"
# None
    And the output contains "has_drift=True"
# None

  @wip @REQ-0.0.37-07-05
  Scenario: Stage 1 passes when receipt is fresh and drift-free
# features/brief_reconcile.feature:248
    Given an OBPI with a fresh brief_reconciled receipt whose has_drift is False
# None
    When I launch "gz obpi pipeline <OBPI-ID>"
# None
    Then Stage 1 passes and Stage 2 is permitted
# None

  @wip @REQ-0.0.37-08-01
  Scenario: Stage 5 blocks when no brief_reconciled receipt exists
# features/brief_reconcile.feature:255
    Given an OBPI with no brief_reconciled ledger event
# None
    When I run "gz obpi complete <OBPI-ID> --attestor <attestor>
--attestation-text <text>" # None
    Then the command exits 3
# None
    And the output contains "Completion blocked: no `brief_reconciled` receipt"
# None
    And the output contains "gz brief reconcile"
# None

  @wip @REQ-0.0.37-08-02
  Scenario: Stage 5 blocks when brief_reconciled receipt is stale
# features/brief_reconcile.feature:264
    Given an OBPI with a brief_reconciled receipt older than its allowed-path
mtimes        # None
    When I run "gz obpi complete <OBPI-ID> --attestor <attestor>
--attestation-text <text>" # None
    Then the command exits 3
# None
    And the output contains "Completion blocked"
# None
    And the output contains "stale"
# None

  @wip @REQ-0.0.37-08-03
  Scenario: Stage 5 blocks when receipt has_drift is True
# features/brief_reconcile.feature:273
    Given an OBPI with a fresh brief_reconciled receipt whose has_drift is True
# None
    When I run "gz obpi complete <OBPI-ID> --attestor <attestor>
--attestation-text <text>" # None
    Then the command exits 3
# None
    And the output contains "has_drift=True"
# None

  @wip @REQ-0.0.37-08-04
  Scenario: Stage 5 passes when receipt is fresh and drift-free
# features/brief_reconcile.feature:281
    Given an OBPI with a fresh brief_reconciled receipt whose has_drift is False
# None
    When I run "gz obpi complete <OBPI-ID> --attestor <attestor>
--attestation-text <text>" # None
    Then Stage 5 completes successfully
# None

  @wip @REQ-0.0.37-08-04
  Scenario: --accept-stale-reconciliation without --reason exits with error  #
features/brief_reconcile.feature:288
    Given any OBPI                                                           #
None
    When I run "gz obpi complete <OBPI-ID> --accept-stale-reconciliation"    #
None
    Then the command exits with a non-zero code                              #
None
    And the output contains "--accept-stale-reconciliation"                  #
None
    And the output contains "--reason"                                       #
None

  @wip @REQ-0.0.37-08-05
  Scenario: --accept-stale-reconciliation with valid reason emits override event
and completes              # features/brief_reconcile.feature:297
    Given an OBPI with no brief_reconciled receipt
# None
    When I run "gz obpi complete <OBPI-ID> --accept-stale-reconciliation
--reason 'emergency fix approved'" # None
    Then the command exits 0
# None
    And a brief_reconcile_drift_overridden event is emitted to the ledger before
the completion receipt     # None

  @wip @REQ-0.0.37-08-06
  Scenario: Escape hatch works for heavy-lane foundation-kind OBPI
# features/brief_reconcile.feature:305
    Given a heavy-lane foundation-kind OBPI with no brief_reconciled receipt
# None
    When I run "gz obpi complete <OBPI-ID> --accept-stale-reconciliation
--reason 'emergency fix approved'" # None
    Then the command exits 0 regardless of lane or kind
# None

Feature: Advisory drift detection in gz check #
features/check_drift_advisory.feature:1
  The gz check command includes drift detection as an advisory (non-blocking)
  check that runs after all blocking quality checks complete.
  Scenario: Check help shows json flag       #
features/check_drift_advisory.feature:5
    When I run the gz command "check --help" # features/steps/gz_steps.py:209
    Then the command exits with code 0       # features/steps/gz_steps.py:220
    And the output contains "--json"         # features/steps/gz_steps.py:225
    And the output contains "advisory drift" # features/steps/gz_steps.py:225

Feature: Chores distribution end-to-end # features/chores_distribution.feature:1
  Heavy-lane Gate 4 proof for ADR-0.0.21 (chores as gzkit surface).
  Exercises the install -> scaffold -> list -> repair lifecycle against the
  real installed gzkit package (editable install in .venv resolves
  importlib.resources('gzkit.chores') to the canonical source tree).
  @REQ-0.0.21-07-01 @REQ-0.0.21-07-02 @REQ-0.0.21-07-06 @REQ-0.0.21-07-07
  Scenario: Package fallback works without gz init     #
features/chores_distribution.feature:11
    Given a fresh empty project directory              #
features/steps/chores_distribution_steps.py:62
    When I run "gz chores list" as a subprocess        #
features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0              #
features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "quality-check" #
features/steps/chores_distribution_steps.py:113
    And the subprocess output contains "pool-triage"   #
features/steps/chores_distribution_steps.py:113

  @REQ-0.0.21-07-03
  Scenario: gz init populates project chores and --explain reports project
source  # features/chores_distribution.feature:19
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    When I run "gz init --no-skeleton" as a subprocess
# features/steps/chores_distribution_steps.py:98
    And I run "gz chores list --explain" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0
# features/steps/chores_distribution_steps.py:105
    And the file ".gzkit/chores/quality-check/CHORE.md" exists
# features/steps/gz_steps.py:235
    And every chore row in the subprocess output reports "project" source
# features/steps/chores_distribution_steps.py:120

  @REQ-0.0.21-07-04
  Scenario: Re-running gz init preserves operator edits to a chore
# features/chores_distribution.feature:28
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the operator edits ".gzkit/chores/quality-check/CHORE.md" with marker
"OPERATOR-EDIT-MARKER-XYZ" # features/steps/chores_distribution_steps.py:77
    When I run "gz init --no-skeleton" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0
# features/steps/chores_distribution_steps.py:105
    And the file ".gzkit/chores/quality-check/CHORE.md" contains
"OPERATOR-EDIT-MARKER-XYZ"              # features/steps/gz_steps.py:240

  @REQ-0.0.21-07-05
  Scenario: Merge diff fires for canonical-only slug and --yes writes the merge
# features/chores_distribution.feature:37
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the slug "quality-check" has been removed from
".gzkit/chores/registry.json" # features/steps/chores_distribution_steps.py:85
    When I run "gz init --no-skeleton --yes" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0
# features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "+ quality-check"
# features/steps/chores_distribution_steps.py:113
    And the registry ".gzkit/chores/registry.json" contains slug "quality-check"
# features/steps/chores_distribution_steps.py:135

Feature: Closeout ceremony enforcement # features/closeout_ceremony.feature:1
  The closeout ceremony presents a Defense Brief with closing arguments,
  product proof, and reviewer assessment. It blocks when evidence is missing.
  Scenario: Closeout dry-run shows Defense Brief section     #
features/closeout_ceremony.feature:5
    Given the workspace is initialized in heavy mode         #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                #
features/steps/closeout_product_proof_steps.py:51
    And the OBPI source file has public docstrings           #
features/steps/closeout_product_proof_steps.py:139
    And the OBPI brief has a closing argument                #
features/steps/closeout_ceremony_steps.py:12
    When I run the gz command "closeout ADR-0.1.0 --dry-run" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                       #
features/steps/gz_steps.py:220
    And the output contains "Defense Brief"                  #
features/steps/gz_steps.py:225
    And the output contains "Closing Arguments"              #
features/steps/gz_steps.py:225

  Scenario: Closeout form includes Defense Brief when rendered  #
features/closeout_ceremony.feature:15
    Given the workspace is initialized in heavy mode            #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                   #
features/steps/closeout_product_proof_steps.py:51
    And the OBPI source file has public docstrings              #
features/steps/closeout_product_proof_steps.py:139
    And the OBPI brief has a closing argument                   #
features/steps/closeout_ceremony_steps.py:12
    When I run the gz command "closeout ADR-0.1.0 --dry-run"    #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    And the output contains "Product Proof"                     #
features/steps/gz_steps.py:225

  Scenario: Defense Brief shows reviewer assessment when present  #
features/closeout_ceremony.feature:24
    Given the workspace is initialized in heavy mode              #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                     #
features/steps/closeout_product_proof_steps.py:51
    And the OBPI source file has public docstrings                #
features/steps/closeout_product_proof_steps.py:139
    And the OBPI brief has a closing argument                     #
features/steps/closeout_ceremony_steps.py:12
    And a reviewer assessment exists for the OBPI                 #
features/steps/closeout_ceremony_steps.py:35
    When I run the gz command "closeout ADR-0.1.0 --dry-run"      #
features/steps/gz_steps.py:209
    Then the command exits with code 0                            #
features/steps/gz_steps.py:220
    And the output contains "Reviewer Assessment"                 #
features/steps/gz_steps.py:225
    And the output contains "PASS"                                #
features/steps/gz_steps.py:225

Feature: Closeout product proof gate # features/closeout_product_proof.feature:1
  The closeout command validates that each OBPI has operator-facing
  documentation proof before allowing ADR closeout to proceed.
  Scenario: Closeout blocked when OBPI has no product proof  #
features/closeout_product_proof.feature:5
    Given the workspace is initialized in heavy mode         #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                #
features/steps/closeout_product_proof_steps.py:51
    When I run the gz command "closeout ADR-0.1.0 --dry-run" #
features/steps/gz_steps.py:209
    Then the command exits non-zero                          #
features/steps/gz_steps.py:215
    And the output contains "MISSING"                        #
features/steps/gz_steps.py:225
    And the output contains "missing product proof"          #
features/steps/gz_steps.py:225

  Scenario: Closeout allowed when OBPI has docstring proof   #
features/closeout_product_proof.feature:13
    Given the workspace is initialized in heavy mode         #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                #
features/steps/closeout_product_proof_steps.py:51
    And the OBPI source file has public docstrings           #
features/steps/closeout_product_proof_steps.py:139
    When I run the gz command "closeout ADR-0.1.0 --dry-run" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                       #
features/steps/gz_steps.py:220
    And the output contains "docstring"                      #
features/steps/gz_steps.py:225

  Scenario: Closeout product proof shown in JSON mode               #
features/closeout_product_proof.feature:21
    Given the workspace is initialized in heavy mode                #
features/steps/gz_steps.py:60
    And a heavy ADR exists with an OBPI brief                       #
features/steps/closeout_product_proof_steps.py:51
    And the OBPI source file has public docstrings                  #
features/steps/closeout_product_proof_steps.py:139
    When I run the gz command "closeout ADR-0.1.0 --dry-run --json" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                              #
features/steps/gz_steps.py:220
    And the output contains "product_proof"                         #
features/steps/gz_steps.py:225

Feature: gz complexity advise — trigger-time advisor diagnosis (ADR-0.0.29 /
OBPI-0.0.29-03) # features/complexity_advise.feature:1
  As an operator authoring or reviewing complexity-sensitive code,
  I want to preview the doctrinal advisor diagnosis for a file before commit,
  so that warn-band crossings surface a refactor archetype + authority +
  proof binding instead of an opaque numeric verdict, and block-band
  crossings fail the verb's exit status.
  @REQ-0.0.29-03-01
  Scenario: clean file produces exit 0 with no crossings
# features/complexity_advise.feature:9
    Given a synthetic complexity-advise environment with a clean Python source
# features/steps/complexity_advise_steps.py:153
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "No crossings"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-03-02
  Scenario: warn-band crossing produces exit 0 with diagnosis prose
# features/complexity_advise.feature:16
    Given a synthetic complexity-advise environment with a warn-band Python
source                  # features/steps/complexity_advise_steps.py:158
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Archetype"
# features/steps/gz_steps.py:225
    And the output contains "Authority"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-03-03
  Scenario: block-band crossing produces exit 3
# features/complexity_advise.feature:24
    Given a synthetic complexity-advise environment with a block-band Python
source                 # features/steps/complexity_advise_steps.py:163
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220

  @REQ-0.0.29-03-04
  Scenario: --json mode emits valid JSON validating against schema
# features/complexity_advise.feature:30
    Given a synthetic complexity-advise environment with a warn-band Python
source                         # features/steps/complexity_advise_steps.py:158
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json --json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "metric"
# features/steps/gz_steps.py:225
    And the output contains "radon_cc"
# features/steps/gz_steps.py:225
    And the output contains "crossing_band"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-03-05
  Scenario: --help exits 0 with description, options, example  #
features/complexity_advise.feature:39
    When I run the gz command "complexity advise --help"       #
features/steps/gz_steps.py:209
    Then the command exits with code 0                         #
features/steps/gz_steps.py:220
    And the output contains "usage"                            #
features/steps/gz_steps.py:225
    And the output contains "--json"                           #
features/steps/gz_steps.py:225
    And the output contains "--quiet"                          #
features/steps/gz_steps.py:225

  @REQ-0.0.29-03-06
  Scenario: --help describes purpose, exit codes, and examples  #
features/complexity_advise.feature:47
    When I run the gz command "complexity advise --help"        #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    And the output contains "Exit codes"                        #
features/steps/gz_steps.py:225
    And the output contains "Examples"                          #
features/steps/gz_steps.py:225

  @REQ-0.0.29-03-07
  Scenario: verb is registered with full surface (manpage + index + runbook
coverage)  # features/complexity_advise.feature:54
    When I run the gz command "complexity advise --help"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "complexity advise"
# features/steps/gz_steps.py:225
    And the output contains "ADR-0.0.28"
# features/steps/gz_steps.py:225

Feature: gz complexity advise — ad-hoc vs auto-chain presenter dispatch
(ADR-0.0.29 / OBPI-0.0.29-06) # features/complexity_advise_ad_hoc.feature:1
  As an operator using the complexity advisor ad-hoc or via auto-chain hook,
  I want the advisor to present output appropriate to the pathway:
  ad-hoc preview shows verbose diagnosis with full doctrinal frame,
  while auto-chain trigger-time shows a concise one-liner hint,
  so that operators get fast preview-before-fail guidance and hooks get compact,
parseable output.
  @REQ-0.0.29-06-01
  Scenario: ad-hoc preview against clean file shows "no crossings"
# features/complexity_advise_ad_hoc.feature:9
    Given a synthetic complexity-advise environment with a clean Python source
# features/steps/complexity_advise_steps.py:153
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "No crossings"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-06-02
  Scenario: ad-hoc preview against warn-band file shows archetype and authority
# features/complexity_advise_ad_hoc.feature:16
    Given a synthetic complexity-advise environment with a warn-band Python
source                  # features/steps/complexity_advise_steps.py:158
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Archetype"
# features/steps/gz_steps.py:225
    And the output contains "Authority"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-06-03
  Scenario: auto-chain against warn-band file produces concise one-liner
# features/complexity_advise_ad_hoc.feature:24
    Given a synthetic complexity-advise environment with a warn-band Python
source                               #
features/steps/complexity_advise_steps.py:158
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json --auto-chain" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "radon_cc"
# features/steps/gz_steps.py:225
    And the output does not contain "Doctrinal frame"
# features/steps/gz_steps.py:230

Feature: Complexity advisor auto-chain from xenon-as-gate failure (ADR-0.0.29 /
OBPI-0.0.29-05) # features/complexity_advisor_auto_chain.feature:1
  As a developer committing complexity-sensitive code,
  I want the pre-commit hook to run the advisor when xenon fails,
  so that warn-band crossings surface a diagnosis and block-band
  crossings prevent the commit.
  @REQ-0.0.29-05-01
  Scenario: clean commit — xenon passes, advisor not invoked  #
features/complexity_advisor_auto_chain.feature:8
    Given a git repo with the auto-chain hook installed       #
features/steps/complexity_advisor_auto_chain_steps.py:53
    And a staged Python file with no complexity crossings     #
features/steps/complexity_advisor_auto_chain_steps.py:63
    When the pre-commit hook runs                             #
features/steps/complexity_advisor_auto_chain_steps.py:85
    Then the hook exits with code 0                           #
features/steps/complexity_advisor_auto_chain_steps.py:108
    And the advisor is not invoked                            #
features/steps/complexity_advisor_auto_chain_steps.py:115

  @REQ-0.0.29-05-02 @REQ-0.0.29-05-05 @REQ-0.0.29-05-06
  Scenario: warn-band commit — xenon fails, advisor diagnoses warn crossing  #
features/complexity_advisor_auto_chain.feature:18
    Given a git repo with the auto-chain hook installed                      #
features/steps/complexity_advisor_auto_chain_steps.py:53
    And a staged Python file with a warn-band complexity crossing            #
features/steps/complexity_advisor_auto_chain_steps.py:68
    When the pre-commit hook runs                                            #
features/steps/complexity_advisor_auto_chain_steps.py:85
    Then the hook exits with code 0                                          #
features/steps/complexity_advisor_auto_chain_steps.py:108
    And stderr contains a diagnosis with "Archetype"                         #
features/steps/complexity_advisor_auto_chain_steps.py:122

  @REQ-0.0.29-05-04 @REQ-0.0.29-05-05 @REQ-0.0.29-05-06
  Scenario: block-band commit — xenon fails, advisor diagnoses block crossing  #
features/complexity_advisor_auto_chain.feature:28
    Given a git repo with the auto-chain hook installed                        #
features/steps/complexity_advisor_auto_chain_steps.py:53
    And a staged Python file with a block-band complexity crossing             #
features/steps/complexity_advisor_auto_chain_steps.py:74
    When the pre-commit hook runs                                              #
features/steps/complexity_advisor_auto_chain_steps.py:85
    Then the hook exits with code 1                                            #
features/steps/complexity_advisor_auto_chain_steps.py:108
    And stderr contains a diagnosis with "Archetype"                           #
features/steps/complexity_advisor_auto_chain_steps.py:122

  @REQ-0.0.29-05-03
  Scenario: SKIP-bypassed commit — neither xenon nor advisor runs  #
features/complexity_advisor_auto_chain.feature:36
    Given a git repo with the auto-chain hook installed            #
features/steps/complexity_advisor_auto_chain_steps.py:53
    And a staged Python file with a block-band complexity crossing #
features/steps/complexity_advisor_auto_chain_steps.py:74
    And the SKIP environment variable includes the hook id         #
features/steps/complexity_advisor_auto_chain_steps.py:80
    When the pre-commit hook runs with SKIP active                 #
features/steps/complexity_advisor_auto_chain_steps.py:95
    Then the hook exits with code 0                                #
features/steps/complexity_advisor_auto_chain_steps.py:108
    And the advisor is not invoked                                 #
features/steps/complexity_advisor_auto_chain_steps.py:115

Feature: gz validate --complexity-doctrine-links link integrity (OBPI-0.0.27-07)
# features/complexity_doctrine_links.feature:1
  As a governance maintainer
  I want gz validate --complexity-doctrine-links to fail-close on broken
citations
  So that operators following an advisor diagnosis at 2am never land on a
  missing or stale distilled-characteristics document.
  Feature: gz validate --complexity-doctrine-links link integrity
(OBPI-0.0.27-07)  # features/complexity_doctrine_links.feature:1

  @REQ-0.0.27-07-01
  Scenario: Well-formed citation resolves clean                      #
features/complexity_doctrine_links.feature:11
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    Given a complexity-doctrine fixture with a well-formed citation  #
features/steps/complexity_doctrine_links_steps.py:62
    When I run the gz command "validate --complexity-doctrine-links" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                               #
features/steps/gz_steps.py:220

  @REQ-0.0.27-07-02
  Scenario: Missing distilled-characteristics file fails closed       #
features/complexity_doctrine_links.feature:17
    Given the workspace is initialized                                #
features/steps/gz_steps.py:67
    Given a complexity-doctrine fixture with a missing distilled file #
features/steps/complexity_doctrine_links_steps.py:73
    When I run the gz command "validate --complexity-doctrine-links"  #
features/steps/gz_steps.py:209
    Then the command exits with code 3                                #
features/steps/gz_steps.py:220
    And the output contains "distilled-characteristics-1999-01-01.md" #
features/steps/gz_steps.py:225

  @REQ-0.0.27-07-03
  Scenario: Unresolved section anchor fails closed                   #
features/complexity_doctrine_links.feature:24
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    Given a complexity-doctrine fixture with an unresolved anchor    #
features/steps/complexity_doctrine_links_steps.py:84
    When I run the gz command "validate --complexity-doctrine-links" #
features/steps/gz_steps.py:209
    Then the command exits with code 3                               #
features/steps/gz_steps.py:220
    And the output contains "nonexistent-metric"                     #
features/steps/gz_steps.py:225

  @REQ-0.0.27-07-04
  Scenario: Non-portable corpus revision fails closed                       #
features/complexity_doctrine_links.feature:31
    Given the workspace is initialized                                      #
features/steps/gz_steps.py:67
    Given a complexity-doctrine fixture with a non-portable corpus revision #
features/steps/complexity_doctrine_links_steps.py:95
    When I run the gz command "validate --complexity-doctrine-links"        #
features/steps/gz_steps.py:209
    Then the command exits with code 3                                      #
features/steps/gz_steps.py:220
    And the output contains "doctrine-amendment-protocol"                   #
features/steps/gz_steps.py:225

  @REQ-0.0.27-07-05
  Scenario: Speculative-skip marker bypasses a forward-reference citation  #
features/complexity_doctrine_links.feature:38
    Given the workspace is initialized                                     #
features/steps/gz_steps.py:67
    Given a complexity-doctrine fixture with a speculative-skip marker     #
features/steps/complexity_doctrine_links_steps.py:108
    When I run the gz command "validate --complexity-doctrine-links"       #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                     #
features/steps/gz_steps.py:220

  @REQ-0.0.27-07-06
  Scenario: Validator integrates into gz validate --complexity-doctrine-links
direct invocation  # features/complexity_doctrine_links.feature:44
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run the gz command "validate --complexity-doctrine-links"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "complexity_doctrine_links"
# features/steps/gz_steps.py:225

  @REQ-0.0.27-07-07
  Scenario: Validate command doc documents the flag with example
# features/complexity_doctrine_links.feature:50
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I check that the file "docs/user/manpages/validate.md" contains
"--complexity-doctrine-links" #
features/steps/complexity_doctrine_links_steps.py:128
    Then the file is documented
# features/steps/complexity_doctrine_links_steps.py:142

Feature: gz complexity guide — authoring-time hint surface (ADR-0.0.30 /
OBPI-0.0.30-01) # features/complexity_guide.feature:1
  As an operator editing complexity-sensitive code,
  I want to preview authoring-time complexity hints on a file before committing,
  so that advise-band crossings surface a refactor archetype + guidance headline
  + recommended move before reaching gate time, without blocking the build.
  @REQ-0.0.30-01-01
  Scenario: Clean file produces exit 0 with no-hints message                  #
features/complexity_guide.feature:8
    Given a synthetic complexity-guide environment with a clean Python source #
features/steps/complexity_guide_steps.py:127
    When I run the gz command "complexity guide subject.py"                   #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                        #
features/steps/gz_steps.py:220
    And the output contains "No advise-band hints found"                      #
features/steps/gz_steps.py:225

  @REQ-0.0.30-01-02
  Scenario: File with advise-band crossings produces prose hint blocks
# features/complexity_guide.feature:15
    Given a synthetic complexity-guide environment with an advise-band Python
source # features/steps/complexity_guide_steps.py:132
    When I run the gz command "complexity guide subject.py"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Archetype"
# features/steps/gz_steps.py:225
    And the output contains "Move"
# features/steps/gz_steps.py:225

  @REQ-0.0.30-01-03
  Scenario: --json mode produces valid JSON with AuthoringHint fields
# features/complexity_guide.feature:23
    Given a synthetic complexity-guide environment with an advise-band Python
source # features/steps/complexity_guide_steps.py:132
    When I run the gz command "complexity guide subject.py --json"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "precedence_band"
# features/steps/gz_steps.py:225
    And the output contains "recommended_move"
# features/steps/gz_steps.py:225
    And the output contains "archetype"
# features/steps/gz_steps.py:225

  @REQ-0.0.30-01-04 @REQ-0.0.30-01-05
  Scenario: --help exits 0 with standard sections (and exit 3 is never produced)
# features/complexity_guide.feature:33
    When I run the gz command "complexity guide --help"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "usage"
# features/steps/gz_steps.py:225
    And the output contains "options"
# features/steps/gz_steps.py:225

Feature: gz validate --complexity-thresholds rule body shape (OBPI-0.0.28-03) #
features/complexity_thresholds.feature:1
  As a governance maintainer
  I want gz validate --complexity-thresholds to fail-close on malformed
threshold tables
  So that downstream consumers (advisor, authoring-guidance, xenon-as-gate)
never
  bind against a rule body the loader cannot parse, and the bootstrap-mode
  carve-out surfaces visibly to the operator without silently passing the gate.
  Feature: gz validate --complexity-thresholds rule body shape (OBPI-0.0.28-03)
# features/complexity_thresholds.feature:1

  @REQ-0.0.28-03-01
  Scenario: Well-formed rule body validates clean                #
features/complexity_thresholds.feature:12
    Given the workspace is initialized                           #
features/steps/gz_steps.py:67
    Given a well-formed complexity-thresholds fixture            #
features/steps/complexity_thresholds_steps.py:139
    When I run the gz command "validate --complexity-thresholds" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                           #
features/steps/gz_steps.py:220

  @REQ-0.0.28-03-06
  Scenario: gz validate help surfaces the new flag     #
features/complexity_thresholds.feature:18
    Given the workspace is initialized                 #
features/steps/gz_steps.py:67
    Given the workspace is initialized                 #
features/steps/gz_steps.py:67
    When I run the gz command "validate --help"        #
features/steps/gz_steps.py:209
    Then the output contains "--complexity-thresholds" #
features/steps/gz_steps.py:225

  @REQ-0.0.28-03-07
  Scenario: Command doc references the new flag
# features/complexity_thresholds.feature:24
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    Then the repo file "docs/user/manpages/validate.md" contains
"--complexity-thresholds" # features/steps/complexity_thresholds_steps.py:144

  @REQ-0.0.28-03-02
  Scenario: Rule body where any metric lacks a block band fails closed         #
features/complexity_thresholds.feature:29
    Given the workspace is initialized                                         #
features/steps/gz_steps.py:67
    Given a complexity-thresholds fixture with radon_cc missing its block band #
features/steps/complexity_thresholds_steps.py:105
    When I run the gz command "validate --complexity-thresholds"               #
features/steps/gz_steps.py:209
    Then the command exits with code 3                                         #
features/steps/gz_steps.py:220
    And the output contains "radon_cc"                                         #
features/steps/gz_steps.py:225

  @REQ-0.0.28-03-03
  Scenario: Off-enum corpus percentile fails closed
# features/complexity_thresholds.feature:36
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    Given a complexity-thresholds fixture with radon_cc carrying an off-enum
percentile # features/steps/complexity_thresholds_steps.py:116
    When I run the gz command "validate --complexity-thresholds"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220

  @REQ-0.0.28-03-04
  Scenario: Unparseable citation fails closed                             #
features/complexity_thresholds.feature:42
    Given the workspace is initialized                                    #
features/steps/gz_steps.py:67
    Given a complexity-thresholds fixture with a malformed citation block #
features/steps/complexity_thresholds_steps.py:126
    When I run the gz command "validate --complexity-thresholds"          #
features/steps/gz_steps.py:209
    Then the command exits with code 3                                    #
features/steps/gz_steps.py:220

  @REQ-0.0.28-03-05
  Scenario: Bootstrap-absolutes section emits a warning without failing
# features/complexity_thresholds.feature:48
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    Given a well-formed complexity-thresholds fixture with the bootstrap section
# features/steps/complexity_thresholds_steps.py:133
    When I run the gz command "validate --complexity-thresholds"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Bootstrap"
# features/steps/gz_steps.py:225

Feature: Constitutional invariant composition renderer (ADR-0.0.37,
OBPI-0.0.37-02) # features/constitutional_invariants.feature:1
  As an operator maintaining gzkit governance
  I want the composition renderer to produce byte-deterministic output
  And I want --check mode to detect drift between the registry and AGENTS.md
  Feature: Constitutional invariant composition renderer (ADR-0.0.37,
OBPI-0.0.37-02)  # features/constitutional_invariants.feature:1

  @REQ-0.0.37-02-01
  Scenario: Renderer produces identical bytes across consecutive invocations  #
features/constitutional_invariants.feature:11
    Given the workspace is initialized                                        #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry          #
features/steps/constitutional_invariants_steps.py:105
    When I run "gz governance render --target agents-md --stdout" twice       #
features/steps/constitutional_invariants_steps.py:123
    Then the two outputs are byte-identical                                   #
features/steps/constitutional_invariants_steps.py:165

  @REQ-0.0.37-02-02
  Scenario: --check exits 0 when AGENTS.md matches rendered output   #
features/constitutional_invariants.feature:16
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    Given AGENTS.md contains the current rendered output             #
features/steps/constitutional_invariants_steps.py:110
    When I run "gz governance render --target agents-md --check"     #
features/steps/constitutional_invariants_steps.py:132
    Then the command exits with code 0                               #
features/steps/gz_steps.py:220

  @REQ-0.0.37-02-02
  Scenario: --check exits 3 when AGENTS.md differs from rendered output  #
features/constitutional_invariants.feature:22
    Given the workspace is initialized                                   #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry     #
features/steps/constitutional_invariants_steps.py:105
    Given AGENTS.md contains stale content                               #
features/steps/constitutional_invariants_steps.py:116
    When I run "gz governance render --target agents-md --check"         #
features/steps/constitutional_invariants_steps.py:132
    Then the command exits with code 3                                   #
features/steps/gz_steps.py:220
    And the output contains "@@"                                         #
features/steps/gz_steps.py:225

  @REQ-0.0.37-02-04
  Scenario: Unsupported target exits nonzero with error message      #
features/constitutional_invariants.feature:29
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    When I run "gz governance render --target skill-readme"          #
features/steps/constitutional_invariants_steps.py:146
    Then the command exits non-zero                                  #
features/steps/gz_steps.py:215
    And the output contains "unsupported target"                     #
features/steps/gz_steps.py:225

  @REQ-0.0.37-02-03
  Scenario: Write mode writes rendered bytes to AGENTS.md and reports byte count
# features/constitutional_invariants.feature:35
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    When I run "gz governance render --target agents-md"
# features/steps/constitutional_invariants_steps.py:139
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "bytes"
# features/steps/gz_steps.py:225
    And AGENTS.md exists in the workspace
# features/steps/constitutional_invariants_steps.py:160

  @REQ-0.0.37-02-05
  Scenario: governance render verb resolves via gz governance render --help  #
features/constitutional_invariants.feature:42
    Given the workspace is initialized                                       #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry         #
features/steps/constitutional_invariants_steps.py:105
    When I run "gz governance render --help"                                 #
features/steps/constitutional_invariants_steps.py:153
    Then the command exits with code 0                                       #
features/steps/gz_steps.py:220
    And the output contains "render"                                         #
features/steps/gz_steps.py:225

  @REQ-0.0.37-03-01
  Scenario: gz validate --invariant-coherence exits 0 on matching AGENTS.md  #
features/constitutional_invariants.feature:50
    Given the workspace is initialized                                       #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry         #
features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry       #
features/steps/constitutional_invariants_steps.py:105
    And AGENTS.md matches the rendered registry output                       #
features/steps/constitutional_invariants_steps.py:183
    When I run "gz validate --invariant-coherence"                           #
features/steps/constitutional_invariants_steps.py:204
    Then the command exits with code 0                                       #
features/steps/gz_steps.py:220

  @REQ-0.0.37-03-02
  Scenario: gz validate --invariant-coherence exits 3 on drift         #
features/constitutional_invariants.feature:57
    Given the workspace is initialized                                 #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry   #
features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    And AGENTS.md differs from the rendered registry output            #
features/steps/constitutional_invariants_steps.py:190
    When I run "gz validate --invariant-coherence"                     #
features/steps/constitutional_invariants_steps.py:204
    Then the command exits with code 3                                 #
features/steps/gz_steps.py:220
    And the output contains "Diff (first 50 lines)"                    #
features/steps/gz_steps.py:225
    And the output contains "@@"                                       #
features/steps/gz_steps.py:225

  @REQ-0.0.37-03-03
  Scenario: gz validate --invariant-coherence emits composition_rendered event
# features/constitutional_invariants.feature:66
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    And a committed AGENTS.md rendition exists
# features/steps/constitutional_invariants_steps.py:199
    When I run "gz validate --invariant-coherence"
# features/steps/constitutional_invariants_steps.py:204
    Then a "composition_rendered" event is appended to the ledger
# features/steps/constitutional_invariants_steps.py:211

  @REQ-0.0.37-04-01
  Scenario: BriefStructure model is frozen and rejects empty field lists  #
features/constitutional_invariants.feature:75
    Given the workspace is initialized                                    #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry      #
features/steps/constitutional_invariants_steps.py:105
    Given a valid BriefStructure field set                                #
features/steps/constitutional_invariants_steps.py:263
    When I construct a BriefStructure instance                            #
features/steps/constitutional_invariants_steps.py:268
    Then the model is frozen and mutation raises an error                 #
features/steps/constitutional_invariants_steps.py:273
    And constructing with an empty allowlist raises ValidationError       #
features/steps/constitutional_invariants_steps.py:282
    And constructing with an empty reqs list raises ValidationError       #
features/steps/constitutional_invariants_steps.py:291

  @REQ-0.0.37-04-02
  Scenario: JSON Schema mirror has additionalProperties false and validates
compliant instances  # features/constitutional_invariants.feature:83
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the obpi_brief_structure.json schema file
# features/steps/constitutional_invariants_steps.py:300
    When I validate a compliant brief instance against it
# features/steps/constitutional_invariants_steps.py:305
    Then validation succeeds
# features/steps/constitutional_invariants_steps.py:314
    And an instance missing the reqs field fails validation
# features/steps/constitutional_invariants_steps.py:321

  @REQ-0.0.37-04-03
  Scenario: parse_brief returns LegacyBriefShape with warning for a legacy brief
# features/constitutional_invariants.feature:90
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given a legacy OBPI brief file without structured frontmatter fields
# features/steps/constitutional_invariants_steps.py:331
    When I call parse_brief on it in permissive mode
# features/steps/constitutional_invariants_steps.py:337
    Then the result is a LegacyBriefShape instance
# features/steps/constitutional_invariants_steps.py:345
    And a DeprecationWarning is emitted
# features/steps/constitutional_invariants_steps.py:352

  @REQ-0.0.37-04-04
  Scenario: parse_brief strict mode raises ValueError for a brief missing
structured fields  # features/constitutional_invariants.feature:97
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given a legacy OBPI brief file without structured frontmatter fields
# features/steps/constitutional_invariants_steps.py:331
    When I call parse_brief on it with strict=True
# features/steps/constitutional_invariants_steps.py:358
    Then a ValueError is raised
# features/steps/constitutional_invariants_steps.py:367

  @REQ-0.0.37-04-05
  Scenario: The OBPI-0.0.37-04 brief round-trips through parse_brief as
BriefStructure  # features/constitutional_invariants.feature:103
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the OBPI-0.0.37-04 brief file with structured frontmatter
# features/steps/constitutional_invariants_steps.py:374
    When I call parse_brief on it
# features/steps/constitutional_invariants_steps.py:380
    Then the result is a BriefStructure instance
# features/steps/constitutional_invariants_steps.py:388
    And no DeprecationWarning is emitted
# features/steps/constitutional_invariants_steps.py:395

  @REQ-0.0.37-13-01
  Scenario: Import AGENTS.md populates pillars for every ## section        #
features/constitutional_invariants.feature:112
    Given the workspace is initialized                                     #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry       #
features/steps/constitutional_invariants_steps.py:105
    Given the AGENTS.md file in the project root                           #
features/steps/constitutional_invariants_steps.py:407
    When I parse the file as AgentContract                                 #
features/steps/constitutional_invariants_steps.py:413
    Then the model has more than 5 pillars                                 #
features/steps/constitutional_invariants_steps.py:422
    And a pillar with title "Behavior Rules" exists with non-empty bullets #
features/steps/constitutional_invariants_steps.py:428

  @REQ-0.0.37-13-02
  Scenario: Bullet classification is joined from the advisory scorecard
# features/constitutional_invariants.feature:119
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the AGENTS.md file in the project root
# features/steps/constitutional_invariants_steps.py:407
    When I parse the file as AgentContract
# features/steps/constitutional_invariants_steps.py:413
    Then a bullet containing "Never prefix" and "uv run gz" has classification
"Mechanical" # features/steps/constitutional_invariants_steps.py:436

  @REQ-0.0.37-13-03
  Scenario: agents.local.md content is captured as model rows via the AGENTS.md
splice  # features/constitutional_invariants.feature:125
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the AGENTS.md file in the project root
# features/steps/constitutional_invariants_steps.py:407
    When I parse the file as AgentContract
# features/steps/constitutional_invariants_steps.py:413
    Then a pillar line containing "Operator PII" is present in the model
# features/steps/constitutional_invariants_steps.py:446

  @REQ-0.0.37-13-04
  Scenario: Model to JSON and back is lossless                       #
features/constitutional_invariants.feature:131
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    Given the AGENTS.md file in the project root                     #
features/steps/constitutional_invariants_steps.py:407
    When I parse the file as AgentContract                           #
features/steps/constitutional_invariants_steps.py:413
    Then the model round-trips losslessly through JSON serialization #
features/steps/constitutional_invariants_steps.py:452

  @REQ-0.0.37-13-05
  Scenario: Unmatched bullets default to Ambiguous classification    #
features/constitutional_invariants.feature:137
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    Given a minimal markdown document with an unmatchable rule       #
features/steps/constitutional_invariants_steps.py:461
    When I parse it as AgentContract via the content API             #
features/steps/constitutional_invariants_steps.py:469
    Then the custom-section bullet classification is "Ambiguous"     #
features/steps/constitutional_invariants_steps.py:476

  @REQ-0.0.37-14-01
  Scenario: sync_agents_md renders AGENTS.md via the content model, not the
monolith  # features/constitutional_invariants.feature:145
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    When I sync AGENTS.md via the model pipeline
# features/steps/constitutional_invariants_steps.py:498
    Then the committed AGENTS.md matches the model render
# features/steps/constitutional_invariants_steps.py:513

  @REQ-0.0.37-14-02
  Scenario: The project purpose is sourced through the model pipeline  #
features/constitutional_invariants.feature:151
    Given the workspace is initialized                                 #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry   #
features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    When I sync AGENTS.md via the model pipeline                       #
features/steps/constitutional_invariants_steps.py:498
    Then the rendered AGENTS.md contains the project purpose value     #
features/steps/constitutional_invariants_steps.py:523

  @REQ-0.0.37-14-03
  Scenario: A hand-edit to AGENTS.md fails the invariant-coherence gate  #
features/constitutional_invariants.feature:157
    Given the workspace is initialized                                   #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry     #
features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry   #
features/steps/constitutional_invariants_steps.py:105
    And AGENTS.md has been synced via the model pipeline                 #
features/steps/constitutional_invariants_steps.py:503
    When I hand-edit AGENTS.md outside the render path                   #
features/steps/constitutional_invariants_steps.py:535
    Then "gz validate --invariant-coherence" reports a coherence error   #
features/steps/constitutional_invariants_steps.py:542

  @REQ-0.0.37-14-04
  Scenario: The model render is semantically equivalent to the pre-migration
contract  # features/constitutional_invariants.feature:164
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    When I sync AGENTS.md via the model pipeline
# features/steps/constitutional_invariants_steps.py:498
    Then the rendered AGENTS.md contains the section "Behavior Rules"
# features/steps/constitutional_invariants_steps.py:553
    And the rendered AGENTS.md contains the section "Gate Covenant"
# features/steps/constitutional_invariants_steps.py:553

  @REQ-0.0.37-15-01
  Scenario: temperature_for resolves per-vendor temperature from manifest
# features/constitutional_invariants.feature:173
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given a vendor manifest declaring AgentContract temperatures codex=lite,
claude=heavy # features/steps/constitutional_invariants_steps.py:564
    When I call temperature_for for AgentContract and claude
# features/steps/constitutional_invariants_steps.py:581
    Then the resolved temperature is "heavy"
# features/steps/constitutional_invariants_steps.py:603

  @REQ-0.0.37-15-02
  Scenario: temperature_for fails closed for an undeclared vendor
# features/constitutional_invariants.feature:179
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given a vendor manifest declaring AgentContract temperatures codex=lite,
claude=heavy # features/steps/constitutional_invariants_steps.py:564
    When I call temperature_for for AgentContract and an unknown vendor
# features/steps/constitutional_invariants_steps.py:591
    Then a temperature ValueError is raised
# features/steps/constitutional_invariants_steps.py:610

  @REQ-0.0.37-15-03
  Scenario: Codex lite render includes all bullets (density projection retired)
# features/constitutional_invariants.feature:185
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given an AgentContract with a Judgment bullet and a plain bullet
# features/steps/constitutional_invariants_steps.py:616
    When I render the contract for codex at lite temperature
# features/steps/constitutional_invariants_steps.py:645
    Then the Judgment bullet is present in the rendered output
# features/steps/constitutional_invariants_steps.py:654
    And the plain bullet is also present in the rendered output
# features/steps/constitutional_invariants_steps.py:661

  @REQ-0.0.37-15-04
  Scenario: Temperature no longer differentiates a vendor render (selection
retired)  # features/constitutional_invariants.feature:192
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given an AgentContract with a Judgment bullet and a plain bullet
# features/steps/constitutional_invariants_steps.py:616
    When I render the contract for codex at lite and codex at heavy
# features/steps/constitutional_invariants_steps.py:669
    Then the two rendered outputs are identical
# features/steps/constitutional_invariants_steps.py:686

  @REQ-0.0.37-18-01
  Scenario: A corpus entry carries exactly the ten addressed fields    #
features/constitutional_invariants.feature:200
    Given the workspace is initialized                                 #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry   #
features/steps/constitutional_invariants_steps.py:105
    Given a corpus entry with all ten addressed fields populated       #
features/steps/constitutional_invariants_steps.py:728
    Then the corpus entry model has exactly the ten addressed fields   #
features/steps/constitutional_invariants_steps.py:733
    And constructing a corpus entry with an unknown field fails closed #
features/steps/constitutional_invariants_steps.py:751

  @REQ-0.0.37-18-02
  Scenario: The corpus is append-only and round-trips through JSONL
# features/constitutional_invariants.feature:206
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given an empty corpus
# features/steps/constitutional_invariants_steps.py:760
    When two corpus entries are appended
# features/steps/constitutional_invariants_steps.py:767
    Then the corpus holds two entries and the original empty corpus is unchanged
# features/steps/constitutional_invariants_steps.py:774
    And the corpus round-trips losslessly through JSONL
# features/steps/constitutional_invariants_steps.py:780

  @REQ-0.0.37-18-03
  Scenario: A corpus entry section must resolve to a contract Pillar
# features/constitutional_invariants.feature:213
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry
# features/steps/constitutional_invariants_steps.py:105
    Given an agent contract whose only section is "prime-directive"
# features/steps/constitutional_invariants_steps.py:787
    Then a corpus entry in section "prime-directive" validates against the
contract # features/steps/constitutional_invariants_steps.py:798
    And a corpus entry in section "no-such-section" fails validation
# features/steps/constitutional_invariants_steps.py:807

  @REQ-0.0.37-18-04
  Scenario: The corpus_entry JSON Schema mirrors the model           #
features/constitutional_invariants.feature:219
    Given the workspace is initialized                               #
features/steps/gz_steps.py:67
    And the constitutional invariant registry has at least one entry #
features/steps/constitutional_invariants_steps.py:105
    Given the corpus_entry JSON Schema                               #
features/steps/constitutional_invariants_steps.py:820
    Then the schema accepts a conformant corpus entry                #
features/steps/constitutional_invariants_steps.py:825
    And the schema rejects a corpus entry with an out-of-enum tier   #
features/steps/constitutional_invariants_steps.py:830

Feature: gz content advise-rendition — advisor-QC info-retention verdict #
features/content_advise_rendition.feature:1
  The advise-rendition subcommand records an agent-supplied
  information-retained-per-byte verdict for a candidate rendition as an ARB
  receipt the operator cites at Gate 5. The tool is deterministic (no LLM /
  network call) and advisory, never gating: any score is recorded and the
  command exits 0. The only fail-closed path is a structurally malformed
  verdict — an empty explanation — which writes no receipt (ADR-0.0.39
  explanation-before-verdict doctrine).
  Feature: gz content advise-rendition — advisor-QC info-retention verdict  #
features/content_advise_rendition.feature:1

  @REQ-0.0.37-24-01
  Scenario: A low retention score is advisory — recorded, exits 0
# features/content_advise_rendition.feature:15
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    When I run advise-rendition for "AGENTS.md" consumer "codex" score "0.12"
explanation "Two Promotable bullets dropped — measurable info loss, surfaced for
the operator." # features/steps/content_advise_rendition_steps.py:54
    Then the command exits 0
# features/steps/content_compose_steps.py:197
    And an advisor-QC receipt is written
# features/steps/content_advise_rendition_steps.py:78
    And the ledger contains a "rendition_advisor_verdict" event for surface
"AGENTS.md"
# features/steps/content_compose_steps.py:214

  @REQ-0.0.37-24-01
  Scenario: A high retention score is recorded the same way — exits 0
# features/content_advise_rendition.feature:22
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    When I run advise-rendition for "AGENTS.md" consumer "codex" score "0.94"
explanation "All Mechanical bullets retained; two Promotable bullets combined
cleanly." # features/steps/content_advise_rendition_steps.py:54
    Then the command exits 0
# features/steps/content_compose_steps.py:197
    And an advisor-QC receipt is written
# features/steps/content_advise_rendition_steps.py:78

  @REQ-0.0.37-24-02
  Scenario: An empty explanation fails closed — no receipt written
# features/content_advise_rendition.feature:28
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    When I run advise-rendition for "AGENTS.md" consumer "codex" score "0.5"
explanation " " # features/steps/content_advise_rendition_steps.py:54
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And no advisor-QC receipt is written
# features/steps/content_advise_rendition_steps.py:84
    And the ledger has no "rendition_advisor_verdict" event
# features/steps/content_advise_rendition_steps.py:90

  @REQ-0.0.37-24-03
  Scenario: Identical input yields a byte-identical receipt (deterministic, no
LLM)                                                          #
features/content_advise_rendition.feature:35
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    When I run advise-rendition twice for "AGENTS.md" consumer "codex" score
"0.77" explanation "Deterministic body under identical inputs." #
features/steps/content_advise_rendition_steps.py:66
    Then both advise-rendition runs exit 0
# features/steps/content_advise_rendition_steps.py:104
    And the two advisor-QC receipts are byte-identical
# features/steps/content_advise_rendition_steps.py:110

Feature: gz content compose — authoring-time compression candidate validation #
features/content_compose.feature:1
  The compose subcommand accepts a candidate rendition text, validates
  invariant-tier verbatim preservation (0-Kelvin floor), computes per-tier
  byte evidence, writes the candidate artifact, and emits a ledger event.
  The tool is deterministic and NEVER writes a rendered surface.
  Feature: gz content compose — authoring-time compression candidate validation
# features/content_compose.feature:1

  @REQ-0.0.37-21-01
  Scenario: Compose produces candidate rendition with byte evidence
# features/content_compose.feature:15
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY." # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."       # features/steps/content_compose_steps.py:80
    Given a candidate file containing the invariant entry and compressed
compressible content # features/steps/content_compose_steps.py:86
    When I run "gz content compose AGENTS.md --consumer codex --candidate
<file>"             # features/steps/content_compose_steps.py:131
    Then the command exits 0
# features/steps/content_compose_steps.py:197
    And the candidate file exists at
".gzkit/renditions/AGENTS.md/codex.candidate.md"         #
features/steps/content_compose_steps.py:204
    And the output includes "Byte evidence"
# features/steps/content_compose_steps.py:209
    And the output includes "setpoint=lite"
# features/steps/content_compose_steps.py:209
    And the ledger contains a "composition_candidate_emitted" event for surface
"AGENTS.md"   # features/steps/content_compose_steps.py:214

  @REQ-0.0.37-21-02
  Scenario: Compose is deterministic
# features/content_compose.feature:25
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY." # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."       # features/steps/content_compose_steps.py:80
    Given a candidate file containing the invariant entry and some content
# features/steps/content_compose_steps.py:92
    When I run "gz content compose AGENTS.md --consumer codex --candidate
<file>" twice       # features/steps/content_compose_steps.py:148
    Then both runs exit 0
# features/steps/content_compose_steps.py:230
    And the byte evidence output is identical between runs
# features/steps/content_compose_steps.py:235

  @REQ-0.0.37-21-03
  Scenario: Invariant-tier entries appear verbatim in candidate
# features/content_compose.feature:32
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY."                  # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."                        # features/steps/content_compose_steps.py:80
    Given a candidate file containing the invariant entry text verbatim
# features/steps/content_compose_steps.py:104
    When I run "gz content compose AGENTS.md --consumer codex --candidate
<file>"                              #
features/steps/content_compose_steps.py:131
    Then the command exits 0
# features/steps/content_compose_steps.py:197
    And the candidate at ".gzkit/renditions/AGENTS.md/codex.candidate.md"
contains the invariant text verbatim #
features/steps/content_compose_steps.py:242

  @REQ-0.0.37-21-04
  Scenario: Compose fails closed on absent corpus
# features/content_compose.feature:39
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY." # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."       # features/steps/content_compose_steps.py:80
    Given no corpus store exists for "MISSING.md"
# features/steps/content_compose_steps.py:116
    And a candidate file with some text
# features/steps/content_compose_steps.py:121
    When I run "gz content compose MISSING.md --consumer codex --candidate
<file>"            # features/steps/content_compose_steps.py:185
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And no candidate file is written at
".gzkit/renditions/MISSING.md/codex.candidate.md"     #
features/steps/content_compose_steps.py:250

  @REQ-0.0.37-21-04
  Scenario: Compose fails closed on undeclared setpoint
# features/content_compose.feature:47
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY." # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."       # features/steps/content_compose_steps.py:80
    Given a candidate file containing the invariant entry text
# features/steps/content_compose_steps.py:98
    When I run "gz content compose AGENTS.md --consumer unknown-vendor
--candidate <file>"    # features/steps/content_compose_steps.py:168
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And no candidate file is written for "unknown-vendor"
# features/steps/content_compose_steps.py:255

  @REQ-0.0.37-21-05
  Scenario: Compose does not modify rendered surfaces
# features/content_compose.feature:54
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    And the vendor manifest declares setpoint "lite" for (AgentContract, codex)
# features/steps/content_compose_steps.py:68
    And the corpus for "AGENTS.md" contains an invariant entry "YOU OWN THE WORK
COMPLETELY." # features/steps/content_compose_steps.py:73
    And the corpus for "AGENTS.md" contains a compressible entry "Prefer stdlib
JSONL."       # features/steps/content_compose_steps.py:80
    Given "AGENTS.md" exists with some content
# features/steps/content_compose_steps.py:126
    And "CLAUDE.md" exists with some content
# features/steps/content_compose_steps.py:126
    And a valid candidate file containing the invariant entry
# features/steps/content_compose_steps.py:110
    When I run "gz content compose AGENTS.md --consumer codex --candidate
<file>"             # features/steps/content_compose_steps.py:131
    Then the command exits 0
# features/steps/content_compose_steps.py:197
    And "AGENTS.md" is byte-unchanged
# features/steps/content_compose_steps.py:262
    And "CLAUDE.md" is byte-unchanged
# features/steps/content_compose_steps.py:262

Feature: Corpus capture (gz content remember) #
features/content_remember.feature:1
  As a gzkit operator
  I want to capture an addressed entry into a surface's append-only corpus
  So that the source of truth grows without ever hand-editing a rendered surface
  @REQ-0.0.37-19-01
  Scenario: remember appends an addressed entry to the per-surface corpus store
# features/content_remember.feature:8
    Given a control surface "AGENTS.md" with a "Behavior Rules" section
# features/steps/content_remember_steps.py:34
    When I run the gz command "content remember AGENTS.md --section
behavior-rules --text capture-note" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the file ".gzkit/corpus/AGENTS.md.jsonl" exists
# features/steps/gz_steps.py:235

  @REQ-0.0.37-19-02
  Scenario: remember does not modify the rendered surface
# features/content_remember.feature:16
    Given a control surface "AGENTS.md" with a "Behavior Rules" section
# features/steps/content_remember_steps.py:34
    When I run the gz command "content remember AGENTS.md --section
behavior-rules --text capture-note" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the file "AGENTS.md" contains "## Behavior Rules"
# features/steps/gz_steps.py:240
    And the file ".gzkit/corpus/AGENTS.md.jsonl" exists
# features/steps/gz_steps.py:235

  @REQ-0.0.37-19-03
  Scenario: remember emits a corpus_entry_appended ledger event
# features/content_remember.feature:25
    Given a control surface "AGENTS.md" with a "Behavior Rules" section
# features/steps/content_remember_steps.py:34
    When I run the gz command "content remember AGENTS.md --section
behavior-rules --text x --tier invariant" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And ledger event "corpus_entry_appended" has field "surface" equal to
"AGENTS.md"                         # features/steps/gz_steps.py:266
    And ledger event "corpus_entry_appended" has field "tier" equal to
"invariant"                            # features/steps/gz_steps.py:266

  @REQ-0.0.37-19-04
  Scenario: remember fails closed on an unknown surface
# features/content_remember.feature:34
    When I run the gz command "content remember NOPE.md --section behavior-rules
--text x" # features/steps/gz_steps.py:209
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And the file ".gzkit/corpus/NOPE.md.jsonl" does not exist
# features/steps/gz_steps.py:261

Feature: Distribution invariant T0 smoke test #
features/distribution_invariant.feature:1
  Heavy-lane Gate 4 proof for ADR-0.0.32 OBPI-06.
  Builds the wheel via uv build, installs it into a fresh temp venv,
  runs gz init in a clean tempdir using the installed binary, and
  asserts byte-equivalence of the resulting .gzkit/ tree against the
  frozen data/distribution_baseline_manifest.json. Detects drift in
  both directions: missing baseline entries AND extra installed
  artifacts not in the baseline.
  Runtime cost: 30-90s depending on uv build cache state. Tagged with
  the slow marker so it can be excluded from the standard gz test smoke
  run; invoke explicitly via:
  uv run -m behave features/distribution_invariant.feature
  @REQ-0.0.32-06-01 @REQ-0.0.32-06-02 @REQ-0.0.32-06-04 @REQ-0.0.32-06-06
@REQ-0.0.32-06-07 @REQ-0.0.32-06-08 @REQ-0.0.32-06-09 @REQ-0.0.32-06-10 @slow
  Scenario: Build, install, init, assert byte-equivalence vs baseline
# features/distribution_invariant.feature:24
    Given an empty distribution-test project directory
# features/steps/distribution_invariant_steps.py:73
    And the gzkit baseline manifest at
"data/distribution_baseline_manifest.json"                 #
features/steps/distribution_invariant_steps.py:82
    When I build the wheel with uv build
# features/steps/distribution_invariant_steps.py:91
    And I install the built wheel into a fresh temporary venv
# features/steps/distribution_invariant_steps.py:105
    And I run "gz init" in the project directory using the venv's gz binary
# features/steps/distribution_invariant_steps.py:138
    Then every baseline manifest entry is present in the project's .gzkit tree
# features/steps/distribution_invariant_steps.py:149
    And no installed .gzkit artifact under a tracked surface is absent from the
baseline manifest # features/steps/distribution_invariant_steps.py:165

  @REQ-0.0.32-06-03 @REQ-0.0.32-06-05
  Scenario: Baseline manifest validates against frozen schema
# features/distribution_invariant.feature:35
    Given the gzkit baseline manifest at
"data/distribution_baseline_manifest.json" #
features/steps/distribution_invariant_steps.py:82
    Then the manifest has schema_version "1.0"
# features/steps/distribution_invariant_steps.py:193
    And the manifest surfaces include "skills"
# features/steps/distribution_invariant_steps.py:200
    And the manifest surfaces include "rules"
# features/steps/distribution_invariant_steps.py:200
    And the manifest surfaces include "personas"
# features/steps/distribution_invariant_steps.py:200
    And the manifest surfaces include "templates"
# features/steps/distribution_invariant_steps.py:200
    And each "skills" entry resolves to a real file under "src/gzkit/skills/"
# features/steps/distribution_invariant_steps.py:205
    And each "rules" entry resolves to a real file under "src/gzkit/rules/"
# features/steps/distribution_invariant_steps.py:205

@adr-0.0.26 @heavy @foundation
Feature: Evaluation feedback loop end-to-end (ADR-0.0.26 / OBPI-0.0.26-05) #
features/evaluation_feedback_loop.feature:2
  As an operator running gz governance,
  I want the evaluation-feedback loop to traverse from low-score evaluation
  through justify scaffolding, clustering, GHI proposal, and trailer-validated
  rule edit,
  so that the agent's own structured reasoning artifacts feed back into the
  rule corpus through human-attested governance.
  @adr-0.0.26 @heavy @foundation
  Feature: Evaluation feedback loop end-to-end (ADR-0.0.26 / OBPI-0.0.26-05)  #
features/evaluation_feedback_loop.feature:2

  @REQ-0.0.26-01-01 @REQ-0.0.26-01-04
  Scenario: Successful evaluations append distinct adr-evaluation events
# features/evaluation_feedback_loop.feature:17
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given an adr-evaluation event for "ADR-0.99.0-emit-a" with weighted total
4.5 and timestamp "2026-05-03T22:00:00+00:00" #
features/steps/evaluation_feedback_loop_steps.py:253
    And an adr-evaluation event for "ADR-0.99.0-emit-a" with weighted total 4.6
and timestamp "2026-05-03T22:01:00+00:00"   #
features/steps/evaluation_feedback_loop_steps.py:253
    Then the ledger contains 2 "adr-evaluation" events for "ADR-0.99.0-emit-a"
# features/steps/evaluation_feedback_loop_steps.py:337

  @REQ-0.0.26-01-02
  Scenario: A malformed evaluation does not emit an adr-evaluation event     #
features/evaluation_feedback_loop.feature:23
    Given the workspace is initialized for the evaluation-feedback loop      #
features/steps/evaluation_feedback_loop_steps.py:231
    When I attempt to record a malformed adr-evaluation for "ADR-0.99.0-bad" #
features/steps/evaluation_feedback_loop_steps.py:306
    Then the ledger contains 0 "adr-evaluation" events for "ADR-0.99.0-bad"  #
features/steps/evaluation_feedback_loop_steps.py:337

  @REQ-0.0.26-01-03
  Scenario: gz validate --documents accepts the adr-evaluation event shape
# features/evaluation_feedback_loop.feature:28
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given an adr-evaluation event for "ADR-0.99.0-shape" with weighted total 4.0
and timestamp "2026-05-03T22:02:00+00:00" #
features/steps/evaluation_feedback_loop_steps.py:253
    When I run the gz command "validate --documents"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-02-01
  Scenario: Binding gate fails closed on a low dimension score with no justify
artifact              # features/evaluation_feedback_loop.feature:41
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-low" with dimension
"clarity" scoring 1.5 # features/steps/evaluation_feedback_loop_steps.py:266
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-low"                 # features/steps/gz_steps.py:209
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    And the output contains "ADR-0.99.0-low"
# features/steps/gz_steps.py:225

  @REQ-0.0.26-02-02
  Scenario: Binding gate fails closed on three or more red-team challenges
# features/evaluation_feedback_loop.feature:48
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given an adr-evaluation event for "ADR-0.99.0-rt" firing red-team challenges
"C1,C2,C3,C4" # features/steps/evaluation_feedback_loop_steps.py:292
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-rt"            # features/steps/gz_steps.py:209
    Then the command exits non-zero
# features/steps/gz_steps.py:215

  @REQ-0.0.26-02-03
  Scenario: Binding gate exits 0 when a qualifying justify artifact is present
# features/evaluation_feedback_loop.feature:54
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-justified" with
dimension "clarity" scoring 1.5 #
features/steps/evaluation_feedback_loop_steps.py:266
    And a complete justify scaffold exists for "ADR-0.99.0-justified"
# features/steps/evaluation_feedback_loop_steps.py:362
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-justified"                 # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-02-04
  Scenario: Binding gate exits 0 when scores are healthy and no challenges fired
# features/evaluation_feedback_loop.feature:61
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given an adr-evaluation event for "ADR-0.99.0-healthy" with dimension
"clarity" scoring 4.5 # features/steps/evaluation_feedback_loop_steps.py:279
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-healthy"        # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-02-05
  Scenario: Threshold config drives binding gate behavior
# features/evaluation_feedback_loop.feature:67
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given the eval-feedback threshold "low_score_threshold" is set to 1.0
# features/steps/evaluation_feedback_loop_steps.py:373
    And an adr-evaluation event for "ADR-0.99.0-thresh" with dimension "clarity"
scoring 2.0 # features/steps/evaluation_feedback_loop_steps.py:279
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-thresh"      # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-03-01
  Scenario: eval-feedback-cluster appears in gz chores list             #
features/evaluation_feedback_loop.feature:76
    Given the workspace is initialized for the evaluation-feedback loop #
features/steps/evaluation_feedback_loop_steps.py:231
    When I run the gz command "chores list"                             #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                  #
features/steps/gz_steps.py:220
    And the output contains "eval-feedback"                             #
features/steps/gz_steps.py:225

  @REQ-0.0.26-03-02
  Scenario: Clustering chore emits no proposal below the recurrence threshold
# features/evaluation_feedback_loop.feature:82
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-cluster-1" with
dimension "clarity" scoring 1.5 #
features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-cluster-2" with
dimension "clarity" scoring 1.5   #
features/steps/evaluation_feedback_loop_steps.py:266
    When the eval-feedback-cluster chore runs
# features/steps/evaluation_feedback_loop_steps.py:386
    Then 0 proposal records exist under
".gzkit/chores/eval-feedback-cluster/proofs/"                      #
features/steps/evaluation_feedback_loop_steps.py:392

  @REQ-0.0.26-03-03
  Scenario: Clustering chore emits one proposal at the recurrence threshold
# features/evaluation_feedback_loop.feature:89
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-cluster-1" with
dimension "clarity" scoring 1.5 #
features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-cluster-2" with
dimension "clarity" scoring 1.5   #
features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-cluster-3" with
dimension "clarity" scoring 1.5   #
features/steps/evaluation_feedback_loop_steps.py:266
    When the eval-feedback-cluster chore runs
# features/steps/evaluation_feedback_loop_steps.py:386
    Then 1 proposal record exists under
".gzkit/chores/eval-feedback-cluster/proofs/"                      #
features/steps/evaluation_feedback_loop_steps.py:392

  @REQ-0.0.26-03-04
  Scenario: Clustering chore re-run is idempotent (content-hash dedup)
# features/evaluation_feedback_loop.feature:97
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-idem-1" with
dimension "clarity" scoring 1.5 #
features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-idem-2" with dimension
"clarity" scoring 1.5   # features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-idem-3" with dimension
"clarity" scoring 1.5   # features/steps/evaluation_feedback_loop_steps.py:266
    When the eval-feedback-cluster chore runs
# features/steps/evaluation_feedback_loop_steps.py:386
    And the eval-feedback-cluster chore runs again
# features/steps/evaluation_feedback_loop_steps.py:386
    Then 1 proposal record exists under
".gzkit/chores/eval-feedback-cluster/proofs/"                   #
features/steps/evaluation_feedback_loop_steps.py:392

  @REQ-0.0.26-03-05
  Scenario: gz validate --chores-layout passes for the eval-feedback-cluster
chore  # features/evaluation_feedback_loop.feature:106
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    When I run the gz command "validate --chores-layout"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-04-01 @REQ-0.0.26-04-12
  Scenario: TTY plus PROPOSE confirmation files a GHI via mocked gh
# features/evaluation_feedback_loop.feature:114
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a proposal record for cluster "dim:clarity:low" exists in the
eval-feedback-cluster proofs      #
features/steps/evaluation_feedback_loop_steps.py:407
    And the environment is interactive
# features/steps/evaluation_feedback_loop_steps.py:431
    And the operator confirms with "PROPOSE"
# features/steps/evaluation_feedback_loop_steps.py:447
    And gh issue create returns "https://github.com/owner/repo/issues/777"
# features/steps/evaluation_feedback_loop_steps.py:453
    When I invoke chores_propose_ghi for "eval-feedback-cluster"
# features/steps/evaluation_feedback_loop_steps.py:462
    Then the most recent proposal record has "filed" equal to true
# features/steps/evaluation_feedback_loop_steps.py:472
    And the most recent proposal record has "ghi_url" equal to
"https://github.com/owner/repo/issues/777" #
features/steps/evaluation_feedback_loop_steps.py:484

  @REQ-0.0.26-04-02
  Scenario: A headless run marks the proposal advisory-only
# features/evaluation_feedback_loop.feature:124
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a proposal record for cluster "dim:clarity:low" exists in the
eval-feedback-cluster proofs #
features/steps/evaluation_feedback_loop_steps.py:407
    And the environment is headless
# features/steps/evaluation_feedback_loop_steps.py:439
    When I invoke chores_propose_ghi for "eval-feedback-cluster"
# features/steps/evaluation_feedback_loop_steps.py:462
    Then the most recent proposal record has "advisory" equal to true
# features/steps/evaluation_feedback_loop_steps.py:472
    And the most recent proposal record has "filed" equal to false
# features/steps/evaluation_feedback_loop_steps.py:478

  @REQ-0.0.26-04-03
  Scenario: A propose-ghi re-run does not refile an already-filed proposal
# features/evaluation_feedback_loop.feature:132
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a filed proposal record for cluster "dim:clarity:low" exists with url
"https://github.com/owner/repo/issues/700" #
features/steps/evaluation_feedback_loop_steps.py:418
    And the environment is interactive
# features/steps/evaluation_feedback_loop_steps.py:431
    And the operator confirms with "PROPOSE"
# features/steps/evaluation_feedback_loop_steps.py:447
    And gh issue create returns "https://github.com/owner/repo/issues/999"
# features/steps/evaluation_feedback_loop_steps.py:453
    When I invoke chores_propose_ghi for "eval-feedback-cluster"
# features/steps/evaluation_feedback_loop_steps.py:462
    Then the most recent proposal record has "ghi_url" equal to
"https://github.com/owner/repo/issues/700"                 #
features/steps/evaluation_feedback_loop_steps.py:484

  @REQ-0.0.26-04-10
  Scenario: ProposalRecord deserializes with default optional fields           #
features/evaluation_feedback_loop.feature:141
    Given the workspace is initialized for the evaluation-feedback loop        #
features/steps/evaluation_feedback_loop_steps.py:231
    Given a minimal proposal record without filed, ghi_url, or advisory fields #
features/steps/evaluation_feedback_loop_steps.py:495
    Then the proposal record deserializes with "filed" equal to false          #
features/steps/evaluation_feedback_loop_steps.py:513
    And the proposal record deserializes with "advisory" equal to false        #
features/steps/evaluation_feedback_loop_steps.py:513
    And the proposal record deserializes with "ghi_url" equal to None          #
features/steps/evaluation_feedback_loop_steps.py:519

  @REQ-0.0.26-04-04 @REQ-0.0.26-05-03
  Scenario: Trailer validator fails closed on a rule-edit commit closing an
eval-feedback GHI without trailer  #
features/evaluation_feedback_loop.feature:156
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a git repo with a rule-edit commit closing GHI 4242 without an
Eval-feedback-source trailer          #
features/steps/evaluation_feedback_loop_steps.py:554
    And gh issue view labels for 4242 include "eval-feedback"
# features/steps/evaluation_feedback_loop_steps.py:588
    When I run the gz command "validate --commit-trailers"
# features/steps/gz_steps.py:209
    Then the command exits non-zero
# features/steps/gz_steps.py:215

  @REQ-0.0.26-04-05
  Scenario: Trailer validator passes when the rule-edit commit carries an
Eval-feedback-source trailer  # features/evaluation_feedback_loop.feature:163
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a git repo with a rule-edit commit closing GHI 4243 with an
Eval-feedback-source trailer      #
features/steps/evaluation_feedback_loop_steps.py:570
    And gh issue view labels for 4243 include "eval-feedback"
# features/steps/evaluation_feedback_loop_steps.py:588
    When I run the gz command "validate --commit-trailers"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.26-05-01 @REQ-0.0.26-05-02
  Scenario: Full evaluation-feedback loop traverses every transition end-to-end
# features/evaluation_feedback_loop.feature:173
    Given the workspace is initialized for the evaluation-feedback loop
# features/steps/evaluation_feedback_loop_steps.py:231
    Given a low-score adr-evaluation event for "ADR-0.99.0-loop-a" with
dimension "clarity" scoring 1.5   #
features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-loop-b" with dimension
"clarity" scoring 1.5     # features/steps/evaluation_feedback_loop_steps.py:266
    And a low-score adr-evaluation event for "ADR-0.99.0-loop-c" with dimension
"clarity" scoring 1.5     # features/steps/evaluation_feedback_loop_steps.py:266
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-loop-a"                   # features/steps/gz_steps.py:209
    Then the command exits non-zero
# features/steps/gz_steps.py:215
    Given a complete justify scaffold exists for "ADR-0.99.0-loop-a"
# features/steps/evaluation_feedback_loop_steps.py:362
    And a complete justify scaffold exists for "ADR-0.99.0-loop-b"
# features/steps/evaluation_feedback_loop_steps.py:362
    And a complete justify scaffold exists for "ADR-0.99.0-loop-c"
# features/steps/evaluation_feedback_loop_steps.py:362
    When I run the gz command "validate --evaluation-justify-binding
ADR-0.99.0-loop-a"                   # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    When the eval-feedback-cluster chore runs
# features/steps/evaluation_feedback_loop_steps.py:386
    Then 1 proposal record exists under
".gzkit/chores/eval-feedback-cluster/proofs/"                     #
features/steps/evaluation_feedback_loop_steps.py:392
    Given the environment is interactive
# features/steps/evaluation_feedback_loop_steps.py:431
    And the operator confirms with "PROPOSE"
# features/steps/evaluation_feedback_loop_steps.py:447
    And gh issue create returns "https://github.com/owner/repo/issues/555"
# features/steps/evaluation_feedback_loop_steps.py:453
    When I invoke chores_propose_ghi for "eval-feedback-cluster"
# features/steps/evaluation_feedback_loop_steps.py:462
    Then the most recent proposal record has "filed" equal to true
# features/steps/evaluation_feedback_loop_steps.py:472
    And the most recent proposal record has "ghi_url" equal to
"https://github.com/owner/repo/issues/555" #
features/steps/evaluation_feedback_loop_steps.py:484

Feature: gz validate --fidelity-presence enforcement #
features/fidelity_presence.feature:1
  ADR-0.0.73 Boundary Invariant #4 requires every non-pool ADR Decision to ship
  a parseable ## Fidelity Assertions block — runnable commands that exercise the
  ADR's thesis against the real system. The --fidelity-presence scope mechanizes
  that invariant: it fails closed (exit 3) on any non-pool ADR Decision lacking
a
  block, minus the pre-existing block-less ADRs grandfathered in
  data/fidelity_presence_grandfather.json (OBPI-0.0.73-08).
  @REQ-0.0.73-08-01
  Scenario: --fidelity-presence flag appears in gz validate --help  #
features/fidelity_presence.feature:10
    When I run "gz validate --help"                                 #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                       #
features/steps/obpi_lock_steps.py:80
    And the output contains "--fidelity-presence"                   #
features/steps/gz_steps.py:225

  @REQ-0.0.73-08-01
  Scenario: A block-less non-pool ADR Decision fails closed  #
features/fidelity_presence.feature:16
    Given a project with a block-less non-pool ADR Decision  #
features/steps/fidelity_presence_steps.py:36
    When I run "gz validate --fidelity-presence"             #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 3                                #
features/steps/obpi_lock_steps.py:80

  @REQ-0.0.73-08-01
  Scenario: A corpus where every non-pool ADR Decision carries a block passes
# features/fidelity_presence.feature:22
    Given a project where every non-pool ADR Decision carries a Fidelity
Assertions block # features/steps/fidelity_presence_steps.py:44
    When I run "gz validate --fidelity-presence"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

Feature: Foundation Triage and Nominal Allocator (ADR-0.0.57 / OBPI-0.0.57-05) #
features/foundation_triage.feature:1
  As an operator,
  I want foundation triage and nominal ID gap-allocation to work correctly,
  So that I can prioritize foundations by impact and allocate IDs without gaps.
  @REQ-0.0.57-05-03
  Scenario: nominal-allocator gap-allocation suggests lowest free integer
# features/foundation_triage.feature:7
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And foundation ADRs exist for IDs "1,2,4"
# features/steps/plan_create_nominal_steps.py:13
    When I run the gz command "plan create gap-test --kind foundation --semver
99.0.0 --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "0.0.3"
# features/steps/gz_steps.py:225

  @REQ-0.0.57-05-03
  Scenario: foundation-triage script produces structured JSON from in-flight
foundations                  # features/foundation_triage.feature:15
    Given a foundation-triage fixture with ADRs "ADR-0.0.1,ADR-0.0.2" and
insights mentioning "ADR-0.0.1" # features/steps/foundation_triage_steps.py:20
    When I run the foundation-triage script with format "json"
# features/steps/foundation_triage_steps.py:47
    Then the output is valid JSON
# features/steps/task_governance_steps.py:45
    And the JSON contains an entry with id containing "ADR-0.0.1"
# features/steps/foundation_triage_steps.py:67

Feature: gz frontmatter reconcile ledger-wins reconciliation (ADR-0.0.16 /
OBPI-0.0.16-03) # features/frontmatter_reconcile.feature:1
  As an operator remediating frontmatter drift,
  I want gz frontmatter reconcile to rewrite drifted fields to match the ledger
  so that frontmatter stays consistent with its source of truth without
hand-editing.
  Feature: gz frontmatter reconcile ledger-wins reconciliation (ADR-0.0.16 /
OBPI-0.0.16-03)  # features/frontmatter_reconcile.feature:1

  @REQ-0.0.16-03-02
  Scenario: Reconcile rewrites drifted lane and emits a receipt  #
features/frontmatter_reconcile.feature:11
    Given the workspace is initialized                           #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                         #
features/steps/gz_steps.py:90
    Given ADR-0.1.0 has drifted lane frontmatter "heavy"         #
features/steps/frontmatter_reconcile_steps.py:52
    When I run the gz command "frontmatter reconcile"            #
features/steps/gz_steps.py:209
    Then the command exits with code 0                           #
features/steps/gz_steps.py:220
    And ADR-0.1.0 frontmatter "lane" equals "lite"               #
features/steps/frontmatter_reconcile_steps.py:58
    And a frontmatter-coherence receipt exists                   #
features/steps/frontmatter_reconcile_steps.py:67

  @REQ-0.0.16-03-03
  Scenario: Dry-run leaves ADR files untouched but emits the receipt  #
features/frontmatter_reconcile.feature:19
    Given the workspace is initialized                                #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                              #
features/steps/gz_steps.py:90
    Given ADR-0.1.0 has drifted lane frontmatter "heavy"              #
features/steps/frontmatter_reconcile_steps.py:52
    When I run the gz command "frontmatter reconcile --dry-run"       #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                #
features/steps/gz_steps.py:220
    And ADR-0.1.0 frontmatter "lane" equals "heavy"                   #
features/steps/frontmatter_reconcile_steps.py:58
    And a frontmatter-coherence receipt exists                        #
features/steps/frontmatter_reconcile_steps.py:67

  @REQ-0.0.16-03-07
  Scenario: Unmapped status term exits with policy-breach code  #
features/frontmatter_reconcile.feature:27
    Given the workspace is initialized                          #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                        #
features/steps/gz_steps.py:90
    Given ADR-0.1.0 has drifted status frontmatter "Nonsense"   #
features/steps/gates_frontmatter_steps.py:17
    When I run the gz command "frontmatter reconcile"           #
features/steps/gz_steps.py:209
    Then the command exits with code 3                          #
features/steps/gz_steps.py:220
    And the output contains "Nonsense"                          #
features/steps/gz_steps.py:225

Feature: gz gates frontmatter integration (ADR-0.0.16 / OBPI-0.0.16-02) #
features/gates.feature:1
  As an operator running governance gates,
  I want Gate 1 to mechanically block on frontmatter-ledger drift,
  so that stale frontmatter never masquerades as truth during attestation.
  Feature: gz gates frontmatter integration (ADR-0.0.16 / OBPI-0.0.16-02)  #
features/gates.feature:1

  @REQ-0.0.16-02-02 @REQ-0.0.16-02-03
  Scenario: Gate 1 blocks on status frontmatter drift with exit 3        #
features/gates.feature:12
    Given the workspace is initialized                                   #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                                 #
features/steps/gz_steps.py:90
    Given ADR-0.1.0 has drifted status frontmatter "Completed"           #
features/steps/gates_frontmatter_steps.py:17
    When I run the gz command "gates --gate 1 --adr ADR-0.1.0"           #
features/steps/gz_steps.py:209
    Then the command exits with code 3                                   #
features/steps/gz_steps.py:220
    And the output contains "status"                                     #
features/steps/gz_steps.py:225
    And the output contains "gz chores run frontmatter-ledger-coherence" #
features/steps/gz_steps.py:225

  @REQ-0.0.16-02-04
  Scenario: gz gates rejects the --skip-frontmatter bypass flag          #
features/gates.feature:20
    Given the workspace is initialized                                   #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                                 #
features/steps/gz_steps.py:90
    When I run the gz command "gates --skip-frontmatter --adr ADR-0.1.0" #
features/steps/gz_steps.py:209
    Then the command exits non-zero                                      #
features/steps/gz_steps.py:215
    And the output contains "unrecognized arguments"                     #
features/steps/gz_steps.py:225

Feature: Heavy lane Gate 4 governance # features/heavy_lane_gate4.feature:1
  Heavy-lane ADR workflows must enforce Gate 4 BDD checks.
  Scenario: Attestation is blocked until Gate 4 passes              #
features/heavy_lane_gate4.feature:4
    Given the workspace is initialized in heavy mode                #
features/steps/gz_steps.py:60
    And a heavy ADR exists                                          #
features/steps/gz_steps.py:84
    And gate 2 and gate 3 are marked pass for ADR-0.1.0             #
features/steps/gz_steps.py:96
    When I run the gz command "attest ADR-0.1.0 --status completed" #
features/steps/gz_steps.py:209
    Then the command exits non-zero                                 #
features/steps/gz_steps.py:215
    And the output contains "Gate 4 must pass"                      #
features/steps/gz_steps.py:225

  Scenario: Closeout guidance includes Gate 4 BDD command              #
features/heavy_lane_gate4.feature:12
    Given the workspace is initialized in heavy mode                   #
features/steps/gz_steps.py:60
    And a heavy ADR exists                                             #
features/steps/gz_steps.py:84
    When I run the gz command "closeout ADR-0.1.0 --dry-run"           #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                 #
features/steps/gz_steps.py:220
    And the output contains "Gate 4 (BDD): uv run -m behave features/" #
features/steps/gz_steps.py:225

  Scenario: Heavy ADR status reports Gate 4 as pending when not checked  #
features/heavy_lane_gate4.feature:19
    Given the workspace is initialized in heavy mode                     #
features/steps/gz_steps.py:60
    And a heavy ADR exists                                               #
features/steps/gz_steps.py:84
    When I run the gz command "adr status ADR-0.1.0 --json"              #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                   #
features/steps/gz_steps.py:220
    And JSON path "gates.4" equals "pending"                             #
features/steps/gz_steps.py:252

  Scenario: Pipeline guidance requires guarded git sync before completion
accounting  # features/heavy_lane_gate4.feature:26
    Given the workspace is initialized with agent surfaces in heavy mode
# features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "guarded git sync -> completion"
# features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "uv run gz git-sync --apply --lint --test"
# features/steps/gz_steps.py:240

  Scenario: Canonical lane doctrine narrows Heavy to runtime-contract changes
# features/heavy_lane_gate4.feature:31
    Given the workspace is initialized with agent surfaces in heavy mode
# features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "Documentation/process/template-only
changes stay" # features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "command/API/schema/runtime-contract
changes"       # features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "uv run gz check"
# features/steps/gz_steps.py:240

Feature: gz init --update — version-aware canonical refresh #
features/init.feature:1
  Heavy-lane Gate 4 proof for OBPI-0.0.32-05 under ADR-0.0.32 (canonical
  surface packaging). Exercises the three-state IDENTICAL/STALE/EDITED
  detection in :func:`gzkit.commands.init_cmd._refresh_canonical_surfaces`
  via the operator-facing ``gz init --update`` flag.
  @REQ-0.0.32-05-01 @REQ-0.0.32-05-07
  Scenario: stale canonical refreshes cleanly
# features/init.feature:9
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the operator edits ".gzkit/chores/quality-check/CHORE.md" with marker
"DRIFTED-WITHOUT-MARKER-XYZ" # features/steps/chores_distribution_steps.py:77
    When I run "gz init --update" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0
# features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "STALE"
# features/steps/chores_distribution_steps.py:113
    And the subprocess output contains ".gzkit/chores/quality-check/CHORE.md"
# features/steps/chores_distribution_steps.py:113

  @REQ-0.0.32-05-02 @REQ-0.0.32-05-05 @REQ-0.0.32-05-06 @REQ-0.0.32-05-07
  Scenario: project-edit preservation (EDITED state is not overwritten)
# features/init.feature:22
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the operator edits ".gzkit/chores/quality-check/CHORE.md" with marker
"<!-- gzkit-canonical-version: 0.1.0 -->" #
features/steps/chores_distribution_steps.py:77
    When I run "gz init --update" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 3
# features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "EDITED"
# features/steps/chores_distribution_steps.py:113
    And the file ".gzkit/chores/quality-check/CHORE.md" contains "<!--
gzkit-canonical-version: 0.1.0 -->"              #
features/steps/gz_steps.py:240

  @REQ-0.0.32-05-03 @REQ-0.0.32-05-05 @REQ-0.0.32-05-07
  Scenario: conflict reporting — unresolved EDITED entries surface in summary
# features/init.feature:34
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the operator edits ".gzkit/chores/quality-check/CHORE.md" with marker
"<!-- gzkit-canonical-version: 0.1.0 -->" #
features/steps/chores_distribution_steps.py:77
    And the operator edits ".gzkit/chores/pool-triage/CHORE.md" with marker
"<!-- gzkit-canonical-version: 0.1.0 -->"   #
features/steps/chores_distribution_steps.py:77
    When I run "gz init --update" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 3
# features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "Conflicts (EDITED"
# features/steps/chores_distribution_steps.py:113
    And the subprocess output contains ".gzkit/chores/quality-check/CHORE.md"
# features/steps/chores_distribution_steps.py:113
    And the subprocess output contains ".gzkit/chores/pool-triage/CHORE.md"
# features/steps/chores_distribution_steps.py:113

  @REQ-0.0.32-05-04 @REQ-0.0.32-05-08
  Scenario: dry-run prints per-artifact action without writing
# features/init.feature:47
    Given a fresh empty project directory
# features/steps/chores_distribution_steps.py:62
    And the workspace has been initialized via gz init
# features/steps/chores_distribution_steps.py:71
    And the operator edits ".gzkit/chores/quality-check/CHORE.md" with marker
"DRIFTED-WITHOUT-MARKER-XYZ" # features/steps/chores_distribution_steps.py:77
    When I run "gz init --update --dry-run" as a subprocess
# features/steps/chores_distribution_steps.py:98
    Then the subprocess exits with code 0
# features/steps/chores_distribution_steps.py:105
    And the subprocess output contains "Dry run"
# features/steps/chores_distribution_steps.py:113
    And the subprocess output contains "STALE"
# features/steps/chores_distribution_steps.py:113
    And the file ".gzkit/chores/quality-check/CHORE.md" contains
"DRIFTED-WITHOUT-MARKER-XYZ"              # features/steps/gz_steps.py:240

Feature: Intrinsic complexity attestation — two-path escape from refactor
pressure (ADR-0.0.29 / OBPI-0.0.29-07) #
features/intrinsic_complexity_attestation.feature:1
  As an operator diagnosing a function with genuinely irreducible cyclomatic
complexity,
  I want to formally attest that the complexity is intrinsic and cannot be
safely decomposed,
  so that the advisor honors the attestation instead of surfacing repeated
refactor recommendations.
  Two paths are provided:
  - Decorator path: @intrinsic_complexity(reason=..., attestor=...) annotated in
code
  - Commit-time path: gz complexity advise <file>:<qualname> --attest-intrinsic
  @REQ-0.0.29-07-01
  Scenario: decorator-path attested function renders "intrinsic complexity
attested" and exits 0    # features/intrinsic_complexity_attestation.feature:11
    Given a synthetic complexity-advise environment with a block-band Python
source                 # features/steps/complexity_advise_steps.py:163
    And the function "block_band" in "subject.py" is registered as intrinsically
attested           # features/steps/intrinsic_complexity_attestation_steps.py:43
    When I run the gz command "complexity advise subject.py --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "intrinsic complexity attested"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-07-03
  Scenario: --attest-intrinsic refused when function does not cross any band
# features/intrinsic_complexity_attestation.feature:19
    Given a synthetic complexity-advise environment with a clean Python source
# features/steps/complexity_advise_steps.py:153
    And the ledger directory exists at ".gzkit"
# features/steps/intrinsic_complexity_attestation_steps.py:58
    When I run the gz command "complexity advise subject.py:add
--attest-intrinsic --reason=irreducible --attestor=Jeffry --rule-path
complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "does not cross any threshold band"
# features/steps/gz_steps.py:225

  @REQ-0.0.29-07-04
  Scenario: --attest-intrinsic with TTY confirmation writes one ledger event and
exits 0
# features/intrinsic_complexity_attestation.feature:27
    Given a synthetic complexity-advise environment with a block-band Python
source
# features/steps/complexity_advise_steps.py:163
    And the ledger directory exists at ".gzkit"
# features/steps/intrinsic_complexity_attestation_steps.py:58
    When I run the gz command "complexity advise subject.py:block_band
--attest-intrinsic --reason=irreducible-state-machine --attestor=Jeffry
--rule-path complexity-thresholds.json" with simulated TTY attestation #
features/steps/intrinsic_complexity_attestation_steps.py:67
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Intrinsic complexity attested"
# features/steps/gz_steps.py:225
    And the ledger contains an "intrinsic-complexity-attestation" event for
"block_band"
# features/steps/intrinsic_complexity_attestation_steps.py:83

  @REQ-0.0.29-07-05
  Scenario: --attest-intrinsic refused in headless environment
# features/intrinsic_complexity_attestation.feature:36
    Given a synthetic complexity-advise environment with a block-band Python
source
# features/steps/complexity_advise_steps.py:163
    And the ledger directory exists at ".gzkit"
# features/steps/intrinsic_complexity_attestation_steps.py:58
    When I run the gz command "complexity advise subject.py:block_band
--attest-intrinsic --reason=irreducible-state-machine --attestor=Jeffry
--rule-path complexity-thresholds.json" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "headless invocation refused"
# features/steps/gz_steps.py:225

Feature: gz issue file cross-repo defect filing wrapper (ADR-0.0.23 /
OBPI-0.0.23-04) # features/issue_file.feature:1
  As an agent or operator inside a gzkit-consuming repository,
  I want a wrapper that auto-stamps a provenance trailer, validates
  that the issue body references a gzkit-owned surface, and routes the
  issue at tvproductions/gzkit regardless of my git remote,
  so that gzkit-surface defects cannot be misrouted to the consumer's
  tracker even by accident, closing the Safeguard circumvention shape.
  @REQ-0.0.23-04-04 @REQ-0.0.23-04-05
  Scenario: Provenance trailer auto-stamps consumer slug and gz version
# features/issue_file.feature:11
    Given a fixture git remote "git@github.com:acme/widget.git"
# features/steps/issue_file_steps.py:21
    When I run the gz command "issue file --title T --body 'gz validate fails on
stale ledger' --enhancement --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Filed from acme/widget running gz v"
# features/steps/gz_steps.py:225
    And the output contains "Target: tvproductions/gzkit"
# features/steps/gz_steps.py:225
    And the output contains "Label: enhancement"
# features/steps/gz_steps.py:225

  @REQ-0.0.23-04-05
  Scenario: Issue is routed to tvproductions/gzkit regardless of consumer remote
# features/issue_file.feature:20
    Given a fixture git remote "https://github.com/other/consumer.git"
# features/steps/issue_file_steps.py:21
    When I run the gz command "issue file --title T --body 'gz cli audit
miscounts manpages' --defect --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Target: tvproductions/gzkit"
# features/steps/gz_steps.py:225
    And the output contains "Filed from other/consumer running gz v"
# features/steps/gz_steps.py:225

  @REQ-0.0.23-04-06
  Scenario: Body without a gzkit-owned surface marker is hard-rejected
# features/issue_file.feature:28
    Given a fixture git remote "git@github.com:acme/widget.git"
# features/steps/issue_file_steps.py:21
    When I run the gz command "issue file --title T --body 'consumer auth flow
regression' --defect --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "gzkit-owned surface"
# features/steps/gz_steps.py:225

Feature: gz justify pre-execution reasoning walkthrough (ADR-0.0.19 /
OBPI-0.0.19-05) # features/justify.feature:1
  As an operator authoring or attesting governance work,
  I want a deterministic 8-section reasoning scaffold for GHIs, OBPIs, or
  drafts and a validate subverb that gates completion on every section
  being filled,
  so that pre-execution reasoning is preserved as evidence rather than
  reconstructed post-hoc.
  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Render scaffold for a GHI anchor with mocked gh  #
features/justify.feature:11
    Given gh issue view returns fixture body for "GHI-232"   #
features/steps/justify_steps.py:26
    When I run the gz command "justify GHI-232"              #
features/steps/gz_steps.py:209
    Then the command exits with code 0                       #
features/steps/gz_steps.py:220
    And the output contains "# Walkthrough: GHI-232"         #
features/steps/gz_steps.py:225
    And the output contains "_[To be filled]_"               #
features/steps/gz_steps.py:225

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Render scaffold for an OBPI anchor against a fixture brief  #
features/justify.feature:20
    Given a fixture OBPI brief for "OBPI-0.99.0-01"                     #
features/steps/justify_steps.py:48
    When I run the gz command "justify OBPI-0.99.0-01"                  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                  #
features/steps/gz_steps.py:220
    And the output contains "# Walkthrough: OBPI-0.99.0-01"             #
features/steps/gz_steps.py:225

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Render scaffold from --draft with --save and --draft-slug
# features/justify.feature:28
    When I run the gz command "justify --draft 'pre-decision text' --save
--draft-slug my-idea" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And a scaffold artifact is written under "artifacts/justify"
# features/steps/justify_steps.py:125

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Reject ADR anchor with exit 1                            #
features/justify.feature:35
    When I run the gz command "justify ADR-0.0.19"                   #
features/steps/gz_steps.py:209
    Then the command exits with code 1                               #
features/steps/gz_steps.py:220
    And the output contains "justify reasons about change instances" #
features/steps/gz_steps.py:225

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Reject --draft + --save without --draft-slug              #
features/justify.feature:42
    When I run the gz command "justify --draft 'orphan draft' --save" #
features/steps/gz_steps.py:209
    Then the command exits with code 1                                #
features/steps/gz_steps.py:220
    And the output contains "--draft-slug is required"                #
features/steps/gz_steps.py:225

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Validate exits 0 on a complete walkthrough          #
features/justify.feature:49
    Given a complete justify walkthrough fixture at "filled.md" #
features/steps/justify_steps.py:107
    When I run the gz command "justify validate filled.md"      #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Validate exits 1 on an incomplete walkthrough              #
features/justify.feature:56
    Given an incomplete justify walkthrough fixture at "incomplete.md" #
features/steps/justify_steps.py:112
    When I run the gz command "justify validate incomplete.md"         #
features/steps/gz_steps.py:209
    Then the command exits with code 1                                 #
features/steps/gz_steps.py:220
    And the output names an unfilled section ordinal                   #
features/steps/justify_steps.py:131

  @REQ-0.0.19-05-05 @REQ-0.0.19-05-06
  Scenario: Validate exits 2 on a malformed walkthrough             #
features/justify.feature:64
    Given a malformed justify walkthrough fixture at "malformed.md" #
features/steps/justify_steps.py:117
    When I run the gz command "justify validate malformed.md"       #
features/steps/gz_steps.py:209
    Then the command exits with code 2                              #
features/steps/gz_steps.py:220

Feature: gz justify complexity hints integration (ADR-0.0.30 / OBPI-0.0.30-05) #
features/justify_complexity_hints.feature:1
  As an operator running gz justify on an OBPI whose Allowed Paths include .py
files,
  I want authoring-time complexity hints to surface automatically in the
scaffold,
  so that advise-band crossings are visible during pre-execution reasoning
without
  requiring a separate gz complexity-guide invocation.
  @REQ-0.0.30-05-01
  Scenario: justify with .py paths and advise-band crossings injects hints
heading  # features/justify_complexity_hints.feature:8
    Given a justify fixture with .py allowed paths and an advise-band Python
source # features/steps/justify_complexity_hints_steps.py:124
    When I run the gz command "justify OBPI-0.99.1-01"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "### Authoring-time complexity hints"
# features/steps/gz_steps.py:225

  @REQ-0.0.30-05-02
  Scenario: justify with no .py allowed paths skips the hints heading     #
features/justify_complexity_hints.feature:15
    Given a justify fixture with no .py allowed paths                     #
features/steps/justify_complexity_hints_steps.py:137
    When I run the gz command "justify OBPI-0.99.1-02"                    #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                    #
features/steps/gz_steps.py:220
    And the output does not contain "### Authoring-time complexity hints" #
features/steps/gz_steps.py:230

  @REQ-0.0.30-05-03 @REQ-0.0.30-05-04
  Scenario: justify engine failure fails open — heading absent, command exits 0
# features/justify_complexity_hints.feature:23
    Given a justify fixture with .py allowed paths but engine unavailable
# features/steps/justify_complexity_hints_steps.py:143
    When I run the gz command "justify OBPI-0.99.1-03"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output does not contain "### Authoring-time complexity hints"
# features/steps/gz_steps.py:230

  @REQ-0.0.30-05-05 @REQ-0.0.30-05-06
  Scenario: skill amendment artifact exists with bumped version and synced
mirrors  # features/justify_complexity_hints.feature:31
    Given the canonical gz-justify skill amendment is in place
# features/steps/justify_complexity_hints_steps.py:152
    Then the gz-justify skill version is "6.1.0"
# features/steps/justify_complexity_hints_steps.py:162
    And the gz-justify skill body contains "Authoring-time complexity hints"
# features/steps/justify_complexity_hints_steps.py:169
    And the gz-justify vendor mirrors are byte-identical to the canonical
# features/steps/justify_complexity_hints_steps.py:174

Feature: gz validate --kind-invariance enforcement #
features/kind_invariance.feature:1
  Validates that the kind-invariance validator correctly enumerates
  foundation-kind ADRs and asserts each carries a substantive
  @REQ-0.0.35-04-01
  Scenario: --kind-invariance flag is registered and appears in gz validate
--help  # features/kind_invariance.feature:7
    When I run "gz validate --help"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the output contains "--kind-invariance"
# features/steps/gz_steps.py:225

  @REQ-0.0.35-04-02
  Scenario: foundation ADR with substantive Why-foundation-tier section passes
# features/kind_invariance.feature:13
    Given a minimal project with a foundation ADR carrying a substantive
Why-foundation-tier section # features/steps/kind_invariance_steps.py:58
    When I run "gz validate --kind-invariance"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.35-04-03
  Scenario: foundation ADR missing the Why-foundation-tier section fails
# features/kind_invariance.feature:19
    Given a minimal project with a foundation ADR missing the
Why-foundation-tier section # features/steps/kind_invariance_steps.py:67
    When I run "gz validate --kind-invariance"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "kind_invariance"
# features/steps/gz_steps.py:225

  @REQ-0.0.35-04-04
  Scenario: foundation ADR with placeholder-only body fails
# features/kind_invariance.feature:26
    Given a minimal project with a foundation ADR carrying a placeholder-only
Why-foundation-tier section # features/steps/kind_invariance_steps.py:76
    When I run "gz validate --kind-invariance"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "kind_invariance"
# features/steps/gz_steps.py:225

  @REQ-0.0.35-04-05
  Scenario: feature ADR is not enumerated by the validator  #
features/kind_invariance.feature:33
    Given a minimal project with only a feature ADR         #
features/steps/kind_invariance_steps.py:88
    When I run "gz validate --kind-invariance"              #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                               #
features/steps/obpi_lock_steps.py:80

Feature: Lock-handoff coupling validator (ADR-0.0.41 / OBPI-0.0.41-04) #
features/lock_handoff_coupling.feature:1
  As a governance maintainer,
  I want the lock-handoff coupling validator wired into gz check,
  so that every obpi_lock_released event after the OBPI-02 cutover
  is mechanically verified to carry a valid handoff register entry.
  @REQ-0.0.41-04-01
  Scenario: Validator passes on a clean ledger with no post-cutover releases  #
features/lock_handoff_coupling.feature:8
    Given the workspace is initialized                                        #
features/steps/gz_steps.py:67
    When I run the gz command "validate --lock-handoff-coupling"              #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                        #
features/steps/gz_steps.py:220

  @REQ-0.0.41-04-02
  Scenario: Validator fails on a post-cutover release with missing handoff_path
# features/lock_handoff_coupling.feature:14
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And a post-cutover obpi_lock_released event with no handoff_path for
"OBPI-0.0.41-04-bdd-test" # features/steps/lock_handoff_coupling_steps.py:20
    When I run the gz command "validate --lock-handoff-coupling"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220
    And the output contains "OBPI-0.0.41-04-bdd-test"
# features/steps/gz_steps.py:225

Feature: OBPI anchor drift reconciliation # features/obpi_anchor_drift.feature:1
  Completed OBPIs should preserve lifecycle state while reporting superseded
anchors
  when later siblings or ADR closeout commit on top of the anchor.
  Scenario: Reconcile preserves completion state while reporting superseded
anchor  # features/obpi_anchor_drift.feature:5
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And a completed OBPI with anchor-tracked receipt exists for
OBPI-0.1.0-01-demo  # features/steps/gz_steps.py:103
    And the tracked module changes after the completion anchor
# features/steps/gz_steps.py:195
    When I run the gz command "obpi reconcile OBPI-0.1.0-01-demo --json"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And JSON path "runtime_state" equals "completed"
# features/steps/gz_steps.py:252
    And JSON path "anchor_state" equals "superseded"
# features/steps/gz_steps.py:252

Feature: OBPI atomic completion # features/obpi_complete.feature:1
  gz obpi complete atomically validates, writes evidence, flips status,
  records attestation, and emits a completion receipt in a single
  all-or-nothing transaction.
  Scenario: Missing OBPI exits 1
# features/obpi_complete.feature:6
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi complete NONEXISTENT-99 --attestor jeff
--attestation-text Verified" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80

  Scenario: Help text shows required flags       #
features/obpi_complete.feature:11
    Given the workspace is initialized           # features/steps/gz_steps.py:67
    When I run "gz obpi complete -h"             #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                    #
features/steps/obpi_lock_steps.py:80
    And the output contains "--attestor"         #
features/steps/gz_steps.py:225
    And the output contains "--attestation-text" #
features/steps/gz_steps.py:225

Feature: OBPI completion REQ-coverage gate #
features/obpi_completion_coverage_gate.feature:1
  gz obpi complete refuses completion when any REQ in the closing brief's
  Heavy and foundation-kind briefs are fail-closed; lite-non-foundation briefs
  emit a warning and proceed. The override path (--accept-uncovered) records a
  ledger event and requires --attestor-present for agent-relayed acceptance.
  @REQ-0.0.25-01-01
  Scenario: Gate passes when all REQs have passing covering tests
# features/obpi_completion_coverage_gate.feature:13
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-feature OBPI "OBPI-FIXTURE-01-01" with REQ "REQ-0.0.98-01-01"
exists                                                                   #
features/steps/obpi_completion_coverage_gate_steps.py:429
    And a covering test for "REQ-0.0.98-01-01" that passes exists
# features/steps/obpi_completion_coverage_gate_steps.py:438
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000001" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-01" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-01" citing receipt
"arb-step-unittest-00000000000000000000000000000001" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 0
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-01-02
  Scenario: Gate exits 3 when heavy-lane REQ has no covering test
# features/obpi_completion_coverage_gate.feature:23
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-foundation OBPI "OBPI-FIXTURE-01-02" with REQ "REQ-0.0.98-01-02"
exists                                                                #
features/steps/obpi_completion_coverage_gate_steps.py:404
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000002" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-02" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-02" citing receipt
"arb-step-unittest-00000000000000000000000000000002" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 3
# features/steps/obpi_completion_coverage_gate_steps.py:592
    And the output mentions "REQ-0.0.98-01-02"
# features/steps/obpi_completion_coverage_gate_steps.py:600

  @REQ-0.0.25-01-03
  Scenario: Foundation-kind lite-lane brief exits 3 for uncovered REQ
# features/obpi_completion_coverage_gate.feature:33
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a foundation-lite OBPI "OBPI-FIXTURE-01-03" with REQ "REQ-0.0.98-01-03"
exists                                                                 #
features/steps/obpi_completion_coverage_gate_steps.py:417
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000003" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-03" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-03" citing receipt
"arb-step-unittest-00000000000000000000000000000003" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 3
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-01-04
  Scenario: Lite-non-foundation brief warns and proceeds for uncovered REQ
# features/obpi_completion_coverage_gate.feature:42
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a lite-feature OBPI "OBPI-FIXTURE-01-04" with REQ "REQ-0.0.98-01-04"
exists                                                                    #
features/steps/obpi_completion_coverage_gate_steps.py:423
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000004" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-04" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-04" citing receipt
"arb-step-unittest-00000000000000000000000000000004" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 0
# features/steps/obpi_completion_coverage_gate_steps.py:592
    And the output mentions "Warning"
# features/steps/obpi_completion_coverage_gate_steps.py:600

  @REQ-0.0.25-01-05
  Scenario: Gate exits 3 when covering test fails
# features/obpi_completion_coverage_gate.feature:52
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-foundation OBPI "OBPI-FIXTURE-01-05" with REQ "REQ-0.0.98-01-05"
exists                                                                #
features/steps/obpi_completion_coverage_gate_steps.py:404
    And a covering test for "REQ-0.0.98-01-05" that fails exists
# features/steps/obpi_completion_coverage_gate_steps.py:445
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000005" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-05" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-05" citing receipt
"arb-step-unittest-00000000000000000000000000000005" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 3
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-01-06
  Scenario: Any one passing covering test satisfies the REQ
# features/obpi_completion_coverage_gate.feature:62
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-feature OBPI "OBPI-FIXTURE-01-06" with REQ "REQ-0.0.98-01-06"
exists                                                                   #
features/steps/obpi_completion_coverage_gate_steps.py:429
    And a covering test for "REQ-0.0.98-01-06" that passes exists
# features/steps/obpi_completion_coverage_gate_steps.py:438
    And a second covering test for "REQ-0.0.98-01-06" that fails exists
# features/steps/obpi_completion_coverage_gate_steps.py:452
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000006" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-01-06" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-01-06" citing receipt
"arb-step-unittest-00000000000000000000000000000006" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:492
    Then the exit code is 0
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-02-01
  Scenario: Override path proceeds and records ledger event via attestor-present
# features/obpi_completion_coverage_gate.feature:73
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-feature OBPI "OBPI-FIXTURE-02-01" with REQ "REQ-0.0.98-02-01"
exists
# features/steps/obpi_completion_coverage_gate_steps.py:429
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000007" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-02-01" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-02-01" accepting
"REQ-0.0.98-02-01" reason "agent-relayed TTY-path proxy" citing
"arb-step-unittest-00000000000000000000000000000007" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:498
    Then the exit code is 0
# features/steps/obpi_completion_coverage_gate_steps.py:592
    And the ledger contains an "obpi_completion_uncovered_accept" event
# features/steps/obpi_completion_coverage_gate_steps.py:606

  @REQ-0.0.25-02-02
  Scenario: Headless override without pipeline marker proceeds via
operator-verbatim path
# features/obpi_completion_coverage_gate.feature:83
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-foundation OBPI "OBPI-FIXTURE-02-02" with REQ "REQ-0.0.98-02-02"
exists
# features/steps/obpi_completion_coverage_gate_steps.py:404
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000008" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    When I complete coverage-gate OBPI "OBPI-FIXTURE-02-02" accepting
"REQ-0.0.98-02-02" reason "no-marker" citing
"arb-step-unittest-00000000000000000000000000000008" without attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:514
    Then the exit code is 0
# features/steps/obpi_completion_coverage_gate_steps.py:592
    And the ledger contains an "obpi_completion_uncovered_accept" event
# features/steps/obpi_completion_coverage_gate_steps.py:606

  @REQ-0.0.25-02-03
  Scenario: Partial accept-uncovered still fails for unwaived REQ
# features/obpi_completion_coverage_gate.feature:99
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-foundation OBPI "OBPI-FIXTURE-02-03" with REQs
"REQ-0.0.98-03-01" and "REQ-0.0.98-03-02" exists
# features/steps/obpi_completion_coverage_gate_steps.py:411
    And a valid arb step receipt
"arb-step-unittest-00000000000000000000000000000009" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-02-03" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-02-03" accepting only
"REQ-0.0.98-03-01" reason "partial" citing
"arb-step-unittest-00000000000000000000000000000009" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:530
    Then the exit code is 3
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-02-04
  Scenario: gz adr emit-receipt --event closed blocked when OBPI has unwaived
REQ gap                                              #
features/obpi_completion_coverage_gate.feature:108
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy ADR "ADR-FIXTURE-02-04" with a completed OBPI
"OBPI-FIXTURE-02-04" carrying unwaived REQ "REQ-0.0.98-02-04" exists #
features/steps/obpi_completion_coverage_gate_steps.py:467
    When I emit ADR receipt for "ADR-FIXTURE-02-04" event "closed" attestor "BDD
User" text "closing"                              #
features/steps/obpi_completion_coverage_gate_steps.py:571
    Then the exit code is 3
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-02-05
  Scenario: accept-uncovered without accept-uncovered-reason exits 1
# features/obpi_completion_coverage_gate.feature:115
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And a heavy-foundation OBPI "OBPI-FIXTURE-02-05" with REQ "REQ-0.0.98-02-05"
exists
# features/steps/obpi_completion_coverage_gate_steps.py:404
    And a valid arb step receipt
"arb-step-unittest-0000000000000000000000000000000a" exists
# features/steps/obpi_completion_coverage_gate_steps.py:461
    And a pipeline marker for "OBPI-FIXTURE-02-05" is active
# features/steps/attestation_receipt_binding_steps.py:391
    When I complete coverage-gate OBPI "OBPI-FIXTURE-02-05" accepting
"REQ-0.0.98-02-05" without reason citing
"arb-step-unittest-0000000000000000000000000000000a" using attestor-present #
features/steps/obpi_completion_coverage_gate_steps.py:546
    Then the exit code is 1
# features/steps/obpi_completion_coverage_gate_steps.py:592

  @REQ-0.0.25-03-01
  Scenario: This feature file exists with scenario tags for all covered REQs  #
features/obpi_completion_coverage_gate.feature:124
    Given the workspace is the live repository                                #
features/steps/attestation_receipt_binding_steps.py:396
    Then the file "features/obpi_completion_coverage_gate.feature" exists     #
features/steps/gz_steps.py:235

  @REQ-0.0.25-03-02
  Scenario: AGENTS.md OBPI Acceptance Protocol names the REQ-coverage gate
# features/obpi_completion_coverage_gate.feature:129
    Given the workspace is the live repository
# features/steps/attestation_receipt_binding_steps.py:396
    Then AGENTS.md "OBPI Acceptance Protocol" section mentions "REQ-coverage
gate" # features/steps/obpi_completion_coverage_gate_steps.py:619

  @REQ-0.0.25-03-03
  Scenario: obpi-complete manpage documents the accept-uncovered flag
# features/obpi_completion_coverage_gate.feature:134
    Given the workspace is the live repository
# features/steps/attestation_receipt_binding_steps.py:396
    Then the file "docs/user/manpages/obpi-complete.md" contains
"--accept-uncovered" # features/steps/gz_steps.py:240

  @REQ-0.0.25-03-04
  Scenario: CLI audit passes in the live repository post-edit  #
features/obpi_completion_coverage_gate.feature:139
    Given the workspace is the live repository                 #
features/steps/attestation_receipt_binding_steps.py:396
    When I run the gz command "cli audit"                      #
features/steps/gz_steps.py:209
    Then the exit code is 0                                    #
features/steps/obpi_completion_coverage_gate_steps.py:592

Feature: OBPI lock management # features/obpi_lock.feature:1
  Multi-agent work locks for OBPI coordination via gz obpi lock commands.
  Scenario: Claim creates a lock file                    #
features/obpi_lock.feature:4
    Given the workspace is initialized                   #
features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.1.0-01 --json" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                            #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "claimed"      #
features/steps/obpi_lock_steps.py:88

  Scenario: Claim fails when held by another agent                    #
features/obpi_lock.feature:10
    Given the workspace is initialized                                #
features/steps/gz_steps.py:67
    And an OBPI lock exists for "OBPI-0.1.0-01" held by agent "codex" #
features/steps/obpi_lock_steps.py:36
    When I run "gz obpi lock claim OBPI-0.1.0-01 --json"              #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 1                                         #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "conflict"                  #
features/steps/obpi_lock_steps.py:88

  Scenario: Release removes lock (register entry via --abandon)
# features/obpi_lock.feature:17
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.1.0-01"
# features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock release OBPI-0.1.0-01 --abandon tool_failure:demo
--json" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "released"
# features/steps/obpi_lock_steps.py:88

  Scenario: Release validates ownership                               #
features/obpi_lock.feature:24
    Given the workspace is initialized                                #
features/steps/gz_steps.py:67
    And an OBPI lock exists for "OBPI-0.1.0-01" held by agent "codex" #
features/steps/obpi_lock_steps.py:36
    When I run "gz obpi lock release OBPI-0.1.0-01 --json"            #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 1                                         #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "ownership_error"           #
features/steps/obpi_lock_steps.py:88

  Scenario: Release with force overrides ownership (register entry via
--abandon)                    # features/obpi_lock.feature:31
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And an OBPI lock exists for "OBPI-0.1.0-01" held by agent "codex"
# features/steps/obpi_lock_steps.py:36
    When I run "gz obpi lock release OBPI-0.1.0-01 --force --abandon
wrong_obpi_claimed:demo --json" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "released"
# features/steps/obpi_lock_steps.py:88

  Scenario: Check exits 0 when held                     #
features/obpi_lock.feature:38
    Given the workspace is initialized                  #
features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.1.0-01"       #
features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock check OBPI-0.1.0-01 --json" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                           #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "held"        #
features/steps/obpi_lock_steps.py:88

  Scenario: Check exits 1 when free                      #
features/obpi_lock.feature:45
    Given the workspace is initialized                   #
features/steps/gz_steps.py:67
    When I run "gz obpi lock check OBPI-0.1.0-01 --json" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 1                            #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "free"         #
features/steps/obpi_lock_steps.py:88

  Scenario: List shows active locks               #
features/obpi_lock.feature:51
    Given the workspace is initialized            #
features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.1.0-01" #
features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock list --json"          #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                     #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "count" is "1"      #
features/steps/obpi_lock_steps.py:88

  Scenario: List auto-reaps expired locks               #
features/obpi_lock.feature:58
    Given the workspace is initialized                  #
features/steps/gz_steps.py:67
    And an expired OBPI lock exists for "OBPI-0.1.0-01" #
features/steps/obpi_lock_steps.py:51
    When I run "gz obpi lock list --json"               #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                           #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "count" is "0"            #
features/steps/obpi_lock_steps.py:88

  @REQ-0.0.41-02-01 @REQ-0.0.41-02-02 @REQ-0.0.41-02-03
  Scenario: Race-condition interlock — concurrent claim conflicts         #
features/obpi_lock.feature:69
    Given the workspace is initialized                                    #
features/steps/gz_steps.py:67
    And an OBPI lock exists for "OBPI-0.0.41-02" held by agent "agent-a"  #
features/steps/obpi_lock_steps.py:36
    When I run "gz obpi lock claim OBPI-0.0.41-02 --json --agent agent-b" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 1                                             #
features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "conflict"                      #
features/steps/obpi_lock_steps.py:88

  @REQ-0.0.41-02-04 @REQ-0.0.41-02-05 @REQ-0.0.41-02-08
  Scenario: Release --abandon happy path writes degenerate handoff
# features/obpi_lock.feature:77
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.0.41-02"
# features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock release OBPI-0.0.41-02 --abandon network_loss:demo
--json" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "released"
# features/steps/obpi_lock_steps.py:88
    And the JSON output field "category" is "network_loss"
# features/steps/obpi_lock_steps.py:88

  @REQ-0.0.41-02-06
  Scenario: Release --abandon rejects unregistered category
# features/obpi_lock.feature:86
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.0.41-02"
# features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock release OBPI-0.0.41-02 --abandon fabricated:reason
--json" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80
    And the JSON output field "status" is "invalid_abandon"
# features/steps/obpi_lock_steps.py:88

  @REQ-0.0.41-03-01
  Scenario: Release without --abandon and no register entry fails closed
(OBPI-03)  # features/obpi_lock.feature:98
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi lock claim OBPI-0.0.41-02"
# features/steps/obpi_lock_steps.py:67
    And I run "gz obpi lock release OBPI-0.0.41-02"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "FAIL-CLOSED"
# features/steps/gz_steps.py:225
    And the output contains "gz-session-handoff"
# features/steps/gz_steps.py:225
    And the output contains "--abandon"
# features/steps/gz_steps.py:225

Feature: gz obpi repudiate — operator-gated completion repudiation #
features/obpi_repudiate.feature:1
  gz obpi repudiate reverses a fraudulent or erroneous OBPI completion
  without permanent retirement, keeping the OBPI live for re-completion.
  @REQ-0.0.71-02-01
  Scenario: Repudiate verb is registered and dry-run reaches the lookup path
# features/obpi_repudiate.feature:6
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi repudiate NONEXISTENT-99 --cause
model-induced-fabrication --reason smoke --attestor Jeff --dry-run" #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.71-02-02
  Scenario: Empty attestor fails closed (exit 1) before any ledger write
# features/obpi_repudiate.feature:12
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi repudiate NONEXISTENT-99 --cause operator-error --reason
smoke --attestor ''" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.71-02-03
  Scenario: Empty reason fails closed (exit 1) before any ledger write
# features/obpi_repudiate.feature:18
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi repudiate NONEXISTENT-99 --cause operator-error --reason
'' --attestor Jeff" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.71-02-05
  Scenario: Invalid cause value rejected by parser (exit 2)
# features/obpi_repudiate.feature:24
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    When I run "gz obpi repudiate OBPI-0.0.71-02 --cause bad-cause --reason
smoke --attestor Jeff" # features/steps/obpi_lock_steps.py:67
    Then it exits with code 2
# features/steps/obpi_lock_steps.py:80

  Scenario: Help text shows required flags  # features/obpi_repudiate.feature:29
    Given the workspace is initialized      # features/steps/gz_steps.py:67
    When I run "gz obpi repudiate -h"       #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0               #
features/steps/obpi_lock_steps.py:80
    And the output contains "--cause"       # features/steps/gz_steps.py:225
    And the output contains "--reason"      # features/steps/gz_steps.py:225
    And the output contains "--attestor"    # features/steps/gz_steps.py:225

Feature: Patch release discovery CLI # features/patch_release.feature:1
  The gz patch release command discovers qualifying GHIs since the last tag,
  computes the next patch version, and (unless --dry-run) writes a manifest
  and ledger event.
  Scenario: Help text exits zero and lists flags     #
features/patch_release.feature:6
    Given the workspace is initialized in heavy mode #
features/steps/gz_steps.py:60
    When I run the gz command "patch release --help" #
features/steps/gz_steps.py:209
    Then the command exits with code 0               #
features/steps/gz_steps.py:220
    And the output contains "--dry-run"              #
features/steps/gz_steps.py:225
    And the output contains "--json"                 #
features/steps/gz_steps.py:225

  Scenario: Dry run reports discovery without executing  #
features/patch_release.feature:13
    Given the workspace is initialized in heavy mode     #
features/steps/gz_steps.py:60
    When I run the gz command "patch release --dry-run"  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                   #
features/steps/gz_steps.py:220
    And the output contains "Patch Release Discovery"    #
features/steps/gz_steps.py:225
    And the output contains "GHIs discovered"            #
features/steps/gz_steps.py:225

  Scenario: Dry run JSON output is well-formed                 #
features/patch_release.feature:20
    Given the workspace is initialized in heavy mode           #
features/steps/gz_steps.py:60
    When I run the gz command "patch release --dry-run --json" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                         #
features/steps/gz_steps.py:220
    And JSON path "ghi_count" equals "0"                       #
features/steps/gz_steps.py:252
    And JSON path "tag" equals "None"                          #
features/steps/gz_steps.py:252

Feature: Persona control surface # features/persona.feature:1
  Agent personas define behavioral identity frames stored in
  .gzkit/personas/ and loaded at dispatch boundaries (ADR-0.0.11).
  Scenario: List personas in initialized workspace with no files  #
features/persona.feature:5
    Given the workspace is initialized                            #
features/steps/gz_steps.py:67
    When I run the gz command "personas list"                     #
features/steps/gz_steps.py:209
    Then the command exits with code 0                            #
features/steps/gz_steps.py:220

  Scenario: List personas shows implementer when file exists  #
features/persona.feature:10
    Given the workspace is initialized                        #
features/steps/gz_steps.py:67
    And a persona file "implementer" exists                   #
features/steps/persona_steps.py:11
    When I run the gz command "personas list --json"          #
features/steps/gz_steps.py:209
    Then the command exits with code 0                        #
features/steps/gz_steps.py:220
    And the output contains "implementer"                     #
features/steps/gz_steps.py:225
    And the output contains "methodical"                      #
features/steps/gz_steps.py:225

  Scenario: List personas shows main-session when file exists  #
features/persona.feature:18
    Given the workspace is initialized                         #
features/steps/gz_steps.py:67
    And a persona file "main-session" exists                   #
features/steps/persona_steps.py:11
    When I run the gz command "personas list --json"           #
features/steps/gz_steps.py:209
    Then the command exits with code 0                         #
features/steps/gz_steps.py:220
    And the output contains "main-session"                     #
features/steps/gz_steps.py:225
    And the output contains "methodical"                       #
features/steps/gz_steps.py:225

  Scenario: AGENTS.md persona section references main-session grounding  #
features/persona.feature:26
    Given the workspace is initialized with agent surfaces               #
features/steps/gz_steps.py:79
    Then the file "AGENTS.md" contains "main-session"                    #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "craftsperson"                     #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "governance not as overhead"       #
features/steps/gz_steps.py:240

  Scenario: AGENTS.md persona section lists available personas with roles  #
features/persona.feature:32
    Given the workspace is initialized with agent surfaces                 #
features/steps/gz_steps.py:79
    Then the file "AGENTS.md" contains "implementer"                       #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "narrator"                           #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "pipeline-orchestrator"              #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "quality-reviewer"                   #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "spec-reviewer"                      #
features/steps/gz_steps.py:240

  Scenario: Personas list is read-only        # features/persona.feature:40
    Given the workspace is initialized        # features/steps/gz_steps.py:67
    And a persona file "implementer" exists   #
features/steps/persona_steps.py:11
    When I run the gz command "personas list" # features/steps/gz_steps.py:209
    Then the command exits with code 0        # features/steps/gz_steps.py:220
    And the output contains "implementer"     # features/steps/gz_steps.py:225

  Scenario: Persona drift reports with governance evidence                    #
features/persona.feature:47
    Given the workspace is initialized                                        #
features/steps/gz_steps.py:67
    And the ledger contains governance events                                 #
features/steps/persona_steps.py:31
    When I run the gz command "personas drift --persona default-agent --json" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                        #
features/steps/gz_steps.py:220
    And the output is valid JSON                                              #
features/steps/task_governance_steps.py:45

  Scenario: Persona drift filters to single persona                           #
features/persona.feature:54
    Given the workspace is initialized                                        #
features/steps/gz_steps.py:67
    And the ledger contains governance events                                 #
features/steps/persona_steps.py:31
    When I run the gz command "personas drift --persona default-agent --json" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                        #
features/steps/gz_steps.py:220
    And the output contains "default-agent"                                   #
features/steps/gz_steps.py:225

  Scenario: Persona drift help includes description   #
features/persona.feature:61
    Given the workspace is initialized                #
features/steps/gz_steps.py:67
    When I run the gz command "personas drift --help" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                #
features/steps/gz_steps.py:220
    And the output contains "behavioral proxies"      #
features/steps/gz_steps.py:225

Feature: Persona sync to vendor mirrors # features/persona_sync.feature:1
  Persona files in .gzkit/personas/ are mirrored to vendor surfaces
  by gz agent sync control-surfaces (ADR-0.0.13 item 3).
  Scenario: Sync mirrors personas to Claude surface         #
features/persona_sync.feature:5
    Given the workspace is initialized with agent surfaces  #
features/steps/gz_steps.py:79
    And a persona file "implementer" exists                 #
features/steps/persona_steps.py:11
    When I run the gz command "agent sync control-surfaces" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                      #
features/steps/gz_steps.py:220
    And the file ".claude/personas/implementer.md" exists   #
features/steps/gz_steps.py:235

  Scenario: Manifest includes personas control surface      #
features/persona_sync.feature:12
    Given the workspace is initialized with agent surfaces  #
features/steps/gz_steps.py:79
    When I run the gz command "agent sync control-surfaces" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                      #
features/steps/gz_steps.py:220
    And the file ".gzkit/manifest.json" contains "personas" #
features/steps/gz_steps.py:240

  Scenario: Init scaffolds default personas in clean workspace  #
features/persona_sync.feature:20
    Given the workspace is initialized                          #
features/steps/gz_steps.py:67
    Then the file ".gzkit/personas/default-agent.md" exists     #
features/steps/gz_steps.py:235
    And the file ".gzkit/personas/default-reviewer.md" exists   #
features/steps/gz_steps.py:235

  Scenario: Scaffolded personas pass surface validation    #
features/persona_sync.feature:25
    Given the workspace is initialized with agent surfaces #
features/steps/gz_steps.py:79
    When I run the gz command "validate --surfaces"        #
features/steps/gz_steps.py:209
    Then the command exits with code 0                     #
features/steps/gz_steps.py:220

Feature: gz plan create nominal foundation ID allocator (ADR-0.0.57 /
OBPI-0.0.57-02) # features/plan_create_nominal.feature:1
  As an operator,
  I want gz plan create --kind foundation to suggest the next free nominal ID,
  So that I can create foundation ADRs in impact order, not strict ID order.
  @REQ-0.0.57-02-01
  Scenario: Error hint shows lowest gap when foundation tree is sparse
# features/plan_create_nominal.feature:7
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And foundation ADRs exist for IDs "1,2,5,7"
# features/steps/plan_create_nominal_steps.py:13
    When I run the gz command "plan create my-adr --kind foundation --semver
99.0.0 --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "0.0.3"
# features/steps/gz_steps.py:225

  @REQ-0.0.57-02-03
  Scenario: Error hint shows next integer when foundation tree is contiguous
# features/plan_create_nominal.feature:15
    Given the workspace is initialized in heavy mode
# features/steps/gz_steps.py:60
    And foundation ADRs exist for IDs "1,2,3"
# features/steps/plan_create_nominal_steps.py:13
    When I run the gz command "plan create my-adr --kind foundation --semver
99.0.0 --dry-run" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "0.0.4"
# features/steps/gz_steps.py:225

Feature: Committed-rendition store, deterministic playback, and freshness gate #
features/rendition_playback.feature:1
  The committed-rendition store holds a durable
`.gzkit/renditions/<surface>/<consumer>.md`
  artifact. `sync_agents_md` plays it back deterministically (no LLM, no
network).
  The freshness gate (`gz validate --rendition-freshness`) fails closed when the
  corpus has mutated after the committed rendition. `gz validate
--invariant-coherence`
  diffs rendition playback against the committed rendered surface.
  Feature: Committed-rendition store, deterministic playback, and freshness gate
# features/rendition_playback.feature:1

  @REQ-0.0.37-22-01
  Scenario: Rendition store loads byte-identically
# features/rendition_playback.feature:13
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Test rendition\n" # features/steps/rendition_playback_steps.py:28
    When I load the rendition for "AGENTS.md" consumer "claude"
# features/steps/rendition_playback_steps.py:82
    Then the loaded bytes equal "# Test rendition\n"
# features/steps/rendition_playback_steps.py:136
    And loading again returns the same bytes
# features/steps/rendition_playback_steps.py:144

  @REQ-0.0.37-22-01
  Scenario: Rendition store fails closed when artifact is absent           #
features/rendition_playback.feature:20
    Given I have initialized a gzkit project                               #
features/steps/content_compose_steps.py:53
    Given no committed rendition exists for "AGENTS.md" consumer "claude"  #
features/steps/rendition_playback_steps.py:37
    When I attempt to load the rendition for "AGENTS.md" consumer "claude" #
features/steps/rendition_playback_steps.py:93
    Then a FileNotFoundError is raised                                     #
features/steps/rendition_playback_steps.py:151

  @REQ-0.0.37-22-02
  Scenario: sync_agents_md renders AGENTS.md from committed rendition
byte-identically             # features/rendition_playback.feature:26
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Deterministic\n" # features/steps/rendition_playback_steps.py:28
    When I run sync_agents_md
# features/steps/rendition_playback_steps.py:103
    Then AGENTS.md contains exactly "# Deterministic\n"
# features/steps/rendition_playback_steps.py:158
    And running sync_agents_md again produces the same AGENTS.md bytes
# features/steps/rendition_playback_steps.py:165

  @REQ-0.0.37-22-02
  Scenario: sync_agents_md does not call the model pipeline when a rendition
exists            # features/rendition_playback.feature:33
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Rendition\n" # features/steps/rendition_playback_steps.py:28
    When I run sync_agents_md
# features/steps/rendition_playback_steps.py:103
    Then the model render pipeline was not invoked
# features/steps/rendition_playback_steps.py:172

  @REQ-0.0.37-22-03
  Scenario: Freshness gate exits 3 when corpus has mutated after committed
rendition               # features/rendition_playback.feature:39
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Old rendition\n" # features/steps/rendition_playback_steps.py:28
    And the corpus for "AGENTS.md" was mutated after the committed rendition
# features/steps/rendition_playback_steps.py:43
    When I run the gz command "validate --rendition-freshness"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220
    And the ledger contains a "composition_drift_detected" event
# features/steps/rendition_playback_steps.py:179

  @REQ-0.0.37-22-03
  Scenario: Freshness gate exits 0 when rendition is up to date
# features/rendition_playback.feature:47
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given the corpus for "AGENTS.md" exists
# features/steps/rendition_playback_steps.py:54
    And a committed rendition for "AGENTS.md" consumer "claude" committed after
the corpus # features/steps/rendition_playback_steps.py:64
    When I run the gz command "validate --rendition-freshness"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.37-22-04
  Scenario: --invariant-coherence exits 3 when rendition playback differs from
committed AGENTS.md  # features/rendition_playback.feature:54
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Rendition\n"      # features/steps/rendition_playback_steps.py:28
    And AGENTS.md contains "# Different content\n"
# features/steps/rendition_playback_steps.py:71
    When I run the gz command "validate --invariant-coherence"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220

  @REQ-0.0.37-22-04
  Scenario: --invariant-coherence exits 0 when rendition playback matches
AGENTS.md        # features/rendition_playback.feature:61
    Given I have initialized a gzkit project
# features/steps/content_compose_steps.py:53
    Given a committed rendition for "AGENTS.md" consumer "claude" with content
"# Match\n" # features/steps/rendition_playback_steps.py:28
    And AGENTS.md contains "# Match\n"
# features/steps/rendition_playback_steps.py:71
    When I run the gz command "validate --invariant-coherence"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

Feature: Reporter rendering presets # features/reporter_rendering.feature:1
  The reporter module provides consistent Rich rendering for CLI tables and
panels.
  Scenario: status_table renders governance table
# features/reporter_rendering.feature:4
    Given a status_table with title "ADR Status" and columns "ADR,Lane,Status"
and 2 rows # features/steps/reporter_steps.py:13
    When the table is rendered to text
# features/steps/reporter_steps.py:66
    Then the rendered output contains "ADR Status"
# features/steps/reporter_steps.py:75
    And the rendered output contains "ADR-0.1.0"
# features/steps/reporter_steps.py:75

  Scenario: status_table renders empty state                                  #
features/reporter_rendering.feature:10
    Given a status_table with title "ADR Status" and columns "ADR" and 0 rows #
features/steps/reporter_steps.py:13
    When the table is rendered to text                                        #
features/steps/reporter_steps.py:66
    Then the rendered output contains "No data."                              #
features/steps/reporter_steps.py:75

  Scenario: kv_table renders key-value pairs           #
features/reporter_rendering.feature:15
    Given a kv_table with title "Overview" and 3 pairs #
features/steps/reporter_steps.py:32
    When the table is rendered to text                 #
features/steps/reporter_steps.py:66
    Then the rendered output contains "Overview"       #
features/steps/reporter_steps.py:75
    And the rendered output contains "Lane"            #
features/steps/reporter_steps.py:75
    And the rendered output contains "heavy"           #
features/steps/reporter_steps.py:75

  Scenario: ceremony_panel renders with double border        #
features/reporter_rendering.feature:22
    Given a ceremony_panel with title "Closeout" and 2 items #
features/steps/reporter_steps.py:38
    When the panel is rendered to text                       #
features/steps/reporter_steps.py:66
    Then the rendered output contains "Closeout"             #
features/steps/reporter_steps.py:75
    And the rendered output contains "Step 1"                #
features/steps/reporter_steps.py:75

  Scenario: list_table renders simple catalog                                  #
features/reporter_rendering.feature:28
    Given a list_table with title "Chores" and columns "Slug,Title" and 2 rows #
features/steps/reporter_steps.py:47
    When the table is rendered to text                                         #
features/steps/reporter_steps.py:66
    Then the rendered output contains "Chores"                                 #
features/steps/reporter_steps.py:75
    And the rendered output contains "chore-a"                                 #
features/steps/reporter_steps.py:75

  Scenario: list_table renders empty state                               #
features/reporter_rendering.feature:34
    Given a list_table with title "Skills" and columns "Name" and 0 rows #
features/steps/reporter_steps.py:47
    When the table is rendered to text                                   #
features/steps/reporter_steps.py:66
    Then the rendered output contains "No items found."                  #
features/steps/reporter_steps.py:75

Feature: OBPI reviewer agent dispatch  # OBPI-0.23.0-03 #
features/reviewer_agent.feature:1
  An independent reviewer agent verifies delivered work against OBPI
  promises with fresh-eyes assessment of promises-met, docs-quality,
  and closing-argument-quality.
  @review @dispatch
  Scenario: Reviewer prompt contains brief and closing argument           #
features/reviewer_agent.feature:7
    Given an OBPI brief with 2 requirements                               #
features/steps/reviewer_agent_steps.py:28
    And a closing argument "This work earned its closure by delivering X" #
features/steps/reviewer_agent_steps.py:35
    And 2 changed files and 1 doc file                                    #
features/steps/reviewer_agent_steps.py:45
    When I compose the reviewer prompt                                    #
features/steps/reviewer_agent_steps.py:52
    Then the prompt contains the OBPI identifier                          #
features/steps/reviewer_agent_steps.py:63
    And the prompt contains the brief content                             #
features/steps/reviewer_agent_steps.py:68
    And the prompt contains the closing argument                          #
features/steps/reviewer_agent_steps.py:73
    And the prompt contains all changed files                             #
features/steps/reviewer_agent_steps.py:78
    And the prompt contains the doc file                                  #
features/steps/reviewer_agent_steps.py:84
    And the prompt contains the assessment JSON schema                    #
features/steps/reviewer_agent_steps.py:90

  @review @dispatch
  Scenario: Reviewer prompt handles missing closing argument         #
features/reviewer_agent.feature:20
    Given an OBPI brief with 1 requirements                          #
features/steps/reviewer_agent_steps.py:28
    And no closing argument                                          #
features/steps/reviewer_agent_steps.py:40
    And 1 changed files and 0 doc file                               #
features/steps/reviewer_agent_steps.py:45
    When I compose the reviewer prompt                               #
features/steps/reviewer_agent_steps.py:52
    Then the reviewer prompt contains "No closing argument provided" #
features/steps/reviewer_agent_steps.py:99

  @review @parse
  Scenario: Valid PASS assessment is parsed from agent output          #
features/reviewer_agent.feature:28
    Given agent output with a valid PASS assessment for 2 requirements #
features/steps/reviewer_agent_steps.py:113
    When I parse the reviewer assessment                               #
features/steps/reviewer_agent_steps.py:170
    Then the assessment verdict is "PASS"                              #
features/steps/reviewer_agent_steps.py:175
    And 2 promise assessments are returned                             #
features/steps/reviewer_agent_steps.py:183
    And docs quality is "substantive"                                  #
features/steps/reviewer_agent_steps.py:191
    And closing argument quality is "earned"                           #
features/steps/reviewer_agent_steps.py:199

  @review @parse
  Scenario: Valid FAIL assessment is parsed from agent output  #
features/reviewer_agent.feature:37
    Given agent output with a valid FAIL assessment            #
features/steps/reviewer_agent_steps.py:133
    When I parse the reviewer assessment                       #
features/steps/reviewer_agent_steps.py:170
    Then the assessment verdict is "FAIL"                      #
features/steps/reviewer_agent_steps.py:175
    And docs quality is "missing"                              #
features/steps/reviewer_agent_steps.py:191
    And closing argument quality is "missing"                  #
features/steps/reviewer_agent_steps.py:199

  @review @parse
  Scenario: CONCERNS assessment with mixed promises                  #
features/reviewer_agent.feature:45
    Given agent output with a CONCERNS assessment and mixed promises #
features/steps/reviewer_agent_steps.py:147
    When I parse the reviewer assessment                             #
features/steps/reviewer_agent_steps.py:170
    Then the assessment verdict is "CONCERNS"                        #
features/steps/reviewer_agent_steps.py:175
    And promise 1 is met                                             #
features/steps/reviewer_agent_steps.py:207
    And promise 2 is not met                                         #
features/steps/reviewer_agent_steps.py:214

  @review @parse
  Scenario: Invalid agent output returns no assessment  #
features/reviewer_agent.feature:53
    Given agent output with no JSON block               #
features/steps/reviewer_agent_steps.py:164
    When I parse the reviewer assessment                #
features/steps/reviewer_agent_steps.py:170
    Then no assessment is returned                      #
features/steps/reviewer_agent_steps.py:221

  @review @artifact
  Scenario: Assessment artifact is stored alongside the brief  #
features/reviewer_agent.feature:59
    Given a parsed PASS reviewer assessment                    #
features/steps/reviewer_agent_steps.py:231
    And a temporary ADR package directory                      #
features/steps/reviewer_agent_steps.py:247
    When I store the reviewer assessment                       #
features/steps/reviewer_agent_steps.py:253
    Then a REVIEW artifact file exists in the briefs directory #
features/steps/reviewer_agent_steps.py:258
    And the artifact contains the verdict                      #
features/steps/reviewer_agent_steps.py:265
    And the artifact contains promise assessments              #
features/steps/reviewer_agent_steps.py:271

  @review @ceremony
  Scenario: Assessment is formatted for ceremony display        #
features/reviewer_agent.feature:68
    Given a parsed CONCERNS reviewer assessment with 3 promises #
features/steps/reviewer_agent_steps.py:282
    When I format the assessment for ceremony                   #
features/steps/reviewer_agent_steps.py:298
    Then the output contains a promise table with 3 rows        #
features/steps/reviewer_agent_steps.py:303
    And the output contains the docs quality                    #
features/steps/reviewer_agent_steps.py:314
    And the output contains the closing argument quality        #
features/steps/reviewer_agent_steps.py:319
    And the output contains the verdict                         #
features/steps/reviewer_agent_steps.py:324

Feature: gz validate --setpoint-coherence enforcement #
features/setpoint_coherence.feature:1
  Validates that every (content_type, vendor) pair declared in
  data/vendor-manifest.json content_type_routes carries a legal declared
  compression setpoint in content_type_temperatures (OBPI-0.0.37-20).
  @REQ-0.0.37-20-01
  Scenario: --setpoint-coherence flag is registered and appears in gz validate
--help  # features/setpoint_coherence.feature:7
    When I run "gz validate --help"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the output contains "--setpoint-coherence"
# features/steps/gz_steps.py:225

  @REQ-0.0.37-20-03
  Scenario: a manifest with a legal setpoint for every routed pair passes
# features/setpoint_coherence.feature:13
    Given a project whose vendor manifest declares a setpoint for every routed
pair # features/steps/setpoint_coherence_steps.py:29
    When I run "gz validate --setpoint-coherence"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.37-20-01
  Scenario: a routed pair with no declared setpoint fails closed
# features/setpoint_coherence.feature:19
    Given a project whose vendor manifest routes a pair with no declared
setpoint # features/steps/setpoint_coherence_steps.py:41
    When I run "gz validate --setpoint-coherence"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "content_type_temperatures"
# features/steps/gz_steps.py:225

  @REQ-0.0.37-20-02
  Scenario: an illegal setpoint token fails closed                           #
features/setpoint_coherence.feature:26
    Given a project whose vendor manifest declares an illegal setpoint token #
features/steps/setpoint_coherence_steps.py:53
    When I run "gz validate --setpoint-coherence"                            #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 3                                                #
features/steps/obpi_lock_steps.py:80
    And the output contains "Illegal setpoint token"                         #
features/steps/gz_steps.py:225

Feature: State repair force-reconciliation # features/state_repair.feature:1
  The gz state --repair command force-reconciles all OBPI frontmatter
  status from ledger-derived state (ADR-0.0.9, OBPI-03).
  Scenario: Repair updates drifted frontmatter to match ledger  #
features/state_repair.feature:5
    Given the workspace is initialized                          #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                        #
features/steps/gz_steps.py:90
    And an OBPI brief exists with frontmatter status "Draft"    #
features/steps/state_repair_steps.py:14
    And the ledger marks OBPI-0.1.0-01 as completed             #
features/steps/state_repair_steps.py:29
    When I run the gz command "state --repair"                  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220
    And the OBPI brief frontmatter status is "Completed"        #
features/steps/state_repair_steps.py:48

  Scenario: Repair is idempotent                                 #
features/state_repair.feature:14
    Given the workspace is initialized                           #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                         #
features/steps/gz_steps.py:90
    And an OBPI brief exists with frontmatter status "Completed" #
features/steps/state_repair_steps.py:14
    And the ledger marks OBPI-0.1.0-01 as completed              #
features/steps/state_repair_steps.py:29
    When I run the gz command "state --repair"                   #
features/steps/gz_steps.py:209
    And I run the gz command "state --repair --json"             #
features/steps/gz_steps.py:209
    Then the command exits with code 0                           #
features/steps/gz_steps.py:220
    And the JSON output field "total" equals 0                   #
features/steps/state_repair_steps.py:56

  Scenario: Repair reports changes in JSON mode              #
features/state_repair.feature:24
    Given the workspace is initialized                       #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                     #
features/steps/gz_steps.py:90
    And an OBPI brief exists with frontmatter status "Draft" #
features/steps/state_repair_steps.py:14
    And the ledger marks OBPI-0.1.0-01 as completed          #
features/steps/state_repair_steps.py:29
    When I run the gz command "state --repair --json"        #
features/steps/gz_steps.py:209
    Then the command exits with code 0                       #
features/steps/gz_steps.py:220
    And the JSON output field "total" equals 1               #
features/steps/state_repair_steps.py:56

Feature: Subagent pipeline dispatch lifecycle #
features/subagent_pipeline.feature:1
  Stage 2 controller dispatches implementer subagents per plan task
  with model-aware routing and structured result handling.
  @dispatch
  Scenario: Tasks are extracted from a heading-format plan  #
features/subagent_pipeline.feature:6
    Given a plan with heading-format tasks                  #
features/steps/subagent_pipeline_steps.py:36
    When I extract plan tasks                               #
features/steps/subagent_pipeline_steps.py:53
    Then 3 tasks are found                                  #
features/steps/subagent_pipeline_steps.py:58
    And task 1 description is "Add the model"               #
features/steps/subagent_pipeline_steps.py:65

  @dispatch
  Scenario: Tasks are extracted from a numbered-list plan  #
features/subagent_pipeline.feature:13
    Given a plan with numbered-list tasks                  #
features/steps/subagent_pipeline_steps.py:43
    When I extract plan tasks                              #
features/steps/subagent_pipeline_steps.py:53
    Then 2 tasks are found                                 #
features/steps/subagent_pipeline_steps.py:58

  @dispatch
  Scenario: Empty plan yields zero tasks  #
features/subagent_pipeline.feature:19
    Given an empty plan                   #
features/steps/subagent_pipeline_steps.py:48
    When I extract plan tasks             #
features/steps/subagent_pipeline_steps.py:53
    Then 0 tasks are found                #
features/steps/subagent_pipeline_steps.py:58

  @dispatch
  Scenario: Simple task routes to haiku model              #
features/subagent_pipeline.feature:25
    Given a dispatch state with 1 task and 2 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    Then task 1 model is "haiku"                           #
features/steps/subagent_pipeline_steps.py:99
    And task 1 complexity is "simple"                      #
features/steps/subagent_pipeline_steps.py:105

  @dispatch
  Scenario: Standard task routes to sonnet model           #
features/subagent_pipeline.feature:31
    Given a dispatch state with 1 task and 4 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    Then task 1 model is "sonnet"                          #
features/steps/subagent_pipeline_steps.py:99
    And task 1 complexity is "standard"                    #
features/steps/subagent_pipeline_steps.py:105

  @dispatch
  Scenario: Complex task routes to opus model              #
features/subagent_pipeline.feature:37
    Given a dispatch state with 1 task and 7 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    Then task 1 model is "opus"                            #
features/steps/subagent_pipeline_steps.py:99
    And task 1 complexity is "complex"                     #
features/steps/subagent_pipeline_steps.py:105

  @dispatch
  Scenario: DONE result advances to next task               #
features/subagent_pipeline.feature:43
    Given a dispatch state with 2 tasks and 1 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                                #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE                                #
features/steps/subagent_pipeline_steps.py:118
    Then the dispatch action is "advance"                   #
features/steps/subagent_pipeline_steps.py:142
    And task 1 status is "done"                             #
features/steps/subagent_pipeline_steps.py:149

  @dispatch
  Scenario: DONE on last task completes dispatch           #
features/subagent_pipeline.feature:51
    Given a dispatch state with 1 task and 1 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE                               #
features/steps/subagent_pipeline_steps.py:118
    Then the dispatch action is "complete"                 #
features/steps/subagent_pipeline_steps.py:142

  @dispatch
  Scenario: DONE_WITH_CONCERNS logs concerns and advances               #
features/subagent_pipeline.feature:58
    Given a dispatch state with 2 tasks and 1 allowed paths             #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                                            #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE_WITH_CONCERNS with concern "might break X" #
features/steps/subagent_pipeline_steps.py:136
    Then the dispatch action is "advance"                               #
features/steps/subagent_pipeline_steps.py:142
    And all concerns include "might break X"                            #
features/steps/subagent_pipeline_steps.py:155

  @dispatch
  Scenario: NEEDS_CONTEXT triggers redispatch              #
features/subagent_pipeline.feature:66
    Given a dispatch state with 1 task and 1 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns NEEDS_CONTEXT                      #
features/steps/subagent_pipeline_steps.py:124
    Then the dispatch action is "redispatch"               #
features/steps/subagent_pipeline_steps.py:142

  @dispatch
  Scenario: NEEDS_CONTEXT circuit breaker after max retries  #
features/subagent_pipeline.feature:73
    Given a dispatch state with 1 task and 1 allowed paths   #
features/steps/subagent_pipeline_steps.py:76
    And task 1 has been dispatched 2 times                   #
features/steps/subagent_pipeline_steps.py:90
    When task 1 returns NEEDS_CONTEXT                        #
features/steps/subagent_pipeline_steps.py:124
    Then the dispatch action is "handoff"                    #
features/steps/subagent_pipeline_steps.py:142
    And task 1 status is "blocked"                           #
features/steps/subagent_pipeline_steps.py:149

  @dispatch
  Scenario: BLOCKED triggers fix attempt                   #
features/subagent_pipeline.feature:81
    Given a dispatch state with 1 task and 1 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns BLOCKED                            #
features/steps/subagent_pipeline_steps.py:130
    Then the dispatch action is "fix"                      #
features/steps/subagent_pipeline_steps.py:142

  @dispatch
  Scenario: BLOCKED after max fix attempts triggers handoff  #
features/subagent_pipeline.feature:88
    Given a dispatch state with 1 task and 1 allowed paths   #
features/steps/subagent_pipeline_steps.py:76
    And task 1 has been dispatched 2 times                   #
features/steps/subagent_pipeline_steps.py:90
    When task 1 returns BLOCKED                              #
features/steps/subagent_pipeline_steps.py:130
    Then the dispatch action is "handoff"                    #
features/steps/subagent_pipeline_steps.py:142
    And task 1 status is "blocked"                           #
features/steps/subagent_pipeline_steps.py:149

  @dispatch
  Scenario: Prompt includes allowed files and rules                           #
features/subagent_pipeline.feature:96
    Given a dispatch task for "Add validation" with paths "src/a.py,src/b.py" #
features/steps/subagent_pipeline_steps.py:167
    When I compose the implementer prompt                                     #
features/steps/subagent_pipeline_steps.py:179
    Then the prompt contains "src/a.py"                                       #
features/steps/subagent_pipeline_steps.py:184
    And the prompt contains "### Rules"                                       #
features/steps/subagent_pipeline_steps.py:184

  @dispatch
  Scenario: Result JSON is parsed from agent output  #
features/subagent_pipeline.feature:103
    Given agent output with a DONE result JSON block #
features/steps/subagent_pipeline_steps.py:194
    When I parse the handoff result                  #
features/steps/subagent_pipeline_steps.py:205
    Then the parsed status is "DONE"                 #
features/steps/subagent_pipeline_steps.py:210
    And the parsed files changed include "src/x.py"  #
features/steps/subagent_pipeline_steps.py:218

  @dispatch
  Scenario: Dispatch record tracks subagent lifecycle
# features/subagent_pipeline.feature:110
    Given a subagent dispatch record for task 1 as "Implementer" with model
"sonnet" # features/steps/subagent_pipeline_steps.py:230
    When the dispatch record is completed with status "done"
# features/steps/subagent_pipeline_steps.py:235
    Then the completed record has a completion timestamp
# features/steps/subagent_pipeline_steps.py:240
    And the completed record status is "done"
# features/steps/subagent_pipeline_steps.py:245

  @dispatch
  Scenario: Dispatch aggregation computes correct totals
# features/subagent_pipeline.feature:117
    Given 3 completed dispatch records with statuses
"done,blocked,done_with_concerns" #
features/steps/subagent_pipeline_steps.py:250
    When I aggregate dispatch results
# features/steps/subagent_pipeline_steps.py:260
    Then aggregation shows 2 completed and 1 blocked
# features/steps/subagent_pipeline_steps.py:265

  @dispatch
  Scenario: Model routing loads defaults     #
features/subagent_pipeline.feature:123
    Given no pipeline config file            #
features/steps/subagent_pipeline_steps.py:275
    When I load model routing config         #
features/steps/subagent_pipeline_steps.py:281
    Then implementer simple model is "haiku" #
features/steps/subagent_pipeline_steps.py:286
    And reviewer complex model is "opus"     #
features/steps/subagent_pipeline_steps.py:291

  @dispatch
  Scenario: Agent file validation detects missing files  #
features/subagent_pipeline.feature:130
    Given a project directory with no agent files        #
features/steps/subagent_pipeline_steps.py:296
    When I validate agent files                          #
features/steps/subagent_pipeline_steps.py:302
    Then validation finds 4 errors                       #
features/steps/subagent_pipeline_steps.py:307

  @dispatch @stage2
  Scenario: Stage 2 dispatch loop executes tasks sequentially
# features/subagent_pipeline.feature:136
    Given a plan with 3 tasks
# features/steps/subagent_pipeline_steps.py:319
    And allowed paths ["src/a.py", "src/b.py"]
# features/steps/subagent_pipeline_steps.py:324
    And brief requirements ["Config MUST parse TOML", "Validation MUST reject
nulls"]  # features/steps/subagent_pipeline_steps.py:329
    When the controller creates dispatch state for "OBPI-0.18.0-06" under
"ADR-0.18.0" # features/steps/subagent_pipeline_steps.py:334
    And dispatches each task sequentially with DONE results
# features/steps/subagent_pipeline_steps.py:341
    Then all 3 tasks are completed
# features/steps/subagent_pipeline_steps.py:356
    And dispatch state is finished
# features/steps/subagent_pipeline_steps.py:363
    And each task prompt includes brief requirements
# features/steps/subagent_pipeline_steps.py:368

  @dispatch @stage2
  Scenario: Stage 2 halts on BLOCKED task after fix attempts              #
features/subagent_pipeline.feature:147
    Given a plan with 2 tasks                                             #
features/steps/subagent_pipeline_steps.py:319
    And allowed paths ["src/a.py"]                                        #
features/steps/subagent_pipeline_steps.py:324
    When the controller creates dispatch state for "OBPI-X" under "ADR-X" #
features/steps/subagent_pipeline_steps.py:334
    And task 1 is dispatched                                              #
features/steps/subagent_pipeline_steps.py:84
    And task 1 returns BLOCKED                                            #
features/steps/subagent_pipeline_steps.py:130
    Then the dispatch action is "fix"                                     #
features/steps/subagent_pipeline_steps.py:142
    When task 1 is dispatched                                             #
features/steps/subagent_pipeline_steps.py:84
    And task 1 returns BLOCKED                                            #
features/steps/subagent_pipeline_steps.py:130
    Then the dispatch action is "handoff"                                 #
features/steps/subagent_pipeline_steps.py:142
    And task 2 remains pending                                            #
features/steps/subagent_pipeline_steps.py:376
    And dispatch state has 1 blocked task                                 #
features/steps/subagent_pipeline_steps.py:386

  @dispatch @review
  Scenario: Review dispatched after DONE task with both passing  #
features/subagent_pipeline.feature:161
    Given a dispatch state with 2 tasks and 2 allowed paths      #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                                     #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE                                     #
features/steps/subagent_pipeline_steps.py:118
    Then review should be dispatched for task 1                  #
features/steps/subagent_pipeline_steps.py:398
    When both reviews pass for task 1                            #
features/steps/subagent_pipeline_steps.py:420
    Then the review action is "advance"                          #
features/steps/subagent_pipeline_steps.py:432

  @dispatch @review
  Scenario: Review not dispatched for BLOCKED task         #
features/subagent_pipeline.feature:170
    Given a dispatch state with 1 task and 1 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns BLOCKED                            #
features/steps/subagent_pipeline_steps.py:130
    Then review should not be dispatched for task 1        #
features/steps/subagent_pipeline_steps.py:409

  @dispatch @review
  Scenario: Critical spec finding triggers fix cycle       #
features/subagent_pipeline.feature:177
    Given a dispatch state with 1 task and 2 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE                               #
features/steps/subagent_pipeline_steps.py:118
    And spec review finds critical issue for task 1        #
features/steps/subagent_pipeline_steps.py:439
    Then the review action is "fix"                        #
features/steps/subagent_pipeline_steps.py:432
    And task 1 review fix count is 1                       #
features/steps/subagent_pipeline_steps.py:458

  @dispatch @review
  Scenario: Review blocked after max fix cycles            #
features/subagent_pipeline.feature:186
    Given a dispatch state with 1 task and 2 allowed paths #
features/steps/subagent_pipeline_steps.py:76
    And task 1 is dispatched                               #
features/steps/subagent_pipeline_steps.py:84
    When task 1 returns DONE                               #
features/steps/subagent_pipeline_steps.py:118
    And spec review finds critical issue for task 1        #
features/steps/subagent_pipeline_steps.py:439
    Then the review action is "fix"                        #
features/steps/subagent_pipeline_steps.py:432
    When spec review finds critical issue for task 1       #
features/steps/subagent_pipeline_steps.py:439
    Then the review action is "fix"                        #
features/steps/subagent_pipeline_steps.py:432
    When spec review finds critical issue for task 1       #
features/steps/subagent_pipeline_steps.py:439
    Then the review action is "blocked"                    #
features/steps/subagent_pipeline_steps.py:432

  @dispatch @stage3
  Scenario: Stage 3 builds verification plan from brief requirements  #
features/subagent_pipeline.feature:198
    Given a brief with 2 requirements and distinct test paths         #
features/steps/subagent_pipeline_steps.py:471
    When I prepare Stage 3 verification                               #
features/steps/subagent_pipeline_steps.py:498
    Then the verification plan has 2 scopes                           #
features/steps/subagent_pipeline_steps.py:509
    And the verification strategy is "parallel"                       #
features/steps/subagent_pipeline_steps.py:516

  @dispatch @stage3
  Scenario: Stage 3 falls back to sequential with no test paths  #
features/subagent_pipeline.feature:205
    Given a brief with 2 requirements and no test paths          #
features/steps/subagent_pipeline_steps.py:488
    When I prepare Stage 3 verification                          #
features/steps/subagent_pipeline_steps.py:498
    Then the verification strategy is "sequential"               #
features/steps/subagent_pipeline_steps.py:516

  @dispatch @stage3
  Scenario: Stage 3 records timing metrics
# features/subagent_pipeline.feature:211
    Given a verification run of 5 seconds with strategy "parallel" and 3 groups
# features/steps/subagent_pipeline_steps.py:523
    When I compute verification timing
# features/steps/subagent_pipeline_steps.py:531
    Then elapsed seconds is 5.0
# features/steps/subagent_pipeline_steps.py:543
    And time saved is greater than 0
# features/steps/subagent_pipeline_steps.py:550

  @dispatch @stage3
  Scenario: Stage 3 creates dispatch records from verification results  #
features/subagent_pipeline.feature:218
    Given a verification plan with 2 scopes and PASS results            #
features/steps/subagent_pipeline_steps.py:557
    When I create verification dispatch records                         #
features/steps/subagent_pipeline_steps.py:589
    Then 2 dispatch records are created                                 #
features/steps/subagent_pipeline_steps.py:598
    And all dispatch records have role "Verifier"                       #
features/steps/subagent_pipeline_steps.py:605
    And all dispatch records have stage 3                               #
features/steps/subagent_pipeline_steps.py:611

Feature: Task lifecycle governance # features/task_governance.feature:1
  TASK entities have lifecycle commands via gz task CLI.
  Scenario: gz task --help shows subcommands  #
features/task_governance.feature:4
    Given the workspace is initialized        # features/steps/gz_steps.py:67
    When I run the gz command "task --help"   # features/steps/gz_steps.py:209
    Then the command exits with code 0        # features/steps/gz_steps.py:220
    And the output contains "list"            # features/steps/gz_steps.py:225
    And the output contains "start"           # features/steps/gz_steps.py:225
    And the output contains "complete"        # features/steps/gz_steps.py:225
    And the output contains "block"           # features/steps/gz_steps.py:225
    And the output contains "escalate"        # features/steps/gz_steps.py:225

  Scenario: gz task list shows no tasks when none exist  #
features/task_governance.feature:14
    Given the workspace is initialized                   #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                 #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                     #
features/steps/task_governance_steps.py:28
    When I run the gz command "task list OBPI-0.1.0-01"  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                   #
features/steps/gz_steps.py:220
    And the output contains "No tasks found"             #
features/steps/gz_steps.py:225

  Scenario: gz task start transitions pending to in_progress   #
features/task_governance.feature:22
    Given the workspace is initialized                         #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                       #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                           #
features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists              #
features/steps/task_governance_steps.py:40
    When I run the gz command "task start TASK-0.1.0-01-01-01" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                         #
features/steps/gz_steps.py:220
    And the output contains "Started"                          #
features/steps/gz_steps.py:225

  Scenario: gz task complete on pending task fails                #
features/task_governance.feature:31
    Given the workspace is initialized                            #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                          #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                              #
features/steps/task_governance_steps.py:28
    When I run the gz command "task complete TASK-0.1.0-01-01-01" #
features/steps/gz_steps.py:209
    Then the command exits non-zero                               #
features/steps/gz_steps.py:215
    And the output contains "Invalid TASK transition"             #
features/steps/gz_steps.py:225

  Scenario: gz task block records reason
# features/task_governance.feature:39
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0
# features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists
# features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01"
# features/steps/task_governance_steps.py:34
    When I run the gz command "task block TASK-0.1.0-01-01-01 --reason
Missing_API" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Blocked"
# features/steps/gz_steps.py:225

  Scenario: gz task start resumes a blocked task                               #
features/task_governance.feature:49
    Given the workspace is initialized                                         #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                                       #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                                           #
features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists                              #
features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01"                  #
features/steps/task_governance_steps.py:34
    And I run the gz command "task block TASK-0.1.0-01-01-01 --reason blocked" #
features/steps/task_governance_steps.py:34
    When I run the gz command "task start TASK-0.1.0-01-01-01"                 #
features/steps/gz_steps.py:209
    Then the command exits with code 0                                         #
features/steps/gz_steps.py:220
    And the output contains "Resumed"                                          #
features/steps/gz_steps.py:225

  Scenario: gz task list --json returns valid JSON             #
features/task_governance.feature:60
    Given the workspace is initialized                         #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                       #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                           #
features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists              #
features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01"  #
features/steps/task_governance_steps.py:34
    When I run the gz command "task list OBPI-0.1.0-01 --json" #
features/steps/gz_steps.py:209
    Then the command exits with code 0                         #
features/steps/gz_steps.py:220
    And the output is valid JSON                               #
features/steps/task_governance_steps.py:45

  Scenario: gz task escalate records reason
# features/task_governance.feature:70
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0
# features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists
# features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01"
# features/steps/task_governance_steps.py:34
    When I run the gz command "task escalate TASK-0.1.0-01-01-01 --reason
Needs_human_decision" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "Escalated"
# features/steps/gz_steps.py:225

  Scenario: gz status shows task summary when tasks exist     #
features/task_governance.feature:81
    Given the workspace is initialized                        #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                      #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                          #
features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists             #
features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01" #
features/steps/task_governance_steps.py:34
    When I run the gz command "status"                        #
features/steps/gz_steps.py:209
    Then the command exits with code 0                        #
features/steps/gz_steps.py:220
    And the output contains "Tasks:"                          #
features/steps/gz_steps.py:225

  Scenario: gz status omits task section when no tasks exist  #
features/task_governance.feature:91
    Given the workspace is initialized                        #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                      #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                          #
features/steps/task_governance_steps.py:28
    When I run the gz command "status"                        #
features/steps/gz_steps.py:209
    Then the command exits with code 0                        #
features/steps/gz_steps.py:220

  Scenario: gz status shows escalated count
# features/task_governance.feature:98
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0
# features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists
# features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01"
# features/steps/task_governance_steps.py:34
    And I run the gz command "task escalate TASK-0.1.0-01-01-01 --reason
Needs_review" # features/steps/task_governance_steps.py:34
    When I run the gz command "status"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "escalated"
# features/steps/gz_steps.py:225

  Scenario: gz state --json includes task data                #
features/task_governance.feature:109
    Given the workspace is initialized                        #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                      #
features/steps/gz_steps.py:90
    And an OBPI exists for ADR-0.1.0                          #
features/steps/task_governance_steps.py:28
    And a pending task TASK-0.1.0-01-01-01 exists             #
features/steps/task_governance_steps.py:40
    And I run the gz command "task start TASK-0.1.0-01-01-01" #
features/steps/task_governance_steps.py:34
    When I run the gz command "state --json"                  #
features/steps/gz_steps.py:209
    Then the command exits with code 0                        #
features/steps/gz_steps.py:220
    And the output is valid JSON                              #
features/steps/task_governance_steps.py:45

Feature: Requirement coverage reporting CLI #
features/test_traceability.feature:1
  The gz covers command reports requirement coverage from @covers
  annotations at ADR, OBPI, and REQ granularity.
  Scenario: All-REQ coverage summary exits with code 0
# features/test_traceability.feature:5
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and a decorator-covered test exists
# features/steps/test_traceability_steps.py:13
    When I run the gz command "covers --json --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And JSON path "summary.total_reqs" equals "1"
# features/steps/gz_steps.py:252
    And JSON path "summary.covered_reqs" equals "1"
# features/steps/gz_steps.py:252

  Scenario: Filter by ADR shows only matching REQs
# features/test_traceability.feature:14
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and a decorator-covered test exists
# features/steps/test_traceability_steps.py:13
    When I run the gz command "covers ADR-0.1.0 --json --adr-dir design/adr
--test-dir tests" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And JSON path "summary.total_reqs" equals "1"
# features/steps/gz_steps.py:252

  Scenario: Plain output is one-per-line
# features/test_traceability.feature:22
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and a decorator-covered test exists
# features/steps/test_traceability_steps.py:13
    When I run the gz command "covers --plain --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And the output contains "covered"
# features/steps/gz_steps.py:225

  Scenario: Help text shows description and options  #
features/test_traceability.feature:30
    When I run the gz command "covers --help"        #
features/steps/gz_steps.py:209
    Then the command exits with code 0               #
features/steps/gz_steps.py:220
    And the output contains "--json"                 #
features/steps/gz_steps.py:225
    And the output contains "--plain"                #
features/steps/gz_steps.py:225

  Scenario: No REQs found returns empty summary
# features/test_traceability.feature:36
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    When I run the gz command "covers --json --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And JSON path "summary.total_reqs" equals "0"
# features/steps/gz_steps.py:252

  Scenario: Audit-check includes coverage section in JSON output  #
features/test_traceability.feature:43
    Given the workspace is initialized                            #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                          #
features/steps/gz_steps.py:90
    And an OBPI brief with covered and uncovered REQs exists      #
features/steps/test_traceability_steps.py:50
    When I run the gz command "adr audit-check ADR-0.1.0 --json"  #
features/steps/gz_steps.py:209
    Then JSON path "coverage.total_reqs" equals "2"               #
features/steps/gz_steps.py:252
    And JSON path "coverage.covered_reqs" equals "1"              #
features/steps/gz_steps.py:252
    And JSON path "coverage.uncovered_reqs" equals "1"            #
features/steps/gz_steps.py:252

  Scenario: Audit-check flags uncovered REQs as blocking coverage findings  #
features/test_traceability.feature:52
    Given the workspace is initialized                                      #
features/steps/gz_steps.py:67
    And ADR-0.1.0 exists                                                    #
features/steps/gz_steps.py:90
    And an OBPI brief with covered and uncovered REQs exists                #
features/steps/test_traceability_steps.py:50
    When I run the gz command "adr audit-check ADR-0.1.0 --json"            #
features/steps/gz_steps.py:209
    Then the command exits with code 1                                      #
features/steps/gz_steps.py:220
    And JSON path "coverage_findings" is not empty                          #
features/steps/test_traceability_steps.py:88
    And JSON path "passed" equals "False"                                   #
features/steps/gz_steps.py:252

Feature: Spec-test-code drift detection CLI # features/triangle_drift.feature:1
  The gz drift command detects governance drift by scanning OBPI briefs,
  test @covers references, and the active code change set.
  Scenario: No drift detected exits with code 0
# features/triangle_drift.feature:5
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and a matching test exists
# features/steps/triangle_drift_steps.py:12
    When I run the gz command "drift --json --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220
    And JSON path "summary.total_drift_count" equals "0"
# features/steps/gz_steps.py:252

  Scenario: Unlinked spec exits with code 1
# features/triangle_drift.feature:13
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and no matching test exists
# features/steps/triangle_drift_steps.py:47
    When I run the gz command "drift --json --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And JSON path "summary.unlinked_spec_count" equals "1"
# features/steps/gz_steps.py:252

  Scenario: Plain output is one-per-line
# features/triangle_drift.feature:21
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And an OBPI brief with one REQ and no matching test exists
# features/steps/triangle_drift_steps.py:47
    When I run the gz command "drift --plain --adr-dir design/adr --test-dir
tests" # features/steps/gz_steps.py:209
    Then the command exits with code 1
# features/steps/gz_steps.py:220
    And the output contains "unlinked"
# features/steps/gz_steps.py:225

  Scenario: Help text shows description and options  #
features/triangle_drift.feature:29
    When I run the gz command "drift --help"         #
features/steps/gz_steps.py:209
    Then the command exits with code 0               #
features/steps/gz_steps.py:220
    And the output contains "--json"                 #
features/steps/gz_steps.py:225
    And the output contains "--plain"                #
features/steps/gz_steps.py:225

Feature: Universal OBPI attestation doctrine surface #
features/universal_obpi_attestation.feature:1
  ADR-0.0.36 collapses the Lane & Kind Attestation Matrix to a universal binding
  rule. AGENTS.md and all mirrors must reflect the new doctrine.
  @REQ-0.0.36-01-01
  Scenario: Deprecated self-close phrase is absent from workspace AGENTS.md    #
features/universal_obpi_attestation.feature:6
    Given the workspace is initialized with agent surfaces in heavy mode       #
features/steps/gz_steps.py:74
    Then the file "AGENTS.md" does not contain "Self-closeable after evidence" #
features/steps/gz_steps.py:246

  @REQ-0.0.36-01-02
  Scenario: Universal attestation binding rule is present in workspace AGENTS.md
# features/universal_obpi_attestation.feature:11
    Given the workspace is initialized with agent surfaces in heavy mode
# features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "ALWAYS required for every OBPI
completion" # features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "regardless"
# features/steps/gz_steps.py:240

  @REQ-0.0.36-01-03
  Scenario: Lane and kind axes are retained for gate-firing scope in workspace
AGENTS.md  # features/universal_obpi_attestation.feature:17
    Given the workspace is initialized with agent surfaces in heavy mode
# features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "Gate 3"
# features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "Gate 4"
# features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "which gates"
# features/steps/gz_steps.py:240

  @REQ-0.0.36-01-04
  Scenario: GHI #342 and ADR-0.0.36 cited inline in workspace AGENTS.md  #
features/universal_obpi_attestation.feature:24
    Given the workspace is initialized with agent surfaces in heavy mode #
features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "GHI #342"                        #
features/steps/gz_steps.py:240
    And the file "AGENTS.md" contains "ADR-0.0.36"                       #
features/steps/gz_steps.py:240

  @REQ-0.0.36-01-05
  Scenario: Workspace AGENTS.md mirror reflects the universal attestation rule
# features/universal_obpi_attestation.feature:30
    Given the workspace is initialized with agent surfaces in heavy mode
# features/steps/gz_steps.py:74
    Then the file "AGENTS.md" contains "Universal OBPI Attestation"
# features/steps/gz_steps.py:240
    And the file "AGENTS.md" does not contain "Lane & Kind & Sensitivity
Attestation Matrix" # features/steps/gz_steps.py:246

  @REQ-0.0.36-02-01 @REQ-0.0.36-02-02 @REQ-0.0.36-02-04
  Scenario: Feature-lite OBPI emit-receipt fails without human attestation
fields             # features/universal_obpi_attestation.feature:38
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And I run the gz command "specify demo --parent ADR-0.1.0-f"
# features/steps/task_governance_steps.py:34
    When I emit-receipt for OBPI "OBPI-0.1.0-01-demo" with attestor "human:jeff"
and evidence # features/steps/universal_obpi_attestation_steps.py:30
      """
      {"value_narrative":"v","key_proof":"k"}
      """
    Then it exits with code 1
# features/steps/obpi_lock_steps.py:80
    And the output contains "human_attestation"
# features/steps/gz_steps.py:225

  @REQ-0.0.36-02-03 @REQ-0.0.36-02-05
  Scenario: Feature-lite OBPI emit-receipt succeeds with full attestation
evidence            # features/universal_obpi_attestation.feature:54
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And ADR-0.1.0 exists
# features/steps/gz_steps.py:90
    And I run the gz command "specify demo --parent ADR-0.1.0-f"
# features/steps/task_governance_steps.py:34
    When I emit-receipt for OBPI "OBPI-0.1.0-01-demo" with attestor "human:jeff"
and evidence # features/steps/universal_obpi_attestation_steps.py:30
      """
      {"value_narrative":"v","key_proof":"k","human_attestation":true,"attestati
on_text":"bdd test attestation","attestation_date":"2026-01-01"}
      """
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

Feature: gz upgrade surface-only refresh # features/upgrade.feature:1
  gz upgrade refreshes .gzkit/<surface>/ from the installed wheel's package
  data. Narrower than gz init --update: no manifest mutation, no scaffolder
  hooks, no agent sync. Surface content only.
  @REQ-0.0.32-14-01
  Scenario: gz upgrade is a registered subcommand  # features/upgrade.feature:8
    Given the workspace is initialized             #
features/steps/gz_steps.py:67
    When I run the gz command "upgrade --help"     #
features/steps/gz_steps.py:209
    Then the command exits with code 0             #
features/steps/gz_steps.py:220
    And the output contains "upgrade"              #
features/steps/gz_steps.py:225
    And the output contains "--surface"            #
features/steps/gz_steps.py:225

  @REQ-0.0.32-14-01
  Scenario: upgrade subcommand flag --force is available  #
features/upgrade.feature:16
    Given the workspace is initialized                    #
features/steps/gz_steps.py:67
    When I run the gz command "upgrade --help"            #
features/steps/gz_steps.py:209
    Then the command exits with code 0                    #
features/steps/gz_steps.py:220
    And the output contains "--force"                     #
features/steps/gz_steps.py:225
    And the output contains "--dry-run"                   #
features/steps/gz_steps.py:225

  @REQ-0.0.32-14-02
  Scenario: unknown surface name exits 1 with token named  #
features/upgrade.feature:24
    Given the workspace is initialized                     #
features/steps/gz_steps.py:67
    When I run the gz command "upgrade --surface badtoken" #
features/steps/gz_steps.py:209
    Then the command exits with code 1                     #
features/steps/gz_steps.py:220
    And the output contains "badtoken"                     #
features/steps/gz_steps.py:225

  @REQ-0.0.32-14-02
  Scenario: unknown token in comma list exits 1                        #
features/upgrade.feature:31
    Given the workspace is initialized                                 #
features/steps/gz_steps.py:67
    When I run the gz command "upgrade --surface skills,nosuchsurface" #
features/steps/gz_steps.py:209
    Then the command exits with code 1                                 #
features/steps/gz_steps.py:220
    And the output contains "nosuchsurface"                            #
features/steps/gz_steps.py:225

  @REQ-0.0.32-14-03
  Scenario: EDITED conflict detection without --force exits 3
# features/upgrade.feature:38
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the project has an EDITED skill at
".gzkit/skills/airlineops-parity-scan/SKILL.md" #
features/steps/upgrade_steps.py:40
    When I run the gz command "upgrade --surface skills"
# features/steps/gz_steps.py:209
    Then the command exits with code 3
# features/steps/gz_steps.py:220

  @REQ-0.0.32-14-04
  Scenario: --force overwrites EDITED artifacts and exits 0
# features/upgrade.feature:45
    Given the workspace is initialized
# features/steps/gz_steps.py:67
    And the project has an EDITED skill at
".gzkit/skills/airlineops-parity-scan/SKILL.md" #
features/steps/upgrade_steps.py:40
    When I run the gz command "upgrade --surface skills --force"
# features/steps/gz_steps.py:209
    Then the command exits with code 0
# features/steps/gz_steps.py:220

  @REQ-0.0.32-14-06
  Scenario: bootstrap retrofit when .gzkit/skills/ does not exist  #
features/upgrade.feature:52
    Given the workspace is initialized                             #
features/steps/gz_steps.py:67
    And the .gzkit/skills directory is removed                     #
features/steps/upgrade_steps.py:47
    When I run the gz command "upgrade --surface skills"           #
features/steps/gz_steps.py:209
    Then ".gzkit/skills" exists after the command                  #
features/steps/upgrade_steps.py:54

  @REQ-0.0.32-14-05
  Scenario: --dry-run on a fresh workspace reports and exits 0  #
features/upgrade.feature:59
    Given the workspace is initialized                          #
features/steps/gz_steps.py:67
    When I run the gz command "upgrade --dry-run"               #
features/steps/gz_steps.py:209
    Then the command exits with code 0                          #
features/steps/gz_steps.py:220

  @REQ-0.0.32-14-07
  Scenario: idempotent second run exits 0  # features/upgrade.feature:65
    Given the workspace is initialized     # features/steps/gz_steps.py:67
    When I run the gz command "upgrade"    # features/steps/gz_steps.py:209
    Then the command exits with code 0     # features/steps/gz_steps.py:220
    When I run the gz command "upgrade"    # features/steps/gz_steps.py:209
    Then the command exits with code 0     # features/steps/gz_steps.py:220

  @REQ-0.0.32-14-08
  Scenario: manifest.json not mutated by gz upgrade  #
features/upgrade.feature:73
    Given the workspace is initialized               #
features/steps/gz_steps.py:67
    And I record the manifest checksum               #
features/steps/upgrade_steps.py:19
    When I run the gz command "upgrade"              #
features/steps/gz_steps.py:209
    Then the manifest checksum is unchanged          #
features/steps/upgrade_steps.py:28

  @REQ-0.0.32-14-09
  Scenario: manpage exists and docs build passes     #
features/upgrade.feature:80
    Given the workspace is initialized               #
features/steps/gz_steps.py:67
    When I run the gz command "validate --documents" #
features/steps/gz_steps.py:209
    Then the command exits with code 0               #
features/steps/gz_steps.py:220

Feature: gz validate --distribution T0 static audit #
features/validate_distribution.feature:1
  Validates that the distribution invariant validator correctly detects drift
  between pyproject.toml wheel includes, the baseline manifest, and on-disk
  canonical surface trees (ADR-0.0.32-07).
  @REQ-0.0.32-07-01 @REQ-0.0.32-07-07
  Scenario: --distribution flag is registered and appears in gz validate --help
# features/validate_distribution.feature:8
    When I run "gz validate --help"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80
    And the output contains "--distribution"
# features/steps/gz_steps.py:225

  @REQ-0.0.32-07-01 @REQ-0.0.32-07-02 @REQ-0.0.32-07-03 @REQ-0.0.32-07-05
@REQ-0.0.32-07-11
  Scenario: ON_DISK_NOT_INCLUDED drift detected exits 3
# features/validate_distribution.feature:18
    Given a minimal project with a skills surface file not covered by any
include glob # features/steps/validate_distribution_steps.py:19
    When I run "gz validate --distribution"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80
    And the output contains "ON_DISK_NOT_INCLUDED"
# features/steps/gz_steps.py:225
    And the output contains "pyproject.toml"
# features/steps/gz_steps.py:225

  @REQ-0.0.32-15-01 @REQ-0.0.32-15-02 @REQ-0.0.32-15-03
  Scenario: --regenerate rewrites manifest and validate exits 0  #
features/validate_distribution.feature:28
    Given a minimal clean distribution baseline project          #
features/steps/validate_distribution_steps.py:44
    When I run "gz validate --distribution --regenerate"         #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                    #
features/steps/obpi_lock_steps.py:80
    And the output contains "Baseline regenerated"               #
features/steps/gz_steps.py:225

  @REQ-0.0.32-15-01
  Scenario: --regenerate flag appears in gz validate --help  #
features/validate_distribution.feature:35
    When I run "gz validate --help"                          #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                #
features/steps/obpi_lock_steps.py:80
    And the output contains "--regenerate"                   #
features/steps/gz_steps.py:225

Feature: gz validate --receipt-shape enforcement #
features/validate_receipt_shape.feature:1
  ADR-0.0.36 deprecates three legacy receipt shapes for obpi_receipt_emitted
  events dated on or after the cutoff date (2026-04-26). The --receipt-shape
  validator enforces these rules fail-closed on post-cutoff receipts and
  provides a waiver path for pre-cutoff legacy receipts.
  @REQ-0.0.36-03-01
  Scenario: --receipt-shape flag appears in gz validate --help  #
features/validate_receipt_shape.feature:8
    When I run "gz validate --help"                             #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                   #
features/steps/obpi_lock_steps.py:80
    And the output contains "--receipt-shape"                   #
features/steps/gz_steps.py:225

  @REQ-0.0.36-03-02
  Scenario: Post-cutoff receipt with attestation_requirement optional fails
# features/validate_receipt_shape.feature:14
    Given a minimal project with a post-cutoff receipt having
attestation_requirement optional #
features/steps/validate_receipt_shape_steps.py:89
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-03-03
  Scenario: Post-cutoff receipt with unprefixed obpi_completion fails
# features/validate_receipt_shape.feature:20
    Given a minimal project with a post-cutoff receipt having obpi_completion
completed # features/steps/validate_receipt_shape_steps.py:108
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-03-04
  Scenario: Post-cutoff receipt with agent attestor fails
# features/validate_receipt_shape.feature:26
    Given a minimal project with a post-cutoff receipt having attestor
agent:claude-code # features/steps/validate_receipt_shape_steps.py:127
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-03-05
  Scenario: Pre-cutoff receipt with waiver present passes
# features/validate_receipt_shape.feature:32
    Given a minimal project with a pre-cutoff receipt and a matching waiver
entry # features/steps/validate_receipt_shape_steps.py:146
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-03-06
  Scenario: receipt-shape scope runs cleanly on a clean repository  #
features/validate_receipt_shape.feature:38
    When I run "gz validate --receipt-shape"                        #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                       #
features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-03-07
  Scenario: gz validate manpage documents --receipt-shape scope  #
features/validate_receipt_shape.feature:43
    When I run "gz validate --help"                              #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                    #
features/steps/obpi_lock_steps.py:80
    And the output contains "--receipt-shape"                    #
features/steps/gz_steps.py:225

  @REQ-0.0.36-04-03
  Scenario: Waiver entry with bad added_under is rejected
# features/validate_receipt_shape.feature:49
    Given a minimal project with a pre-cutoff receipt and a waiver entry with
bad added_under # features/steps/validate_receipt_shape_steps.py:184
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 3
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-04-04
  Scenario: Pre-cutoff receipt with waiver present passes silently
# features/validate_receipt_shape.feature:55
    Given a minimal project with a pre-cutoff receipt and a matching waiver
entry having valid added_under #
features/steps/validate_receipt_shape_steps.py:223
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

  @REQ-0.0.36-04-04
  Scenario: Pre-cutoff receipt without waiver entry is warn-only
# features/validate_receipt_shape.feature:61
    Given a minimal project with a pre-cutoff receipt and a waiver file lacking
that receipt # features/steps/validate_receipt_shape_steps.py:264
    When I run "gz validate --receipt-shape"
# features/steps/obpi_lock_steps.py:67
    Then it exits with code 0
# features/steps/obpi_lock_steps.py:80

Feature: gz validate --waiver-ratchet honesty contract #
features/waiver_ratchet.feature:1
  ADR-0.0.73 Boundary Invariant #8 requires every registered waiver/grandfather/
  baseline surface that gates a gz check step to declare exactly one honesty
  mechanism (closed-set lock, dated cutover, or monotonic shrink-ratchet), so a
  waiver list cannot silently launder "not built" into "attested green". The
  --waiver-ratchet scope fails closed (exit 3) on any unratcheted or
unregistered
  surface (OBPI-0.0.73-09).
  @REQ-0.0.73-09-04 @REQ-0.0.73-09-05
  Scenario: --waiver-ratchet flag is wired into gz validate and documented  #
features/waiver_ratchet.feature:11
    When I run "gz validate --help"                                         #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                               #
features/steps/obpi_lock_steps.py:80
    And the output contains "--waiver-ratchet"                              #
features/steps/gz_steps.py:225

  @REQ-0.0.73-09-02
  Scenario: A registry whose every surface is ratcheted passes         #
features/waiver_ratchet.feature:17
    Given a project where every registered waiver surface is ratcheted #
features/steps/waiver_ratchet_steps.py:38
    When I run "gz validate --waiver-ratchet"                          #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 0                                          #
features/steps/obpi_lock_steps.py:80

  @REQ-0.0.73-09-01 @REQ-0.0.73-09-03
  Scenario: A shrink-ratchet waiver list grown past its baseline fails closed  #
features/waiver_ratchet.feature:24
    Given a project with a waiver surface grown past its committed baseline    #
features/steps/waiver_ratchet_steps.py:56
    When I run "gz validate --waiver-ratchet"                                  #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 3                                                  #
features/steps/obpi_lock_steps.py:80

  @REQ-0.0.73-09-06
  Scenario: An unregistered waiver data file fails closed as a silent bypass  #
features/waiver_ratchet.feature:30
    Given a project with an unregistered waiver data file                     #
features/steps/waiver_ratchet_steps.py:74
    When I run "gz validate --waiver-ratchet"                                 #
features/steps/obpi_lock_steps.py:67
    Then it exits with code 3                                                 #
features/steps/obpi_lock_steps.py:80

60 features passed, 0 failed, 1 skipped
367 scenarios passed, 0 failed, 35 skipped
1849 steps passed, 0 failed, 136 skipped
Took 0min 13.039s

LOG_WARNING:gzkit.triangle: Malformed REQ line (skipped): - [ ]
REQ-FIXTURE-01-01: gate fires.
LOG_WARNING:gzkit.triangle: Malformed REQ line (skipped): - [ ]
REQ-FIXTURE-01-01: gate fires.
LOG_WARNING:gzkit.governance.trust_audits.receipt_shape: Pre-cutoff receipt
'OBPI-bad-added-under-01' has deprecated shape but is not in the waiver list.
This is documentation drift — the doctrine binds going forward only.
LOG_WARNING:gzkit.governance.trust_audits.receipt_shape: Pre-cutoff receipt
'OBPI-unwaivered-pre-cutoff-01' has deprecated shape but is not in the waiver
list. This is documentation drift — the doctrine binds going forward only.

  ✓ Gate 4 (BDD): PASS
  ⚠ Gate 5 (Human): PENDING (manual)
