State Model - AFPS Tracker

Review the synthesized logical domain model for the approved AFPS Tracker first repo read-through. This page proposes the canonical domain model and model-tree manifest, but no canonical model files or flow-tree model attachment have been written yet.

Statusreviewsingle approval gate
Topicafps-tracker
Product Pathresearch/afps-tracker
Continuation$state-model afps-tracker

Decision Summary

Recommended approval: approve the logical model as the AFPS Tracker substrate for UX variation work. The model is coherent across all five framework passes and keeps AFPS Tracker read-first, source-native, and subordinate to canonical AFPS artifacts.

Primary aggregateProductPath owns selected-path interpretation, source-backed claims, evidence, provenance, diagnostics, and handoff eligibility.
Thin portfolio rootPortfolioSnapshot and path refs support orientation and unresolved/excluded path visibility without making repo scan the central aggregate.
Trust spineTrustEnvelope preserves trust level, parser confidence, source confidence, warnings, and diagnostic refs separately.
Boundary ruleInactive, promoted, out-of-scope, unsupported, contradicted, and blocked states suppress normal next-command routing.

Framework Evidence

FrameworkIntermediateDecision Contribution
Event modelingdesign/afps-tracker/state-model-afps-tracker/event-modeling.mdTimeline of scan, manifest interpretation, selection, source resolution, diagnostics, evidence/provenance inspection, handoff generation, copy, and boundary explanation.
Domain modeldesign/afps-tracker/state-model-afps-tracker/domain-model.mdDDD spine with ProductPath as the main aggregate root and Portfolio as a thin orientation aggregate.
State machinedesign/afps-tracker/state-model-afps-tracker/state-machine.mdLifecycles for portfolio, product path, source artifact, evidence link, provenance record, diagnostic issue, and handoff summary.
Data modeldesign/afps-tracker/state-model-afps-tracker/data-model.mdLogical ERD, attributes, relationships, read models, and integrity rules, including explicit unresolved refs.
Contract mapdesign/afps-tracker/state-model-afps-tracker/contract-map.mdLogical command/query contracts and response shapes with trust envelopes, diagnostic refs, and boundary suppression.

Proposed Domain Model

The following is the proposed content for design/afps-tracker/domain-model-afps-tracker.md. It will be written only after this page is approved through compiled YAML.

---
skill: state-model
status: proposed
topic: afps-tracker
product_path: research/afps-tracker
source_command: "$state-model afps-tracker"
flow_tree: design/afps-tracker/flow-tree-afps-tracker.yaml
model_tree: design/afps-tracker/model-tree-afps-tracker.yaml
alignment_page: alignment/state-model-afps-tracker.html
---

# AFPS Tracker Domain Model

## Scope

This logical model defines the entities, value objects, state machines, events, commands, read models, policies, and contracts for the approved AFPS Tracker first repo read-through. The model is anchored to the approved flow-tree nodes:

- `uf-orient-portfolio`
- `uf-verify-selected-path`
- `uf-inspect-source-provenance`
- `uf-recover-diagnostics`
- `uf-handoff-resume`
- `uf-explain-boundaries`

AFPS Tracker remains read-first and source-native. Canonical truth stays in AFPS workflow artifacts: `research/.progress.yaml`, scoped research documents, alignment pages, canonical design artifacts, archive snapshots, and working packets where relevant. This model does not define storage, real endpoints, URLs, auth, migrations, indexes, deployment, file watchers, or runtime mechanics.

## Ubiquitous-Language Glossary

| Term | Meaning |
| --- | --- |
| Repository context | Logical context for one repo-local AFPS read-through. |
| Portfolio snapshot | Source-derived grouping of active, parallel, inactive, excluded, and unresolved product paths for a scan. |
| Portfolio path ref | A source active/excluded/unresolved path reference, whether or not it resolves to a ProductPath. |
| Product path | A source-derived AFPS research/product path interpreted from canonical AFPS artifacts. |
| Source artifact | A canonical AFPS file or generated artifact used as evidence, provenance, diagnostics, or handoff source. |
| Source-backed claim | A displayed or handoffable assertion whose support must be traceable or explicitly caveated. |
| Evidence link | Logical link from a claim to a source artifact, field, section, approval record, or source hint. |
| Provenance record | Approval, canonicalization, amendment, archive, review, or working-packet lineage. |
| Diagnostic issue | Trust degradation or recovery item affecting source, claim, path, portfolio, or handoff level. |
| Trust envelope | Response wrapper carrying trust level, parser confidence, source confidence, warnings, and diagnostic refs. |
| Parser confidence | Confidence in extraction or mapping from a source artifact. |
| Source confidence | Confidence that the underlying source/provenance supports the claim. |
| Handoff eligibility | Derived availability of clean, warning, blocked, explanatory, promoted-boundary, or out-of-scope output. |
| Boundary kind | Why a path or request cannot produce normal active AFPS routing. |
| Next-command suppression | Explicit suppression of normal next-command output under blocked, promoted, inactive, contradicted, unsupported, or out-of-scope states. |

## Entities And Value Objects

