Spec onboarding

v7 ships a typed SpecOnboardingMethod in Haft Core. The method is product knowledge: it declares which YAML fields a SpecSection must carry, in which order, with which structural Checks. Surfaces (MCP plugin and CLI subcommand; an alpha desktop wizard exists outside the production envelope) all consume the same WorkflowIntent shape — they render and dispatch, they do not invent semantics.

The method is implemented in internal/project/specflow/. Phases are declarative Go values composed from a reusable vocabulary of typed Checks (RequireField, RequireStatementType, RequireClaimLayer, RequireValidUntil, RequireTermDefined, RequireGuardLocation, RequireBoundaryPerspectives). The same Check vocabulary feeds the parser-level haft spec check, so forward (method) and backward (validator) directions never drift.

The typed flow

Surfaces ask Core for the next step:

haft_spec_section(action="next_step")    # MCP plugin
haft spec onboard --json                  # CLI

The response is a WorkflowIntent:

FieldMeaning
phaseStable id, e.g. target.environment.draft. Do not skip ahead.
prompt_for_userQuestion the operator answers verbatim.
context_for_agentReasoning context for the host agent. Names which FPF patterns to apply.
expected_fieldsYAML keys the new SpecSection MUST carry.
checksStructural validators that will run on the result.
blocking_findingsNon-empty when an existing section fails validation. Resolve before drafting.
terminaltrue when every registered phase is satisfied.

Phase registry

v7 ships ten phases: three target-system, seven enabling-system. Order matters — each phase declares its depends_on, and Core advances only when dependencies are satisfied with active baselined sections.

Target-system spine

  • target.environment.draft — what changes in the world when the target system runs. Apply FRAME-09 + CHR-12 in reasoning.
  • target.role.draft — what role the target plays in producing the environment change. FRAME-09 strict distinction quad.
  • target.boundary.draft — in-scope vs out-of-scope, plus four CHR-10 boundary perspectives (Law / Admissibility / Deontics / Evidence) named in target_refs.

Enabling-system spine

  • enabling.architecture.draft — layered architecture of the team / code / infra that builds and operates the target. Apply FPF Signature Stack.
  • enabling.work_methods.draft — how each load-bearing artifact is produced. FRAME-09 + X-STATEMENT-TYPE.
  • enabling.effect_boundaries.draft — which actors and surfaces may mutate what. CHR-10 boundary corners.
  • enabling.agent_policy.draft — supported host agents and autonomy bounds.
  • enabling.commission_policy.draft — how WorkCommissions are authorized, scoped, retired.
  • enabling.runtime_policy.draft — which surface owns runtime lifecycle, isolation, observability.
  • enabling.evidence_policy.draft — admissible evidence kinds, minimum congruence level, refresh triggers. evidence_required[].kind names a guard location.

The phase loop

  1. Call haft_spec_section(action="next_step").
  2. Read prompt_for_user verbatim to the operator.
  3. Read context_for_agent. Retrieve every named FPF pattern via haft_query(action="fpf", query="FRAME-09") etc. Apply them in your reasoning — never inside the YAML.
  4. Read repo carriers (README, manifests, source structure) to ground the section in reality.
  5. Draft the YAML spec-section block in the carrier file named by document_kind. Populate every field in expected_fields, including the SoTA fields below.
  6. Run haft spec check. Resolve any structural finding.
  7. Promote to active: flip status: draft → active after the operator approves the load-bearing claims.
  8. Record the baseline: haft_spec_section(action="approve", section_id=<id>). Without a baseline the section reports spec_section_needs_baseline and the next phase stays blocked.
  9. Repeat from step 1 until terminal: true.

SoTA fields on every claim

  • statement_type — one of definition, admissibility, duty, evidence, explanation. Mixed types are an L1 error per X-STATEMENT-TYPE; decompose.
  • claim_layer — one of object, description, carrier, work, evidence.
  • valid_until — RFC3339 or YYYY-MM-DD. Refresh discipline lives at the claim level, not only at evidence.
  • target_refs — for boundary sections, enumerate at least four stakeholder perspectives (CHR-10 corners).
  • evidence_required[].kind — for invariants and illegal states, declare guard location: type, L1..L4, DB, E2E, manual.

Drift, staleness, and triage

Once a section is approved (baseline recorded), hand-edits to the carrier surface as spec_section_drifted in haft spec check. An active section whose valid_until is past today surfaces as spec_section_stale. The operator picks one of three actions:

# Intentional evolution: lock the new hash with a recorded reason
haft_spec_section(action="rebaseline", section_id="TS.role.001",
                  reason="role narrowed after stakeholder review")

# Section needs review: drop the baseline, return to the onboarding loop
haft_spec_section(action="reopen", section_id="TS.role.001",
                  reason="external dependency changed")

# Mistaken edit: revert the YAML in the carrier; haft spec check goes clean

What never goes into the carrier

  • FPF citations (FRAME-XX, CHR-XX, X-XXX). Patterns shape your reasoning; the carrier records the resolved claim only.
  • Speculative fields the phase did not request.
  • Active status without an explicit operator approval.

Plugin-mode parity

The CLI haft spec onboard --json output and the MCP haft_spec_section(action="next_step") response share an identical WorkflowIntent shape. A contract test asserts field-by-field parity. Whatever the host agent sees in plugin mode, the operator sees in the CLI.