| Element | Kind | Key attributes | Relationships | Flow bindings |
| --- | --- | --- | --- | --- |
| RepositoryContext | entity | repository_ref, scan_started_at, scan_completed_at, scan_state, source_health, warnings | Owns zero-or-one PortfolioSnapshot and discovered source artifacts/diagnostics for the read-through. | `uf-orient-portfolio` triggers/displays; `uf-recover-diagnostics` displays |
| PortfolioSnapshot | aggregate-root | snapshot_id, repository_ref, progress_manifest_ref, snapshot_state, active_path_refs, excluded_path_refs, parser_confidence, source_confidence | Has many PortfolioPathRefs and ProductPaths. | `uf-orient-portfolio` reads/displays; `uf-explain-boundaries` reads; `uf-recover-diagnostics` transitions |
| PortfolioPathRef | entity | path_ref_id, path_id, scope_path, source_status, resolution_state, boundary_kind, diagnostic_refs | Resolves to zero-or-one ProductPath and may raise diagnostics. | `uf-orient-portfolio` displays; `uf-explain-boundaries` displays; `uf-recover-diagnostics` writes |
| ProductPath | aggregate-root | path_id, label, scope_path, source_status, pipeline_stage, reason, next_skill, path_state, trust_level, handoff_eligibility, boundary_kind | Owns source artifacts, claims, provenance, diagnostics, and handoff summaries. | `uf-orient-portfolio`, `uf-verify-selected-path`, `uf-handoff-resume`, `uf-explain-boundaries` |
| SourceArtifact | entity | artifact_id, artifact_ref, artifact_kind, source_role, read_status, approval_status, freshness_state, parser_confidence, source_confidence, warnings | Supports claims and provenance; can raise diagnostics. | `uf-verify-selected-path`, `uf-inspect-source-provenance`, `uf-recover-diagnostics` |
| SourceBackedClaim | entity | claim_id, path_id, claim_kind, claim_text, support_status, parser_confidence, source_confidence, trust_level, derived_from, warnings | Has zero-or-many EvidenceLinks and Diagnostics. | `uf-verify-selected-path`, `uf-inspect-source-provenance`, `uf-recover-diagnostics`, `uf-handoff-resume` |
| EvidenceLink | entity | evidence_link_id, claim_id, evidence_ref, resolved_ref, resolution_state, support_status, read_status, source_hint, warnings | Links a claim to zero-or-one resolved SourceArtifact. | `uf-inspect-source-provenance`, `uf-recover-diagnostics` |
| ProvenanceRecord | entity | provenance_id, path_id, provenance_ref, provenance_kind, approval_state, decision_summary, canonical_target, archive_refs, confidence, warnings | Supports claims, artifacts, and handoff confidence. | `uf-inspect-source-provenance`, `uf-recover-diagnostics`, `uf-handoff-resume` |
| DiagnosticIssue | entity | issue_id, issue_kind, severity, level, state, affected_refs, affected_claims, affected_paths, handoff_impact, recommended_manual_check | Attaches at the narrowest source, claim, path, portfolio, or handoff level. | `uf-recover-diagnostics`, `uf-orient-portfolio`, `uf-handoff-resume` |
| HandoffSummary | entity/read-model output | summary_id, path_id, handoff_type, copy_state, confidence, next_skill, branch_state_explanation, handoff_boundary, included refs, blocked_reasons | Reads path, claims, evidence, provenance, and diagnostics; may be copied only when policies permit. | `uf-handoff-resume`, `uf-explain-boundaries` |
| ArtifactRef | value-object | path, section_hint, artifact_kind, source_role | Used by source, evidence, provenance, and diagnostics. | `uf-verify-selected-path`, `uf-inspect-source-provenance` |
| TrustLevel | value-object | clean, warning, blocked, partial, inferred, unsupported, contradicted, source-backed, explanatory, out-of-scope | Used by paths, claims, diagnostics, and handoffs. | `uf-recover-diagnostics`, `uf-handoff-resume` |
| ParserConfidence | value-object | high, medium, low, unknown, rationale | Kept separate from source confidence. | `uf-orient-portfolio`, `uf-verify-selected-path`, `uf-recover-diagnostics` |
| SourceConfidence | value-object | confirmed, partial, review-only, unsupported, contradicted, missing, unknown | Kept separate from parser confidence. | `uf-inspect-source-provenance`, `uf-recover-diagnostics`, `uf-handoff-resume` |
| HandoffEligibility | value-object | clean-copy, warning-copy, blocked, explanatory, promoted-boundary, out-of-scope, unavailable | Derived by policy from path state, trust, diagnostics, and boundary kind. | `uf-handoff-resume`, `uf-explain-boundaries` |
| BoundaryKind | value-object | inactive, archived, deferred, revisit-candidate, promoted, out-of-scope, unsupported, unresolved | Suppresses normal active routing. | `uf-explain-boundaries`, `uf-handoff-resume` |

## State Machines

### PortfolioSnapshot

States: `unscanned`, `scanning`, `no-afps-state`, `manifest-malformed`, `paths-discovered`, `partial`, `source-backed`, `blocked`.

Key transitions: `ScanPortfolio` moves `unscanned -> scanning`; missing source moves to `no-afps-state`; malformed manifest moves to `manifest-malformed`; discovered paths move to `paths-discovered`; unresolved refs or non-blocking diagnostics move to `partial`; classified clean source state moves to `source-backed`; blocking portfolio diagnostics move to `blocked`.

UI mappings: empty/no-manifest overview, loading portfolio overview, grouped path rows, partial/warning badges, blocking portfolio diagnostic, clean portfolio overview.

### ProductPath

States: `undiscovered`, `discovered`, `selected`, `source-inventorying`, `source-backed`, `partial`, `warning`, `blocked`, `inactive`, `promoted-boundary`, `out-of-scope`.

Key transitions: discovered source record creates `discovered`; excluded or inactive source status moves to `inactive`; promoted state moves to `promoted-boundary`; selection moves to `selected`; source inventory moves to `source-inventorying`; supported claims move to `source-backed`; unresolved non-core support moves to `partial`; warning diagnostics move to `warning`; contradictions, unreadable core source, malformed core source, or next-skill inconsistency move to `blocked`; out-of-scope requests move to `out-of-scope`.

UI mappings: portfolio row, selected-path loading frame, clean detail, partial detail, warning caveats, blocked detail, inactive explanation, promoted boundary explanation, out-of-scope explanation.

### SourceArtifact

States: `referenced`, `resolving`, `found`, `parsed`, `confirmed`, `review-only`, `amended`, `archived`, `missing`, `unreadable`, `malformed`, `stale`, `unsupported`.

Key transitions: source inventory or evidence resolution moves to `resolving`; readable artifacts move to `found`; parsed facts move to `parsed`; confirmed provenance moves to `confirmed`; review-state provenance moves to `review-only`; amendment or archive lineage moves to `amended` or `archived`; missing, permission, malformed, stale, or unsupported support moves to the corresponding diagnostic state.

### EvidenceLink

States: `unresolved`, `resolving`, `resolved`, `supports-claim`, `partially-supports-claim`, `contradicts-claim`, `inaccessible`.

Key transitions: inspection moves unresolved links to resolving; resolved source refs move to `resolved`; claim evaluation moves to supported, partial, or contradictory support; inaccessible source preview moves to `inaccessible`; source review can return partial or contradictory links to resolving.

### ProvenanceRecord

States: `missing`, `discovered`, `review`, `confirmed`, `amended`, `archived`, `partial`, `contradicted`.

Key transitions: discovered records move to `discovered`; confirmed approval/canonical target moves to `confirmed`; unapproved review state moves to `review`; missing targets move to `missing`; amendments and archives preserve lineage; partial or contradictory provenance degrades trust and can return to discovered after source review.

### DiagnosticIssue

Severity states: `none`, `raised`, `classified`, `warning`, `blocked`, `partial`, `resolved-by-source-review`.

Affected-level dimension: `source-level`, `claim-level`, `path-level`, `portfolio-level`, `handoff-level`.

Rule: severity and affected level are separate dimensions. Diagnostics degrade trust at the narrowest affected scope first, then path and handoff levels only when needed.

### HandoffSummary

States: `unavailable`, `requested`, `clean-copy`, `warning-copy`, `blocked`, `explanatory`, `promoted-boundary`, `out-of-scope`, `copied`, `suppressed`.

Key transitions: `GenerateHandoff` moves unavailable summaries to requested; clean source-backed selected paths move to clean-copy; non-blocking warnings move to warning-copy; blocking diagnostics move to blocked; inactive, promoted, or out-of-scope states move to explanatory/boundary states; permitted copy moves to copied; blocked copy moves to suppressed.

## Events

Core events: `RepositoryScanStarted`, `ProgressManifestLocated`, `ProgressManifestMissing`, `ProgressManifestParsed`, `ProgressManifestMalformed`, `ActivePathsNormalized`, `ProductPathRecordDiscovered`, `ProductPathRecordExcluded`, `ActivePathReferenceUnresolved`, `ProductPathSelected`, `ScopedSourceInventoryStarted`, `ScopedResearchArtifactResolved`, `ScopedResearchArtifactMissing`, `EvidenceRefResolutionStarted`, `EvidenceRefResolved`, `EvidenceRefUnresolved`, `ClaimSupportEvaluated`, `ProvenanceRecordDiscovered`, `ProvenanceRecordConfirmed`, `ProvenanceRecordReviewOnly`, `ProvenanceRecordArchived`, `ProvenanceRecordMissing`, `DiagnosticIssueRaised`, `DiagnosticIssueClassified`, `TrustLevelChanged`, `HandoffEligibilityChanged`, `ClaimInspectionRequested`, `SourcePreviewPrepared`, `SourcePreviewUnavailable`, `NextSkillCandidateDetected`, `NextSkillCandidateSuppressed`, `HandoffGenerationRequested`, `HandoffSummaryPrepared`, `CleanCopyPrepared`, `WarningCopyPrepared`, `HandoffBlocked`, `HandoffCopyRequested`, `HandoffCopied`, `HandoffCopySuppressed`, `BoundaryExplanationRequested`, `PromotedBoundaryDetected`, `InactivePathExplained`, `OutOfScopeRequestExplained`.

## Commands And Queries

Commands: `ScanPortfolio`, `SelectProductPath`, `EvaluateDiagnostics`, `GenerateHandoff`, `CopyHandoff`.

Queries: `ListPortfolioPaths`, `GetSelectedPathDetail`, `InspectEvidenceRef`, `InspectProvenance`, `GetDiagnostics`, `ExplainBoundary`.

Shared logical types: `RepositoryContextInput`, `ProductPathSelector`, `ArtifactRef`, `ClaimRef`, `TrustEnvelope`, `DiagnosticRef`, `HandoffBoundary`.

Every trust-bearing response must carry `trust_level`, `parser_confidence`, `source_confidence`, `warnings[]`, and `diagnostic_refs[]`. Every claim-displaying response must carry `claim_id`, support status, evidence/provenance refs, and caveats. Boundary and handoff contracts must expose `normal_route_suppressed` and suppression reason when a next command is not safe.

## Read Models

| Read model | Source elements | Purpose |
| --- | --- | --- |
| Portfolio Overview | RepositoryContext, PortfolioSnapshot, PortfolioPathRef, ProductPath, DiagnosticIssue, SourceArtifact | Show active, parallel, inactive, excluded, unresolved, warning, and blocked portfolio state. |
| Selected Path Detail | ProductPath, SourceBackedClaim, SourceArtifact, EvidenceLink, ProvenanceRecord, DiagnosticIssue | Show selected path status, stage, reason, next skill, refs, trust, warnings, and handoff eligibility. |
| Evidence Inspector | SourceBackedClaim, EvidenceLink, SourceArtifact, DiagnosticIssue | Verify displayed claims against source support or visible unresolved/contradictory state. |
| Provenance Timeline | ProvenanceRecord, SourceArtifact, SourceBackedClaim, DiagnosticIssue | Show approval, canonicalization, amendment, archive, review, missing, partial, or contradictory provenance. |
| Diagnostics Panel | DiagnosticIssue plus affected source/claim/path/handoff refs | Show severity, affected level, handoff impact, recommended checks, and trust changes. |
| Handoff Summary | HandoffSummary, ProductPath, SourceBackedClaim, EvidenceLink, ProvenanceRecord, DiagnosticIssue | Produce clean, warning, blocked, explanatory, promoted-boundary, or out-of-scope output with caveats preserved. |

## Policies

| Policy | Rule |
| --- | --- |
| Source-backed display rule | A displayed claim can be labeled source-backed only when evidence or provenance supports it. |
| Unresolved ref preservation rule | Unresolved active refs, evidence refs, provenance refs, and source refs stay modeled with diagnostics. |
| Boundary suppression rule | Inactive, promoted, archived, deferred, revisit-candidate, unsupported, unresolved, contradicted, or out-of-scope states cannot emit normal clean next-skill handoffs. |
| Confidence separation rule | Parser confidence and source confidence are distinct and may degrade independently. |
| Narrowest diagnostic scope rule | Diagnostics attach to source, claim, path, portfolio, or handoff level at the narrowest affected level first. |
| Warning preservation rule | Warning and partial handoffs are allowed only when caveats are included in output. |
| Review-only provenance rule | Review-state pages or unconfirmed artifacts cannot support confirmed provenance claims. |
| Next-command suppression rule | Missing, contradicted, blocked, out-of-scope, or promoted-boundary next-skill values suppress normal next-command output. |

## Logical Contracts

### ScanPortfolio
Request: repository context, optional inactive/diagnostics flags, optional source refs.
Response: scan state, repository ref, portfolio snapshot ID, source health trust envelope, path counts, active/excluded/unresolved refs, diagnostics, updated read models.
Errors: no AFPS state, missing/malformed progress manifest, permission denied, partial scan, unreadable source.

### SelectProductPath
Request: repository ref, product path selector, optional portfolio snapshot ID, selection reason, detail depth.
Response: selected path ID, selection state, path state, scope path, trust envelope, source inventory status, available/blocked actions, diagnostics.
Errors: path not found, path excluded, active ref unresolved, scope path missing, selection blocked, source state contradicted.

### EvaluateDiagnostics
Request: repository ref, optional path, claim refs, artifact refs, evaluation reason, prior diagnostics.
Response: diagnostics, trust changes, handoff eligibility, affected read models, manual checks, warnings to preserve, blocked reasons.
Errors: diagnostic source missing, diagnostic scope ambiguous, cannot classify issue, source review required.

### GenerateHandoff
Request: product path selector, handoff intent, detail level, include refs/diagnostics.
Response: summary ID, handoff type/state, path state, optional next skill, branch-state explanation, trust envelope, evidence/provenance refs, warnings, blocked reasons, boundary, copy allowed, normal next command suppression flag.
Errors: handoff unavailable, blocking diagnostics present, next skill unsupported, boundary explanation required, source support insufficient, out-of-scope request.

### CopyHandoff
Request: summary ID, copy intent, acknowledged warnings, requested format.
Response: copied/suppressed/blocked state, warnings preserved, suppression reason, copied payload shape, diagnostic refs, boundary.
Errors: summary not found, warnings not preserved, copy blocked by diagnostics, next command suppressed, unsupported copy format.

### ListPortfolioPaths
Request: repository ref, optional snapshot, filters, selected path.
Response: path groups, active/parallel/inactive/excluded/unresolved refs, source health, diagnostics, available actions.

### GetSelectedPathDetail
Request: selector plus claims/refs/diagnostics flags.
Response: path fields, trust envelope, claims, evidence/provenance refs, diagnostics, handoff eligibility, available/blocked actions.

### InspectEvidenceRef
Request: claim ref, optional evidence ref, path ID, source hint, preview flag.
Response: claim, evidence link, resolved ref, resolution/read/support state, trust envelope, unsupported claims, diagnostics, preview availability.

### InspectProvenance
Request: optional path, claim ref, artifact ref, include archives/review flags.
Response: records, approval state, confirmed/review-only records, archive refs, canonical targets, source confidence, warnings, diagnostics, handoff relevance.

### GetDiagnostics
Request: optional repository, path, claim refs, levels, severities, include resolved.
Response: issues, summaries by level/severity, affected sources/claims/paths, handoff impacts, manual checks, trust changes.

### ExplainBoundary
Request: selector, optional boundary kind, requested-by, include refs.
Response: path, boundary kind, source status, branch-state explanation, route suppression, suppression reason, promoted boundary flag, evidence/provenance refs, warnings, diagnostics, next-step visibility.

## Evidence And Flow Binding Matrix

All model elements bind to approved flow nodes through the model-tree manifest only. Bindings are not duplicated into the flow tree. See `design/afps-tracker/model-tree-afps-tracker.yaml` for element-level `flow_bindings`.

## Assumptions And Confidence

Confidence is high that ProductPath is the correct selected-path aggregate root because all five framework passes converged on that boundary. Confidence is medium that PortfolioSnapshot should be represented as a thin aggregate root because it is necessary for grouped orientation and unresolved refs but should not own selected-path trust. Confidence is high that parser confidence and source confidence must stay separate.

Remaining risk: the approved flow tree still has `evaluation_priority: pending-key-moments`; this model covers the full approved first read-through and can be narrowed later through normal modify-back if key-moments promotes only a subset.

## Physical Concern Deferrals

Deferred to `$spec-interview`: persistence, caches, storage engines, indexes, migrations, file watchers, parser implementation, API routes, endpoint URLs, transports, auth, authorization, sandbox mechanics, deployment, source preview mechanics, clipboard implementation, and high-fidelity UI layout.

Proposed Model-Tree Manifest

The following is the proposed content for design/afps-tracker/model-tree-afps-tracker.yaml. It will be written only after approval.

schema_version: v0.1
mode: product-path
topic: afps-tracker
product_path: research/afps-tracker
status: proposed
flow_tree_ref: design/afps-tracker/flow-tree-afps-tracker.yaml
source_artifacts:
  - design/afps-tracker/_working/state-model-afps-tracker-brief.md
  - design/afps-tracker/state-model-afps-tracker/event-modeling.md
  - design/afps-tracker/state-model-afps-tracker/domain-model.md
  - design/afps-tracker/state-model-afps-tracker/state-machine.md
  - design/afps-tracker/state-model-afps-tracker/data-model.md
  - design/afps-tracker/state-model-afps-tracker/contract-map.md
  - design/afps-tracker/flow-tree-afps-tracker.yaml
  - design/afps-tracker/user-flow-afps-tracker.md
frameworks:
  - event-modeling
  - domain-model
  - state-machine
  - data-model
  - contract-map
entities:
  - id: repository-context
    name: RepositoryContext
    kind: entity
    attributes: [repository_ref, scan_started_at, scan_completed_at, scan_state, source_health, warnings]
    relationships:
      - target: portfolio-snapshot
        cardinality: one-to-zero-or-one
      - target: source-artifact
        cardinality: one-to-many
      - target: diagnostic-issue
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-orient-portfolio, access: triggers }
      - { flow_node_ref: uf-orient-portfolio, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: displays }
  - id: portfolio-snapshot
    name: PortfolioSnapshot
    kind: aggregate-root
    attributes: [snapshot_id, repository_ref, progress_manifest_ref, snapshot_state, active_path_refs, excluded_path_refs, parser_confidence, source_confidence]
    lifecycle_ref: portfolio-snapshot-lifecycle
    relationships:
      - target: portfolio-path-ref
        cardinality: one-to-many
      - target: product-path
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-orient-portfolio, access: reads }
      - { flow_node_ref: uf-orient-portfolio, access: displays }
      - { flow_node_ref: uf-explain-boundaries, access: reads }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
  - id: portfolio-path-ref
    name: PortfolioPathRef
    kind: entity
    attributes: [path_ref_id, path_id, scope_path, source_status, resolution_state, boundary_kind, diagnostic_refs]
    relationships:
      - target: product-path
        cardinality: many-to-zero-or-one
      - target: diagnostic-issue
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-orient-portfolio, access: displays }
      - { flow_node_ref: uf-explain-boundaries, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: writes }
  - id: product-path
    name: ProductPath
    kind: aggregate-root
    attributes: [path_id, label, scope_path, source_status, pipeline_stage, reason, next_skill, revisit_trigger, last_touched, path_state, trust_level, handoff_eligibility, boundary_kind]
    lifecycle_ref: product-path-lifecycle
    relationships:
      - target: source-artifact
        cardinality: one-to-many
      - target: source-backed-claim
        cardinality: one-to-many
      - target: provenance-record
        cardinality: one-to-many
      - target: diagnostic-issue
        cardinality: one-to-many
      - target: handoff-summary
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-orient-portfolio, access: displays }
      - { flow_node_ref: uf-verify-selected-path, access: reads }
      - { flow_node_ref: uf-verify-selected-path, access: writes }
      - { flow_node_ref: uf-handoff-resume, access: reads }
      - { flow_node_ref: uf-explain-boundaries, access: reads }
      - { flow_node_ref: uf-explain-boundaries, access: displays }
  - id: source-artifact
    name: SourceArtifact
    kind: entity
    attributes: [artifact_id, artifact_ref, artifact_kind, source_role, read_status, approval_status, freshness_state, parser_confidence, source_confidence, warnings]
    lifecycle_ref: source-artifact-lifecycle
    relationships:
      - target: source-backed-claim
        cardinality: many-to-many
      - target: provenance-record
        cardinality: many-to-many
      - target: diagnostic-issue
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-verify-selected-path, access: reads }
      - { flow_node_ref: uf-inspect-source-provenance, access: reads }
      - { flow_node_ref: uf-inspect-source-provenance, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
  - id: source-backed-claim
    name: SourceBackedClaim
    kind: entity
    attributes: [claim_id, path_id, claim_kind, claim_text, support_status, parser_confidence, source_confidence, trust_level, derived_from, warnings]
    relationships:
      - target: evidence-link
        cardinality: one-to-zero-or-many
      - target: diagnostic-issue
        cardinality: one-to-many
    flow_bindings:
      - { flow_node_ref: uf-verify-selected-path, access: displays }
      - { flow_node_ref: uf-inspect-source-provenance, access: reads }
      - { flow_node_ref: uf-inspect-source-provenance, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: reads }
      - { flow_node_ref: uf-handoff-resume, access: reads }
  - id: evidence-link
    name: EvidenceLink
    kind: entity
    attributes: [evidence_link_id, claim_id, evidence_ref, resolved_ref, resolution_state, support_status, read_status, source_hint, warnings]
    lifecycle_ref: evidence-link-lifecycle
    relationships:
      - target: source-artifact
        cardinality: many-to-zero-or-one
    flow_bindings:
      - { flow_node_ref: uf-inspect-source-provenance, access: triggers }
      - { flow_node_ref: uf-inspect-source-provenance, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
  - id: provenance-record
    name: ProvenanceRecord
    kind: entity
    attributes: [provenance_id, path_id, provenance_ref, provenance_kind, approval_state, decision_summary, canonical_target, archive_refs, confidence, warnings]
    lifecycle_ref: provenance-record-lifecycle
    relationships:
      - target: source-artifact
        cardinality: many-to-many
    flow_bindings:
      - { flow_node_ref: uf-inspect-source-provenance, access: reads }
      - { flow_node_ref: uf-inspect-source-provenance, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: reads }
      - { flow_node_ref: uf-handoff-resume, access: reads }
  - id: diagnostic-issue
    name: DiagnosticIssue
    kind: entity
    attributes: [issue_id, issue_kind, severity, level, state, affected_refs, affected_claims, affected_paths, handoff_impact, recommended_manual_check]
    lifecycle_ref: diagnostic-issue-lifecycle
    flow_bindings:
      - { flow_node_ref: uf-recover-diagnostics, access: writes }
      - { flow_node_ref: uf-recover-diagnostics, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
      - { flow_node_ref: uf-orient-portfolio, access: displays }
      - { flow_node_ref: uf-handoff-resume, access: reads }
  - id: handoff-summary
    name: HandoffSummary
    kind: entity
    attributes: [summary_id, path_id, handoff_type, copy_state, confidence, next_skill, branch_state_explanation, handoff_boundary, included_evidence_refs, included_provenance_refs, included_diagnostic_refs, blocked_reasons]
    lifecycle_ref: handoff-summary-lifecycle
    relationships:
      - target: evidence-link
        cardinality: many-to-many
      - target: provenance-record
        cardinality: many-to-many
      - target: diagnostic-issue
        cardinality: many-to-many
    flow_bindings:
      - { flow_node_ref: uf-handoff-resume, access: triggers }
      - { flow_node_ref: uf-handoff-resume, access: writes }
      - { flow_node_ref: uf-handoff-resume, access: displays }
      - { flow_node_ref: uf-explain-boundaries, access: displays }
state_machines:
  - id: portfolio-snapshot-lifecycle
    subject: PortfolioSnapshot
    states:
      - { name: unscanned, maps_to_ui_state: "Empty initial portfolio frame" }
      - { name: scanning, maps_to_ui_state: "Loading portfolio overview" }
      - { name: no-afps-state, maps_to_ui_state: "Empty/no-manifest state" }
      - { name: manifest-malformed, maps_to_ui_state: "Blocking parse diagnostic" }
      - { name: paths-discovered, maps_to_ui_state: "Grouped portfolio rows" }
      - { name: partial, maps_to_ui_state: "Visible warning/partial badges" }
      - { name: source-backed, maps_to_ui_state: "Clean portfolio overview" }
      - { name: blocked, maps_to_ui_state: "Blocking portfolio diagnostic" }
    transitions:
      - { from: unscanned, event: ScanPortfolio, to: scanning }
      - { from: scanning, event: ProgressManifestMissing, to: no-afps-state }
      - { from: scanning, event: ProgressManifestMalformed, to: manifest-malformed }
      - { from: scanning, event: ProductPathRecordDiscovered, to: paths-discovered }
      - { from: paths-discovered, event: DiagnosticIssueRaised, guard: non_blocking_portfolio_issue, to: partial }
      - { from: paths-discovered, event: DiagnosticIssueClassified, guard: no_blocking_issues, to: source-backed }
      - { from: partial, event: DiagnosticIssueClassified, guard: blocking_portfolio_issue, to: blocked }
  - id: product-path-lifecycle
    subject: ProductPath
    states:
      - { name: undiscovered, maps_to_ui_state: "Unresolved active ref or absent row" }
      - { name: discovered, maps_to_ui_state: "Portfolio row" }
      - { name: selected, maps_to_ui_state: "Selected path loading frame" }
      - { name: source-inventorying, maps_to_ui_state: "Source checks loading" }
      - { name: source-backed, maps_to_ui_state: "Clean selected-path detail" }
      - { name: partial, maps_to_ui_state: "Partial detail with unresolved refs" }
      - { name: warning, maps_to_ui_state: "Warning detail and handoff caveats" }
      - { name: blocked, maps_to_ui_state: "Blocked detail and diagnostics" }
      - { name: inactive, maps_to_ui_state: "Inactive path explanation" }
      - { name: promoted-boundary, maps_to_ui_state: "Promoted boundary explanation" }
      - { name: out-of-scope, maps_to_ui_state: "Out-of-scope explanation" }
    transitions:
      - { from: undiscovered, event: ProductPathRecordDiscovered, to: discovered }
      - { from: discovered, event: ProductPathRecordExcluded, to: inactive }
      - { from: discovered, event: PromotedBoundaryDetected, to: promoted-boundary }
      - { from: discovered, event: ProductPathSelected, to: selected }
      - { from: selected, event: ScopedSourceInventoryStarted, to: source-inventorying }
      - { from: source-inventorying, event: ClaimSupportEvaluated, guard: required_claims_supported, to: source-backed }
      - { from: source-inventorying, event: EvidenceRefUnresolved, guard: non_core_support_missing, to: partial }
      - { from: source-backed, event: DiagnosticIssueRaised, guard: non_blocking_caveat, to: warning }
      - { from: warning, event: DiagnosticIssueClassified, guard: blocking_issue, to: blocked }
  - id: source-artifact-lifecycle
    subject: SourceArtifact
    states: [referenced, resolving, found, parsed, confirmed, review-only, amended, archived, missing, unreadable, malformed, stale, unsupported]
    transitions:
      - { from: referenced, event: ScopedSourceInventoryStarted, to: resolving }
      - { from: resolving, event: ScopedResearchArtifactResolved, to: found }
      - { from: resolving, event: ScopedResearchArtifactMissing, to: missing }
      - { from: found, event: ProgressManifestParsed, to: parsed }
      - { from: parsed, event: ProvenanceRecordConfirmed, to: confirmed }
      - { from: parsed, event: ProvenanceRecordReviewOnly, to: review-only }
  - id: evidence-link-lifecycle
    subject: EvidenceLink
    states: [unresolved, resolving, resolved, supports-claim, partially-supports-claim, contradicts-claim, inaccessible]
    transitions:
      - { from: unresolved, event: EvidenceRefResolutionStarted, to: resolving }
      - { from: resolving, event: EvidenceRefResolved, to: resolved }
      - { from: resolved, event: ClaimSupportEvaluated, guard: supports, to: supports-claim }
      - { from: resolved, event: ClaimSupportEvaluated, guard: partial_support, to: partially-supports-claim }
      - { from: resolved, event: ClaimSupportEvaluated, guard: contradiction, to: contradicts-claim }
  - id: provenance-record-lifecycle
    subject: ProvenanceRecord
    states: [missing, discovered, review, confirmed, amended, archived, partial, contradicted]
    transitions:
      - { from: missing, event: ProvenanceRecordDiscovered, to: discovered }
      - { from: discovered, event: ProvenanceRecordConfirmed, to: confirmed }
      - { from: discovered, event: ProvenanceRecordReviewOnly, to: review }
      - { from: confirmed, event: AmendmentDetected, to: amended }
  - id: diagnostic-issue-lifecycle
    subject: DiagnosticIssue
    states: [none, raised, classified, warning, blocked, partial, resolved-by-source-review]
    transitions:
      - { from: none, event: DiagnosticIssueRaised, to: raised }
      - { from: raised, event: DiagnosticIssueClassified, to: classified }
      - { from: classified, event: SeverityPolicyApplied, guard: non_blocking, to: warning }
      - { from: classified, event: SeverityPolicyApplied, guard: blocking, to: blocked }
      - { from: classified, event: SeverityPolicyApplied, guard: incomplete, to: partial }
  - id: handoff-summary-lifecycle
    subject: HandoffSummary
    states: [unavailable, requested, clean-copy, warning-copy, blocked, explanatory, promoted-boundary, out-of-scope, copied, suppressed]
    transitions:
      - { from: unavailable, event: HandoffGenerationRequested, to: requested }
      - { from: requested, event: CleanCopyPrepared, to: clean-copy }
      - { from: requested, event: WarningCopyPrepared, to: warning-copy }
      - { from: requested, event: HandoffBlocked, to: blocked }
      - { from: requested, event: InactivePathExplained, to: explanatory }
      - { from: requested, event: PromotedBoundaryDetected, to: promoted-boundary }
      - { from: requested, event: OutOfScopeRequestExplained, to: out-of-scope }
events:
  - { id: repository-scan-started, name: RepositoryScanStarted, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: triggers }] }
  - { id: product-path-selected, name: ProductPathSelected, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: triggers }, { flow_node_ref: uf-verify-selected-path, access: writes }] }
  - { id: claim-support-evaluated, name: ClaimSupportEvaluated, flow_bindings: [{ flow_node_ref: uf-inspect-source-provenance, access: reads }, { flow_node_ref: uf-recover-diagnostics, access: reads }] }
  - { id: diagnostic-issue-raised, name: DiagnosticIssueRaised, flow_bindings: [{ flow_node_ref: uf-recover-diagnostics, access: writes }] }
  - { id: handoff-summary-prepared, name: HandoffSummaryPrepared, flow_bindings: [{ flow_node_ref: uf-handoff-resume, access: displays }] }
  - { id: boundary-explanation-requested, name: BoundaryExplanationRequested, flow_bindings: [{ flow_node_ref: uf-explain-boundaries, access: triggers }] }
commands:
  - { id: scan-portfolio, name: ScanPortfolio, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: triggers }] }
  - { id: select-product-path, name: SelectProductPath, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: triggers }, { flow_node_ref: uf-verify-selected-path, access: writes }] }
  - { id: evaluate-diagnostics, name: EvaluateDiagnostics, flow_bindings: [{ flow_node_ref: uf-recover-diagnostics, access: writes }] }
  - { id: generate-handoff, name: GenerateHandoff, flow_bindings: [{ flow_node_ref: uf-handoff-resume, access: triggers }, { flow_node_ref: uf-explain-boundaries, access: triggers }] }
  - { id: copy-handoff, name: CopyHandoff, flow_bindings: [{ flow_node_ref: uf-handoff-resume, access: writes }] }
read_models:
  - { id: portfolio-overview, name: Portfolio Overview, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: displays }] }
  - { id: selected-path-detail, name: Selected Path Detail, flow_bindings: [{ flow_node_ref: uf-verify-selected-path, access: displays }] }
  - { id: evidence-inspector, name: Evidence Inspector, flow_bindings: [{ flow_node_ref: uf-inspect-source-provenance, access: displays }] }
  - { id: provenance-timeline, name: Provenance Timeline, flow_bindings: [{ flow_node_ref: uf-inspect-source-provenance, access: displays }] }
  - { id: diagnostics-panel, name: Diagnostics Panel, flow_bindings: [{ flow_node_ref: uf-recover-diagnostics, access: displays }] }
  - { id: handoff-summary, name: Handoff Summary, flow_bindings: [{ flow_node_ref: uf-handoff-resume, access: displays }, { flow_node_ref: uf-explain-boundaries, access: displays }] }
policies:
  - { id: source-backed-display-rule, name: Source-backed display rule, flow_bindings: [{ flow_node_ref: uf-verify-selected-path, access: reads }, { flow_node_ref: uf-inspect-source-provenance, access: reads }] }
  - { id: unresolved-ref-preservation-rule, name: Unresolved ref preservation rule, flow_bindings: [{ flow_node_ref: uf-orient-portfolio, access: displays }, { flow_node_ref: uf-recover-diagnostics, access: transitions }] }
  - { id: boundary-suppression-rule, name: Boundary suppression rule, flow_bindings: [{ flow_node_ref: uf-explain-boundaries, access: displays }, { flow_node_ref: uf-handoff-resume, access: displays }] }
  - { id: confidence-separation-rule, name: Confidence separation rule, flow_bindings: [{ flow_node_ref: uf-inspect-source-provenance, access: reads }, { flow_node_ref: uf-recover-diagnostics, access: reads }] }
  - { id: warning-preservation-rule, name: Warning preservation rule, flow_bindings: [{ flow_node_ref: uf-handoff-resume, access: displays }] }
contracts:
  - id: scan-portfolio-contract
    name: ScanPortfolio
    kind: command
    request_shape: [repository_context, include_inactive, include_diagnostics, source_refs]
    response_shape: [scan_state, repository_ref, portfolio_snapshot_id, source_health, path_counts, active_path_refs, excluded_path_refs, unresolved_path_refs, diagnostics, read_models_updated]
    error_shape: [no-afps-state, progress-manifest-missing, progress-manifest-malformed, permission-denied, scan-partial, source-unreadable]
    flow_bindings:
      - { flow_node_ref: uf-orient-portfolio, access: triggers }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
  - id: selected-path-detail-contract
    name: GetSelectedPathDetail
    kind: query
    request_shape: [selector, include_claims, include_refs, include_diagnostics]
    response_shape: [path_id, label, scope_path, source_status, path_state, pipeline_stage, reason, next_skill, trust, claims, evidence_refs, provenance_refs, diagnostics, handoff_eligibility, available_actions, blocked_actions]
    error_shape: [path-not-selected, path-not-found, source-artifact-missing, path-detail-partial, path-detail-blocked, path-state-contradicted]
    flow_bindings:
      - { flow_node_ref: uf-verify-selected-path, access: reads }
      - { flow_node_ref: uf-verify-selected-path, access: displays }
  - id: inspect-evidence-ref-contract
    name: InspectEvidenceRef
    kind: query
    request_shape: [claim_ref, evidence_ref, path_id, source_hint, preview_requested]
    response_shape: [claim_id, claim_text, evidence_link_id, evidence_ref, resolved_ref, resolution_state, read_status, support_status, source_hint, trust, diagnostics, preview_available]
    error_shape: [claim-not-found, evidence-ref-missing, evidence-ref-unresolved, source-preview-unavailable, evidence-inaccessible, claim-contradicted]
    flow_bindings:
      - { flow_node_ref: uf-inspect-source-provenance, access: displays }
      - { flow_node_ref: uf-recover-diagnostics, access: transitions }
  - id: generate-handoff-contract
    name: GenerateHandoff
    kind: command
    request_shape: [selector, handoff_intent, detail_level, include_refs, include_diagnostics]
    response_shape: [summary_id, handoff_type, handoff_state, path_id, state, pipeline_stage, next_skill, branch_state_explanation, confidence, evidence_refs, provenance_refs, warnings, blocked_reasons, boundary, copy_allowed, normal_next_command_suppressed]
    error_shape: [handoff-unavailable, blocking-diagnostics-present, next-skill-unsupported, boundary-explanation-required, source-support-insufficient, out-of-scope-request]
    flow_bindings:
      - { flow_node_ref: uf-handoff-resume, access: triggers }
      - { flow_node_ref: uf-explain-boundaries, access: displays }

Flow Binding Matrix

Flow nodeReadsWrites or triggersDisplaysTransitions
uf-orient-portfolioRepositoryContext, PortfolioSnapshot, PortfolioPathRef, ProductPath, SourceArtifact, DiagnosticIssueScanPortfolio, SelectProductPath, scan state, selected path targetPortfolio Overview, grouped path rows, source health, active/parallel/inactive/unresolved statesunscanned, scanning, no-AFPS, malformed, paths-discovered, partial, source-backed, blocked
uf-verify-selected-pathProductPath, SourceArtifact, SourceBackedClaim, EvidenceLink, ProvenanceRecord, TrustEnvelopeSelected-path context and source inventorySelected Path Detail with source-backed, inferred, partial, warning, and blocked labelsselected, source-inventorying, source-backed, partial, warning, blocked
uf-inspect-source-provenanceSourceBackedClaim, EvidenceLink, SourceArtifact, ProvenanceRecordEvidence resolution, provenance classification, diagnostic re-evaluation when trust changesEvidence Inspector, Provenance Timeline, resolved/unresolved/support/contradiction statesresolved, supports-claim, partially-supports-claim, contradicted, confirmed, review-only, missing
uf-recover-diagnosticsDiagnosticIssue, affected source/claim/path/portfolio/handoff refsEvaluateDiagnostics, severity, affected level, handoff impact, trust degradationDiagnostics Panel, warning/blocking/partial groups, recommended manual checksraised, classified, warning, blocked, partial, resolved-by-source-review
uf-handoff-resumeProductPath, SourceBackedClaim, EvidenceLink, ProvenanceRecord, DiagnosticIssue, HandoffSummaryGenerateHandoff, CopyHandoff, copy state, included refs, blocked reasonsHandoff Summary, clean/warning/blocked copy state, suppression reasonsrequested, clean-copy, warning-copy, blocked, copied, suppressed
uf-explain-boundariesPortfolioPathRef, ProductPath, BoundaryKind, HandoffSummary, source rationaleExplainBoundary, boundary handoff generation, next-command suppressionInactive, promoted-boundary, out-of-scope, unsupported, unresolved explanationinactive, promoted-boundary, out-of-scope, explanatory, suppressed

Visual Model

Logical ERD

RepositoryContext PortfolioSnapshot ProductPath SourceArtifact SourceBackedClaim ProvenanceRecord EvidenceLink HandoffSummary
RelationshipCardinality
RepositoryContext to PortfolioSnapshotone to zero-or-one
PortfolioSnapshot to ProductPathone to many
ProductPath to SourceBackedClaimone to many
SourceBackedClaim to EvidenceLinkone to zero-or-many
ProductPath to HandoffSummaryone to many over time

Product Path Lifecycle

discovered selected source-inventorying source-backed partial warning blocked inactive clean-copy promoted-boundary out-of-scope
State familyHandoff effect
source-backedEligible for clean copy when diagnostics are non-blocking.
partial or warningEligible only when warnings remain visible and are included.
blockedSuppresses clean handoff and may suppress all next-command output.
inactive/promoted/out-of-scopeProduces explanatory or boundary handoff instead of normal route.

Assumptions And Deferrals

ItemStatusDecision impact
ProductPath is the main aggregate root.High confidenceAll five framework passes converged here.
PortfolioSnapshot is a thin aggregate for orientation.Medium confidenceNeeded for grouped paths and unresolved refs, but should not own selected-path trust.
Parser confidence and source confidence remain separate.High confidencePrevents a confident parser from masking weak source support, or vice versa.
Key-moments is still pending.Known caveatThis model covers the approved first read-through and can be narrowed through modify-back later.
Storage, endpoints, auth, migrations, indexes, deployment, file watchers, parser implementation, and UI layout.DeferredOwned by `$spec-interview` or later implementation work, not this logical model.

Proposed File Changes After Approval

PathAction after approvalNotes
design/afps-tracker/domain-model-afps-tracker.mdCreate canonical model docUses proposed content rendered above.
design/afps-tracker/model-tree-afps-tracker.yamlCreate canonical model-tree manifestUses proposed content rendered above; status should become confirmed during approval handling.
design/afps-tracker/flow-tree-afps-tracker.yamlUpdate after approvalAttach branches[].model_ref for the modeled branches and optional top-level model_tree_ref. Do not touch route.
research/afps-tracker/glossary.mdAppend approved glossary terms onlyOnly if glossary gate approves additions.
docs/history/archive/YYYY-MM-DD/HHMMSS/...Archive working cursor after canonical writeArchive the brief and framework intermediates at canonical write timing.

Approval Gates

Domain model document

Approve writing the proposed canonical domain model document?

Model-tree manifest

Approve writing the proposed model-tree manifest and using its flow bindings as authoritative?

Flow-tree attachment

Approve attaching the model to the flow-tree branches via branches[].model_ref plus the optional top-level model_tree_ref, while leaving the route array unchanged?

Glossary additions

Approve appending retained glossary terms from the proposed model where they are not already present?

Compile Responses

Answer the gates above, then compile and paste the YAML into a fresh or current agent session. Approval is complete only when all required gates are answered with approve or no-change and no revision/clarification remains.