diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 47d2480..3602e09 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,7 +5,7 @@ ## Traceability - **Task ID(s):** T0xx -- **Traces:** AC-… / FR-… / NFR-… (from `tasks.md` `**Traces**:` fields) +- **Traces:** AC-… / FR-… / NFR-… (from `tasks.md` `**Carries**:` fields) - **PRD silent?** No / Yes — proposed PRD change: … ## Proof diff --git a/.specify/extensions.yml b/.specify/extensions.yml index c29ab0d..d0fcd50 100644 --- a/.specify/extensions.yml +++ b/.specify/extensions.yml @@ -1,6 +1,6 @@ installed: - agent-context -- clewseau-gate +- specassay-check settings: auto_execute_hooks: true hooks: @@ -23,11 +23,11 @@ hooks: description: Refresh agent context after planning condition: null after_implement: - - extension: clewseau-gate - command: speckit.clewseau-gate.check + - extension: specassay-check + command: speckit.specassay.check enabled: true optional: true priority: 10 - prompt: Run Clewseau Gate 2 traceability check? - description: Refuse silent gaps after implementation; refresh clew.json + prompt: Run SpecAssay Check (Gate 2 traceability)? + description: Refuse silent gaps after implementation; refresh trace-manifest.json condition: null diff --git a/.specify/extensions/.registry b/.specify/extensions/.registry index 65bba4e..e3f9631 100644 --- a/.specify/extensions/.registry +++ b/.specify/extensions/.registry @@ -17,19 +17,19 @@ ], "installed_at": "2026-06-26T14:27:06.775090+00:00" }, - "clewseau-gate": { + "specassay-check": { "version": "0.2.0", "source": "local", - "manifest_hash": "sha256:3dc44c058e1bcfd5fb3f8d99ca3fc78ced48eb2a8dcb80a3d9fe1bb660c2fba4", + "manifest_hash": "sha256:899c9157055ff342d6785fc21d9cfc3e37fe753895e4413dafb51959cb5bad7f", "enabled": true, "priority": 10, "registered_commands": { "claude": [ - "speckit.clewseau-gate.check" + "speckit.specassay.check" ] }, "registered_skills": [], "installed_at": "2026-08-06T18:45:20.408692+00:00" } } -} \ No newline at end of file +} diff --git a/.specify/extensions/clewseau-gate/.specify-dev/agent-commands/claude/speckit-clewseau-gate-check/SKILL.md b/.specify/extensions/clewseau-gate/.specify-dev/agent-commands/claude/speckit-clewseau-gate-check/SKILL.md deleted file mode 100644 index cff008f..0000000 --- a/.specify/extensions/clewseau-gate/.specify-dev/agent-commands/claude/speckit-clewseau-gate-check/SKILL.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: speckit-clewseau-gate-check -description: Run Clewseau Gate 2 — fail on silent gaps; emit clew.json -compatibility: Requires spec-kit project structure with .specify/ directory -metadata: - author: github-spec-kit - source: clewseau-gate:commands/speckit.clewseau-gate.check.md ---- - -# Clewseau Gate 2 check - -Run the portable Gate 2 script shipped with this extension. It checks that: - -- registry IDs ≡ IDs in specs ≡ IDs in tasks (**exact-set** drift), -- durable IDs are either verified (named proof) or tracked as debt (open task) for **ACs**, -- coverage annotations / test-encoded IDs are not orphans, -- checkbox tasks declare `Traces:`, - -and **always writes `clew.json`** (path configurable) — the Clewseau clew / matrix. The clew is written even when the gate fails, so GAPs and `gate.failures` are visible. - -`clew.json` is Clewseau-native (`format: "clew"`, schemaVersion 3). It is not ReqIF/OSLC. clewloupe (the viewer) consumes this file; it does not re-scan the target. - -## Steps - -1. Confirm `.specify/extensions/clewseau-gate/clewseau-gate-config.yml` exists (copy from `config-template.yml` if missing) and points at this project's registry, specs, tasks, and source/test trees. Set `clew_path` if you do not want `clew.json` at the project root. -2. From the project root, run: - -```bash -bash .specify/extensions/clewseau-gate/scripts/check-traceability.sh -``` - -3. Report the script's exit code, any `FAIL:` lines, and confirm `clew.json` (or configured `clew_path`) was written. -4. Do **not** weaken the gate. If something is unfinished, it belongs as tracked debt (unchecked task with `Traces:`), not as a silenced gap. -5. Reminder: **verified** means a named proof exists — not that a full suite was asserted green by this script. \ No newline at end of file diff --git a/.specify/extensions/clewseau-gate/README.md b/.specify/extensions/clewseau-gate/README.md deleted file mode 100644 index 2bacdfb..0000000 --- a/.specify/extensions/clewseau-gate/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Clewseau Gate - -Gate 2: compare the ID registry to specs, tasks, coverage annotations, and test names. **Exact-set** registry ≡ specs ≡ tasks. Silent AC gaps and untraced scope fail. **Always writes a clew** (default `clew.json`, configurable via `clew_path`) for clewloupe or any matrix consumer. - -Install: - -```bash -specify extension add --dev /path/to/clewseau/extensions/clewseau-gate -``` - -Install scaffolds `clewseau-gate-config.yml` from `config-template.yml`. Edit `registry`, `clew_path`, `specs`, `tasks`, `src_globs`, and `test_globs` for your repo. diff --git a/.specify/extensions/clewseau-gate/commands/speckit.clewseau-gate.check.md b/.specify/extensions/clewseau-gate/commands/speckit.clewseau-gate.check.md deleted file mode 100644 index 53a79d9..0000000 --- a/.specify/extensions/clewseau-gate/commands/speckit.clewseau-gate.check.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -description: Run Clewseau Gate 2 — fail on silent gaps; emit clew.json ---- - -# Clewseau Gate 2 check - -Run the portable Gate 2 script shipped with this extension. It checks that: - -- registry IDs ≡ IDs in specs ≡ IDs in tasks (**exact-set** drift), -- durable IDs are either verified (named proof) or tracked as debt (open task) for **ACs**, -- coverage annotations / test-encoded IDs are not orphans, -- checkbox tasks declare `Traces:`, - -and **always writes a clew** (default `clew.json`, path configurable) — the Clewseau matrix. The clew is written even when the gate fails, so GAPs and `gate.failures` are visible. - -`clew.json` is Clewseau-native (`format: "clew"`, schemaVersion 3). It is not ReqIF/OSLC. clewloupe (the viewer) consumes this file; it does not re-scan the target. - -## Steps - -1. Confirm `.specify/extensions/clewseau-gate/clewseau-gate-config.yml` exists (copy from `config-template.yml` if missing) and points at this project's registry, specs, tasks, and source/test trees. Set `clew_path` if you do not want `clew.json` at the project root. -2. From the project root, run: - -```bash -bash .specify/extensions/clewseau-gate/scripts/check-traceability.sh -``` - -3. Report the script's exit code, any `FAIL:` lines, and confirm `clew.json` (or configured `clew_path`) was written. -4. Do **not** weaken the gate. If something is unfinished, it belongs as tracked debt (unchecked task with `Traces:`), not as a silenced gap. -5. Reminder: **verified** means a named proof exists — not that a full suite was asserted green by this script. diff --git a/.specify/extensions/clewseau-gate/extension.yml b/.specify/extensions/clewseau-gate/extension.yml deleted file mode 100644 index 8a2a400..0000000 --- a/.specify/extensions/clewseau-gate/extension.yml +++ /dev/null @@ -1,43 +0,0 @@ -schema_version: "1.0" - -extension: - id: "clewseau-gate" - name: "Clewseau Gate" - version: "0.2.0" - description: "Gate 2 — refuse silent gaps and emit a clew (`clew.json`)." - category: "visibility" - effect: "read-write" - author: "Rik Dryfoos / Dryfoos Consulting" - license: "MIT" - homepage: "https://github.com/rdryfoos/clewseau" - repository: "https://github.com/rdryfoos/clewseau" - -requires: - speckit_version: ">=0.14.0" - tools: [] - -provides: - commands: - - name: "speckit.clewseau-gate.check" - file: "commands/speckit.clewseau-gate.check.md" - description: "Run Clewseau Gate 2 (silent gaps fail; write clew.json)" - - config: - - name: "clewseau-gate-config.yml" - template: "config-template.yml" - description: "Registry path, source/test globs, and ID grammar" - required: true - -hooks: - after_implement: - command: "speckit.clewseau-gate.check" - optional: true - prompt: "Run Clewseau Gate 2 traceability check?" - description: "Refuse silent gaps after implementation; refresh clew.json" - -tags: - - "traceability" - - "gate" - - "ci" - - "governance" - - "sdd" diff --git a/.specify/extensions/specassay-check/.specify-dev/agent-commands/claude/speckit-specassay-check/SKILL.md b/.specify/extensions/specassay-check/.specify-dev/agent-commands/claude/speckit-specassay-check/SKILL.md new file mode 100644 index 0000000..caafd40 --- /dev/null +++ b/.specify/extensions/specassay-check/.specify-dev/agent-commands/claude/speckit-specassay-check/SKILL.md @@ -0,0 +1,34 @@ +--- +name: speckit-specassay-check +description: Run SpecAssay Check (Gate 2) — fail on silent gaps; emit trace-manifest.json +compatibility: Requires spec-kit project structure with .specify/ directory +metadata: + author: github-spec-kit + source: specassay-check:commands/speckit.specassay.check.md +--- + +# SpecAssay Check (Gate 2) + +Run the portable Gate 2 script shipped with this extension. It checks that: + +- registry IDs ≡ IDs in specs ≡ IDs in tasks (**exact-set** drift), +- durable IDs are either verified (named proof) or tracked as debt (open task) for **ACs**, +- coverage annotations / test-encoded IDs are not orphans, +- checkbox tasks declare `Carries:`, + +and **always writes `trace-manifest.json`** (path configurable) — the SpecAssay matrix. The manifest is written even when the gate fails, so GAPs and `gate.failures` are visible. + +`trace-manifest.json` is portable and vendor-neutral (`format: "trace-manifest"`, schemaVersion 3). It is not ReqIF/OSLC. Loupe (the viewer) consumes this file; it does not re-scan the target. + +## Steps + +1. Confirm `.specify/extensions/specassay-check/specassay-check-config.yml` exists (copy from `config-template.yml` if missing) and points at this project's registry, specs, tasks, and source/test trees. Set `manifest_path` if you do not want `trace-manifest.json` at the project root. +2. From the project root, run: + +```bash +bash .specify/extensions/specassay-check/scripts/check-traceability.sh +``` + +3. Report the script's exit code, any `FAIL:` lines, and confirm `trace-manifest.json` (or configured `manifest_path`) was written. +4. Do **not** weaken the gate. If something is unfinished, it belongs as tracked debt (unchecked task with `Carries:`), not as a silenced gap. +5. Reminder: **verified** means a named proof exists — not that a full suite was asserted green by this script. diff --git a/.specify/extensions/specassay-check/README.md b/.specify/extensions/specassay-check/README.md new file mode 100644 index 0000000..8d4d125 --- /dev/null +++ b/.specify/extensions/specassay-check/README.md @@ -0,0 +1,15 @@ +# SpecAssay Check + +Every **wish** is on the **Golden Thread** with its **work** and its **proof**; this check stops the line at any loose end, then strikes a hallmark on what it found — the **trace-manifest**. + +Gate 2: compare the ID registry to specs, tasks, coverage annotations, and test names. **Exact-set** registry ≡ specs ≡ tasks. Silent AC gaps and untraced scope fail. **Always writes a trace-manifest** (default `trace-manifest.json`, configurable via `manifest_path`) for Loupe or any matrix consumer. + +**CI is the property line.** Run this script on every PR / protected-branch push and fail the build on non-zero exit. Local runs are hygiene; CI is what stops a cowboy (or any machine without SpecAssay) from merging unmarked work. Keep the emitted trace-manifest from the CI run as evidence. + +Install: + +```bash +specify extension add --dev /path/to/specassay/extensions/specassay-check +``` + +Install scaffolds `specassay-check-config.yml` from `config-template.yml`. Edit `registry`, `manifest_path`, `specs`, `tasks`, `src_globs`, and `test_globs` for your repo. diff --git a/.specify/extensions/specassay-check/commands/speckit.specassay.check.md b/.specify/extensions/specassay-check/commands/speckit.specassay.check.md new file mode 100644 index 0000000..59ef7fa --- /dev/null +++ b/.specify/extensions/specassay-check/commands/speckit.specassay.check.md @@ -0,0 +1,29 @@ +--- +description: Run SpecAssay Check (Gate 2) — fail on silent gaps; emit trace-manifest.json +--- + +# SpecAssay Check (Gate 2) + +Run the portable Gate 2 script shipped with this extension. It checks that: + +- registry IDs ≡ IDs in specs ≡ IDs in tasks (**exact-set** drift), +- durable IDs are either verified (named proof) or tracked as debt (open task) for **ACs**, +- coverage annotations / test-encoded IDs are not orphans, +- checkbox tasks declare `Carries:`, + +and **always writes a trace-manifest** (default `trace-manifest.json`, path configurable) — the SpecAssay matrix. The manifest is written even when the gate fails, so GAPs and `gate.failures` are visible. + +`trace-manifest.json` is a portable, vendor-neutral matrix (`format: "trace-manifest"`, schemaVersion 3). It is not ReqIF/OSLC. Loupe (the viewer) consumes this file; it does not re-scan the target. + +## Steps + +1. Confirm `.specify/extensions/specassay-check/specassay-check-config.yml` exists (copy from `config-template.yml` if missing) and points at this project's registry, specs, tasks, and source/test trees. Set `manifest_path` if you do not want `trace-manifest.json` at the project root. +2. From the project root, run: + +```bash +bash .specify/extensions/specassay-check/scripts/check-traceability.sh +``` + +3. Report the script's exit code, any `FAIL:` lines, and confirm `trace-manifest.json` (or configured `manifest_path`) was written. +4. Do **not** weaken the gate. If something is unfinished, it belongs as tracked debt (unchecked task with `Carries:`), not as a silenced gap. +5. Reminder: **verified** means a named proof exists — not that a full suite was asserted green by this script. diff --git a/.specify/extensions/clewseau-gate/config-template.yml b/.specify/extensions/specassay-check/config-template.yml similarity index 62% rename from .specify/extensions/clewseau-gate/config-template.yml rename to .specify/extensions/specassay-check/config-template.yml index 71d53f9..1474019 100644 --- a/.specify/extensions/clewseau-gate/config-template.yml +++ b/.specify/extensions/specassay-check/config-template.yml @@ -1,13 +1,13 @@ -# Clewseau Gate config (edit after install) +# SpecAssay Check config (edit after install) # Authoritative ID registry (usually the PRD) registry: "PRD.md" -# Optional display name in clew.json (defaults to project directory name) +# Optional display name in the trace-manifest (defaults to project directory name) target_name: "" -# Where Gate 2 writes the clew (Clewseau native matrix; format: clew) -clew_path: "clew.json" +# Where the check writes the trace-manifest (SpecAssay native matrix; format: trace-manifest) +manifest_path: "trace-manifest.json" # Spec Kit feature artifacts (globs relative to project root) specs: "specs/**/spec.md" @@ -27,5 +27,8 @@ id_regex: "(FR|NFR|AC|US)-[A-Z][A-Z0-9]{1,5}-[0-9]{2,}[a-z]?" # How coverage annotations appear in source covers_regex: "@covers[[:space:]]+.*" +# The task-side mark. Accepts both **Carries**: (new) and **Traces**: (pre-rename) during transition. +carries_regex: "\*\*(Carries|Traces)\*\*:" + # How AC IDs appear inside test names (e.g. test_AC_SYNC_04_...) test_ac_regex: "AC_[A-Z][A-Z0-9]{1,5}_[0-9]{2,}[a-z]?" diff --git a/.specify/extensions/specassay-check/extension.yml b/.specify/extensions/specassay-check/extension.yml new file mode 100644 index 0000000..905a75c --- /dev/null +++ b/.specify/extensions/specassay-check/extension.yml @@ -0,0 +1,43 @@ +schema_version: "1.0" + +extension: + id: "specassay-check" + name: "SpecAssay Check" + version: "0.2.0" + description: "Gate 2 — refuse silent gaps and emit a trace-manifest (`trace-manifest.json`)." + category: "visibility" + effect: "read-write" + author: "Rik Dryfoos / Dryfoos Consulting" + license: "MIT" + homepage: "https://github.com/rdryfoos/specassay" + repository: "https://github.com/rdryfoos/specassay" + +requires: + speckit_version: ">=0.14.0" + tools: [] + +provides: + commands: + - name: "speckit.specassay.check" + file: "commands/speckit.specassay.check.md" + description: "Run SpecAssay Check / Gate 2 (silent gaps fail; write trace-manifest.json)" + + config: + - name: "specassay-check-config.yml" + template: "config-template.yml" + description: "Registry path, source/test globs, and ID grammar" + required: true + +hooks: + after_implement: + command: "speckit.specassay.check" + optional: true + prompt: "Run SpecAssay Check (Gate 2 traceability)?" + description: "Refuse silent gaps after implementation; refresh trace-manifest.json" + +tags: + - "traceability" + - "gate" + - "ci" + - "governance" + - "sdd" diff --git a/.specify/extensions/clewseau-gate/scripts/check-traceability.sh b/.specify/extensions/specassay-check/scripts/check-traceability.sh similarity index 85% rename from .specify/extensions/clewseau-gate/scripts/check-traceability.sh rename to .specify/extensions/specassay-check/scripts/check-traceability.sh index 1b4c950..3dcd040 100755 --- a/.specify/extensions/clewseau-gate/scripts/check-traceability.sh +++ b/.specify/extensions/specassay-check/scripts/check-traceability.sh @@ -1,19 +1,20 @@ #!/usr/bin/env bash -# Clewseau Gate 2 — portable traceability check + clew.json matrix emitter. +# SpecAssay Check (Gate 2) — portable traceability check + trace-manifest.json matrix emitter. +# The check is the assay: it hallmarks the Golden Thread from wish to work to proof. # Exact-set registry ≡ specs ≡ tasks. Silent AC gaps and untraced scope fail. # Tracked debt is allowed. US/FR/NFR without own carrier are backlog (planning altitude) — not silent-gap candidates. -# Anointed backlog: a registry ID whose only carrier is an open Traces TODO is backlog, not drift — minting into +# Anointed backlog: a registry ID whose only carrier is an open Carries TODO is backlog, not drift — minting into # backlog is deliberate (the TODO proves intent); unclaimed IDs with no TODO still fail exact-set. -# Always writes clew.json (even on failure) so the clew reflects GAPs + gate. +# Always writes trace-manifest.json (even on failure) so the manifest reflects GAPs + gate. set -euo pipefail export LC_ALL=C EXT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -PROJECT_ROOT="${CLEWSEAU_PROJECT_ROOT:-}" +PROJECT_ROOT="${SPECASSAY_PROJECT_ROOT:-}" if [[ -z "$PROJECT_ROOT" ]]; then # Spec Kit install: /.specify/extensions// → three levels up. - # Clewseau repo layout: /extensions// → two levels up. + # SpecAssay repo layout: /extensions// → two levels up. parent_dir="$(basename "$(dirname "$EXT_DIR")")" grandparent_dir="$(basename "$(dirname "$(dirname "$EXT_DIR")")")" if [[ "$parent_dir" == "extensions" && "$grandparent_dir" == ".specify" ]]; then @@ -24,13 +25,13 @@ if [[ -z "$PROJECT_ROOT" ]]; then fi cd "$PROJECT_ROOT" -CONFIG="${CLEWSEAU_CONFIG:-$EXT_DIR/clewseau-gate-config.yml}" +CONFIG="${SPECASSAY_CONFIG:-$EXT_DIR/specassay-check-config.yml}" if [[ ! -f "$CONFIG" ]]; then if [[ -f "$EXT_DIR/config-template.yml" ]]; then CONFIG="$EXT_DIR/config-template.yml" - echo "WARN: using config-template.yml; copy to clewseau-gate-config.yml for real projects" >&2 + echo "WARN: using config-template.yml; copy to specassay-check-config.yml for real projects" >&2 else - echo "FAIL: no clewseau-gate-config.yml (looked in $EXT_DIR)" >&2 + echo "FAIL: no specassay-check-config.yml (looked in $EXT_DIR)" >&2 exit 2 fi fi @@ -65,17 +66,22 @@ SPECS_GLOB="$(yaml_scalar specs)" TASKS_GLOB="$(yaml_scalar tasks)" ID_RE="$(yaml_scalar id_regex)" COVERS_RE="$(yaml_scalar covers_regex)" +CARRIES_RE="$(yaml_scalar carries_regex)" TEST_AC_RE="$(yaml_scalar test_ac_regex)" -CLEW_OUT="$(yaml_scalar clew_path)" +MANIFEST_OUT="$(yaml_scalar manifest_path)" +# Back-compat: accept the pre-rename config key clew_path if manifest_path is unset. +[[ -n "$MANIFEST_OUT" ]] || MANIFEST_OUT="$(yaml_scalar clew_path)" TARGET_NAME="$(yaml_scalar target_name)" [[ -n "$REGISTRY" ]] || { echo "FAIL: config missing registry" >&2; exit 2; } [[ -n "$ID_RE" ]] || ID_RE='(FR|NFR|AC|US)-[A-Z][A-Z0-9]{1,5}-[0-9]{2,}[a-z]?' [[ -n "$COVERS_RE" ]] || COVERS_RE='@covers[[:space:]]+.*' +# The task-side mark. Accept both **Carries**: (new) and **Traces**: (pre-rename) during transition. +[[ -n "$CARRIES_RE" ]] || CARRIES_RE='\*\*(Carries|Traces)\*\*:' [[ -n "$TEST_AC_RE" ]] || TEST_AC_RE='AC_[A-Z][A-Z0-9]{1,5}_[0-9]{2,}[a-z]?' [[ -n "$SPECS_GLOB" ]] || SPECS_GLOB='specs/**/spec.md' [[ -n "$TASKS_GLOB" ]] || TASKS_GLOB='specs/**/tasks.md' -[[ -n "$CLEW_OUT" ]] || CLEW_OUT='clew.json' +[[ -n "$MANIFEST_OUT" ]] || MANIFEST_OUT='trace-manifest.json' [[ -n "$TARGET_NAME" ]] || TARGET_NAME="$(basename "$PROJECT_ROOT")" fail=0 @@ -83,7 +89,7 @@ tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT : > "$tmp/failures.jsonl" -# kind | id-or-empty | detail — collected for clew.json gate.failures +# kind | id-or-empty | detail — collected for trace-manifest.json gate.failures record_fail() { local kind="$1" local id="${2:-}" @@ -102,7 +108,7 @@ print(json.dumps(row, ensure_ascii=False)) if [[ ! -f "$REGISTRY" ]]; then record_fail "registry-missing" "" "registry not found: $REGISTRY" - # Still try to emit an empty-ish clew below if possible; exit after emit. + # Still try to emit an empty-ish manifest below if possible; exit after emit. fi if [[ -f "$REGISTRY" ]]; then @@ -147,7 +153,7 @@ sort -u "$tmp/spec.txt" -o "$tmp/spec.txt" while IFS= read -r f; do [[ -f "$f" ]] || continue grep -Eoh "$ID_RE" "$f" | sort -u >> "$tmp/tasks.txt" || true - # Open checkbox tasks that name registry IDs (usually via Traces:) — debt carriers. + # Open checkbox tasks that name registry IDs (usually via Carries:) — debt carriers. grep -nE '^- \[ \]' "$f" 2>/dev/null | while IFS= read -r line; do lineno="${line%%:*}" rest="${line#*:}" @@ -207,7 +213,7 @@ cut -d'|' -f3 "$tmp/proof_hits.txt" 2>/dev/null | sort -u > "$tmp/test_acs.txt" # 1) Exact-set drift: registry ≡ specs, registry ≡ tasks (HomesFlow Gate 2 parity). # Specs/tasks may not invent IDs; registry IDs may not sit unclaimed in either # artifact — except anointed backlog: a registry ID whose only carrier is an -# open Traces TODO is backlog (deliberate, visible), not drift. +# open Carries TODO is backlog (deliberate, visible), not drift. while IFS= read -r id; do [[ -z "$id" ]] && continue record_fail "spec-orphan" "$id" "spec references ID not in registry: $id" @@ -215,7 +221,7 @@ done < <(comm -13 "$tmp/registry.txt" "$tmp/spec.txt") while IFS= read -r id; do [[ -z "$id" ]] && continue - # Anointed backlog: an open Traces TODO carries the claim until a spec picks + # Anointed backlog: an open Carries TODO carries the claim until a spec picks # the ID up. Unclaimed IDs with no TODO still fail (drift / fat-finger). if grep -qx "$id" "$tmp/pending.txt"; then continue @@ -233,13 +239,13 @@ while IFS= read -r id; do record_fail "task-unclaimed" "$id" "registry ID missing from tasks: $id" done < <(comm -23 "$tmp/registry.txt" "$tmp/tasks.txt") -# 2) Every task with a checkbox should have Traces +# 2) Every task with a checkbox should carry its ID(s) via a Carries mark while IFS= read -r f; do [[ -f "$f" ]] || continue while IFS= read -r line; do if [[ "$line" =~ ^-\ \[[x\ ]\]\ T ]]; then - if ! grep -Eq '\*\*Traces\*\*:' <<<"$line"; then - record_fail "missing-traces" "" "task missing Traces field: ${line:0:80}" + if ! grep -Eq "$CARRIES_RE" <<<"$line"; then + record_fail "missing-carries" "" "task missing Carries field: ${line:0:80}" fi fi done < "$f" @@ -269,20 +275,20 @@ while IFS= read -r id; do record_fail "silent-gap" "$id" "silent gap: $id has no test and no open tracked-debt task" done < "$tmp/registry.txt" -# --- Emit clew.json (always; clew should show GAPs even when Gate fails) --- -export CLEW_OUT REGISTRY TARGET_NAME PROJECT_ROOT -export CLEW_TMP="$tmp" -export CLEW_FAIL="$fail" +# --- Emit trace-manifest.json (always; the manifest should show GAPs even when Gate fails) --- +export MANIFEST_OUT REGISTRY TARGET_NAME PROJECT_ROOT +export MANIFEST_TMP="$tmp" +export MANIFEST_FAIL="$fail" python3 - <<'PY' import json, os, re, datetime from pathlib import Path -tmp = Path(os.environ["CLEW_TMP"]) +tmp = Path(os.environ["MANIFEST_TMP"]) registry_path = Path(os.environ["REGISTRY"]) -out_path = Path(os.environ["CLEW_OUT"]) +out_path = Path(os.environ["MANIFEST_OUT"]) target = os.environ.get("TARGET_NAME") or "project" repo = os.environ.get("PROJECT_ROOT") or str(Path.cwd()) -gate_failed = os.environ.get("CLEW_FAIL", "0") != "0" +gate_failed = os.environ.get("MANIFEST_FAIL", "0") != "0" ids = [ln.strip() for ln in (tmp / "registry.txt").read_text().splitlines() if ln.strip()] pending = {ln.strip() for ln in (tmp / "pending.txt").read_text().splitlines() if ln.strip()} @@ -423,8 +429,8 @@ for id_ in ids: doc = { "schemaVersion": 3, - "format": "clew", - "emitter": "clewseau-gate", + "format": "trace-manifest", + "emitter": "specassay-check", "targetName": target, "repoPath": repo, "generatedAt": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z", @@ -441,7 +447,7 @@ doc = { "rows": rows, } -# Prefer structured failures if bash recorded any; else respect CLEW_FAIL. +# Prefer structured failures if bash recorded any; else respect MANIFEST_FAIL. if failures: doc["gate"]["ok"] = False @@ -451,8 +457,8 @@ print(f"Wrote {out_path} ({len(rows)} rows) gate.ok={doc['gate']['ok']}", flush= PY if [[ "$fail" -ne 0 ]]; then - echo "Clewseau Gate 2: FAILED" >&2 + echo "SpecAssay Check (Gate 2): FAILED" >&2 exit 1 fi -echo "Clewseau Gate 2: OK ($(wc -l < "$tmp/registry.txt" | tr -d ' ') registry IDs)" +echo "SpecAssay Check (Gate 2): OK ($(wc -l < "$tmp/registry.txt" | tr -d ' ') registry IDs)" diff --git a/.specify/extensions/clewseau-gate/clewseau-gate-config.yml b/.specify/extensions/specassay-check/specassay-check-config.yml similarity index 73% rename from .specify/extensions/clewseau-gate/clewseau-gate-config.yml rename to .specify/extensions/specassay-check/specassay-check-config.yml index 6d63c6f..d2ed569 100644 --- a/.specify/extensions/clewseau-gate/clewseau-gate-config.yml +++ b/.specify/extensions/specassay-check/specassay-check-config.yml @@ -1,7 +1,7 @@ -# Clewseau Gate config — HomesFlow +# SpecAssay Check config — HomesFlow registry: "HomesFlow.prd.md" target_name: "HomesFlow" -clew_path: "clew.json" +manifest_path: "trace-manifest.json" specs: "specs/**/spec.md" tasks: "specs/**/tasks.md" src_globs: @@ -10,4 +10,5 @@ test_globs: - "ios/**/*Tests*/**/*.swift" id_regex: "(FR|NFR|AC|US)-[A-Z][A-Z0-9]{1,5}-[0-9]{2,}[a-z]?" covers_regex: "@covers[[:space:]]+.*" +carries_regex: "\*\*(Carries|Traces)\*\*:" test_ac_regex: "AC_[A-Z][A-Z0-9]{1,5}_[0-9]{2,}[a-z]?" diff --git a/.specify/presets/.registry b/.specify/presets/.registry index 74e7452..687cb49 100644 --- a/.specify/presets/.registry +++ b/.specify/presets/.registry @@ -1,10 +1,10 @@ { "schema_version": "1.0", "presets": { - "clewseau": { + "specassay": { "version": "0.1.0", "source": "local", - "manifest_hash": "sha256:b635743a9f9c48bebdfb39abf3d840e16cf38ca90c73992af49afeee012530a1", + "manifest_hash": "sha256:f9c169617cb1cefcb15ad9fcaec32f70f7a46f8a8258113708d83be9445dd011", "enabled": true, "priority": 10, "registered_commands": {}, @@ -12,4 +12,4 @@ "installed_at": "2026-08-06T18:45:20.293055+00:00" } } -} \ No newline at end of file +} diff --git a/.specify/presets/clewseau/README.md b/.specify/presets/clewseau/README.md deleted file mode 100644 index a38f4dd..0000000 --- a/.specify/presets/clewseau/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Clewseau preset - -Appends Clewseau's durable-ID rules onto Spec Kit core templates via `append` strategy. Does not replace Spec Kit's workflow. - -Install: - -```bash -specify preset add --dev /path/to/clewseau/presets/clewseau -``` - -See the repo root [`PROMOTION-CONTRACT.md`](../../PROMOTION-CONTRACT.md). diff --git a/.specify/presets/clewseau/templates/constitution-template.md b/.specify/presets/clewseau/templates/constitution-template.md deleted file mode 100644 index 9bcb022..0000000 --- a/.specify/presets/clewseau/templates/constitution-template.md +++ /dev/null @@ -1,13 +0,0 @@ - - - - -### Article: End-to-End Traceability (NON-NEGOTIABLE) - -Every functional requirement, non-functional requirement, and acceptance criterion carries a durable unique ID of the form `--` (e.g. `FR-LOG-01`, `AC-OFFL-03`). IDs are assigned once at the PRD level and are never reused or renumbered; retired IDs are tombstoned, not recycled. - -1. Each acceptance criterion is **atomic** — one independently testable assertion — and maps to at least one automated test *or* an explicitly tracked debt entry. Silent-gap refusal is at **AC altitude**; parent IDs (US/FR/NFR) are planning labels, not silent-gap candidates. -2. Every task in `tasks.md` MUST declare the ID(s) it implements via a `Traces:` field. -3. Every verifying test MUST encode the AC ID it protects. Every requirement-bearing source module MUST carry a coverage annotation naming the ID. -4. Coverage is **bidirectional** and machine-checked: no silent AC gaps, no untraced scope, and exact-set registry ≡ specs ≡ tasks. CI fails the build on any of these. -5. `/speckit.analyze` MUST report zero Clewseau traceability violations before `/speckit.implement` runs. diff --git a/.specify/presets/specassay/GLOSSARY.md b/.specify/presets/specassay/GLOSSARY.md new file mode 100644 index 0000000..a8c98fd --- /dev/null +++ b/.specify/presets/specassay/GLOSSARY.md @@ -0,0 +1,19 @@ +# SpecAssay vocabulary + +Copy into the project glossary (e.g. `glossary.md`) when the repo keeps one. The same terms are appended to the constitution via the SpecAssay preset. + +| Term | Meaning | +|------|---------| +| **trace-manifest** | The check-emitted traceability artifact (`format: "trace-manifest"`). Default filename `trace-manifest.json`. | +| **trace-manifest.json** | Usual on-disk path for a trace-manifest (configurable via `manifest_path`). | +| **SpecAssay** | Spec Kit overlay: durable IDs, Gate 2, trace-manifest emission. | +| **Loupe** | Viewer that reads a trace-manifest only — no target re-scan. Reads any emitter's manifest. | +| **verified** | Named carrier exists (AC proof and/or `@covers` / proof for US/FR/NFR). | +| **tracked-debt** | Incomplete, but declared on an open task with `Carries:`. | +| **GAP** | Silent AC gap — neither proof nor open debt; the Golden Thread is broken; Gate refuses. | +| **backlog** | US/FR/NFR with no own carrier — planning altitude, not a silent gap. | +| **the Golden Thread** | The wish → work → proof chain a trace-manifest records. Human-facing: the Golden Thread is intact or broken. Prefer this wording over "Gate passed/failed" except when naming the check script itself. The lineage is the UK "golden thread" of building-safety records and Jonathan Smart's business "golden thread" in *Sooner Safer Happier*. | +| **mark** / **`@covers`** | Leave a mark when you touch the work: a one-line comment in source naming the durable ID(s) that code serves. Greppable; author-written; Gate reads it; Loupe shows it under Implementation. | +| **`Carries:`** | Mark on a task checkbox naming the ID(s) that task carries — usually open debt or anointed backlog. | +| **Gate 2** | Deterministic SpecAssay check + manifest emit (`speckit.specassay.check`). The mechanism that judges the Golden Thread; say "Gate" when you mean this script, "Golden Thread" when you mean what the human sees. Local runs are hygiene; **CI Gate is the property line** that protects the codebase. | + diff --git a/.specify/presets/specassay/README.md b/.specify/presets/specassay/README.md new file mode 100644 index 0000000..f934339 --- /dev/null +++ b/.specify/presets/specassay/README.md @@ -0,0 +1,13 @@ +# SpecAssay preset + +Appends SpecAssay's durable-ID rules and vocabulary onto Spec Kit core templates via `append` strategy. Does not replace Spec Kit's workflow. + +Install: + +```bash +specify preset add --dev /path/to/specassay/presets/specassay +``` + +Vocabulary (trace-manifest, statuses, Gate 2) lands in the constitution template. For projects that keep a separate glossary, also merge [`GLOSSARY.md`](./GLOSSARY.md). + +See the repo root [`PROMOTION-CONTRACT.md`](../../PROMOTION-CONTRACT.md). diff --git a/.specify/presets/clewseau/preset.yml b/.specify/presets/specassay/preset.yml similarity index 63% rename from .specify/presets/clewseau/preset.yml rename to .specify/presets/specassay/preset.yml index 123a881..c2d80de 100644 --- a/.specify/presets/clewseau/preset.yml +++ b/.specify/presets/specassay/preset.yml @@ -1,14 +1,14 @@ schema_version: "1.0" preset: - id: "clewseau" - name: "Clewseau" + id: "specassay" + name: "SpecAssay" version: "0.1.0" - description: "Appends durable-ID and Traces requirements onto Spec Kit spec, tasks, and constitution templates." + description: "Appends durable-ID, Carries, and SpecAssay vocabulary onto Spec Kit spec, tasks, and constitution templates." author: "Rik Dryfoos / Dryfoos Consulting" license: "MIT" - homepage: "https://github.com/rdryfoos/clewseau" - repository: "https://github.com/rdryfoos/clewseau" + homepage: "https://github.com/rdryfoos/specassay" + repository: "https://github.com/rdryfoos/specassay" requires: speckit_version: ">=0.14.0" @@ -24,13 +24,13 @@ provides: - type: "template" name: "tasks-template" file: "templates/tasks-template.md" - description: "Require Traces: on every task" + description: "Require Carries: on every task" strategy: "append" - type: "template" name: "constitution-template" file: "templates/constitution-template.md" - description: "Append End-to-End Traceability article" + description: "Append End-to-End Traceability article and SpecAssay vocabulary" strategy: "append" tags: diff --git a/.specify/presets/specassay/templates/constitution-template.md b/.specify/presets/specassay/templates/constitution-template.md new file mode 100644 index 0000000..66a7a07 --- /dev/null +++ b/.specify/presets/specassay/templates/constitution-template.md @@ -0,0 +1,28 @@ + + + +### Article: End-to-End Traceability (NON-NEGOTIABLE) + +Every functional requirement, non-functional requirement, and acceptance criterion carries a durable unique ID of the form `--` (e.g. `FR-LOG-01`, `AC-OFFL-03`). IDs are assigned once at the PRD level and are never reused or renumbered; retired IDs are tombstoned, not recycled. + +1. Each acceptance criterion is **atomic** — one independently testable assertion — and maps to at least one automated test *or* an explicitly tracked debt entry. Silent-gap refusal is at **AC altitude**; US/FR/NFR IDs are planning labels (manifest status `backlog`), not silent-gap candidates. +2. Every task in `tasks.md` MUST declare the ID(s) it implements via a `Carries:` field. +3. Every verifying test MUST encode the AC ID it protects. Every requirement-bearing source module MUST carry a coverage annotation naming the ID. +4. Coverage is **bidirectional** and machine-checked: no silent AC gaps, no untraced scope, and exact-set registry ≡ specs ≡ tasks. **CI fails the build on any of these** — local Gate is hygiene; CI Gate is the property line. +5. `/speckit.analyze` MUST report zero SpecAssay traceability violations before `/speckit.implement` runs. + +### Article: SpecAssay vocabulary + +Use these terms; do not invent synonyms (especially not “dossier”). + +| Term | Meaning | +|------|---------| +| **trace-manifest** | The check-emitted traceability artifact (`format: "trace-manifest"`). Default filename `trace-manifest.json`. | +| **trace-manifest.json** | Usual on-disk path for a trace-manifest (configurable via `manifest_path`). | +| **SpecAssay** | Spec Kit overlay: durable IDs, Gate 2, trace-manifest emission. | +| **Loupe** | Viewer that reads a trace-manifest only — no target re-scan. Reads any emitter's manifest. | +| **verified** | Named carrier exists (AC proof and/or `@covers` / proof for US/FR/NFR). | +| **tracked-debt** | Incomplete, but declared on an open task with `Carries:`. | +| **GAP** | Silent AC gap — neither proof nor open debt; Gate refuses; the Golden Thread frays. | +| **backlog** | US/FR/NFR with no own carrier — planning altitude, not a silent gap. | +| **Gate 2** | Deterministic SpecAssay check + manifest emit (`speckit.specassay.check`). | diff --git a/.specify/presets/clewseau/templates/spec-template.md b/.specify/presets/specassay/templates/spec-template.md similarity index 82% rename from .specify/presets/clewseau/templates/spec-template.md rename to .specify/presets/specassay/templates/spec-template.md index b81fa00..5f434a4 100644 --- a/.specify/presets/clewseau/templates/spec-template.md +++ b/.specify/presets/specassay/templates/spec-template.md @@ -1,10 +1,10 @@ - + -## Clewseau — durable IDs (required) +## SpecAssay — durable IDs (required) -Every requirement and acceptance criterion MUST carry a durable ID per the project Clewseau / traceability standard. +Every requirement and acceptance criterion MUST carry a durable ID per the project SpecAssay / traceability standard. - IDs are assigned **once at the PRD (registry) level**. This feature spec **inherits** them; do **not** mint new IDs here. - Grammar: `--` where TYPE is `US` | `FR` | `NFR` | `AC` (example: `AC-SYNC-04`). diff --git a/.specify/presets/clewseau/templates/tasks-template.md b/.specify/presets/specassay/templates/tasks-template.md similarity index 57% rename from .specify/presets/clewseau/templates/tasks-template.md rename to .specify/presets/specassay/templates/tasks-template.md index a257fb5..f70d3eb 100644 --- a/.specify/presets/clewseau/templates/tasks-template.md +++ b/.specify/presets/specassay/templates/tasks-template.md @@ -1,11 +1,11 @@ - + -## Clewseau — Traces (required) +## SpecAssay — Carries (required) Every task MUST declare the registry ID(s) it implements: -- Format on each task line: `**Traces**: AC-…, FR-…` (one or more IDs from the PRD registry). +- Format on each task line: `**Carries**: AC-…, FR-…` (one or more IDs from the PRD registry). - Do not invent IDs in tasks. If an ID is missing from the registry, stop and fix the PRD first. - Test tasks that verify an AC SHOULD encode that AC in the test name (e.g. `test_AC_SYNC_04_…`). diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md index eb82bf5..f9dd930 100644 --- a/.specify/templates/tasks-template.md +++ b/.specify/templates/tasks-template.md @@ -15,7 +15,7 @@ description: "Task list template for feature implementation" ## Format: `[ID] [P?] [Story] Description` -- **Traces**: Required — AC/FR ID(s) this task implements (e.g. `Traces: AC-HOME-01`) +- **Carries**: Required — AC/FR ID(s) this task implements (e.g. `Traces: AC-HOME-01`) - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) - Include exact file paths in descriptions diff --git a/HomesFlow.prd.md b/HomesFlow.prd.md index ab03768..636ed74 100644 --- a/HomesFlow.prd.md +++ b/HomesFlow.prd.md @@ -42,9 +42,9 @@ HomesFlow is a responsive iOS app (iPhone and iPad) that empowers primary homeow * **US-GUEST-01** — As a Guest, I want to see only guest-appropriate information (like WiFi codes/rules), so my access is controlled and simple. * **US-GUEST-02** — As a Guest, I want to view house procedures for guests, so I know what is expected during my stay. -**Clewseau cold-agent trial (temporary probe — tombstone after trial)** +**Planned (anointed backlog — minted ahead, carried by an open TODO)** -* **US-CLEW-01** — As a developer validating Clewseau, I want a tiny pure display-name normalizer with durable IDs, so a stock Spec Kit + Clewseau agent can take one AC from registry through proof without UI or sync scope. +* **US-SHARE-01** — As an Owner, I want to share a single procedure as a read-only link with a non-member helper, so that a one-off task can be followed without granting a full Guest invite. --- @@ -56,6 +56,7 @@ HomesFlow is a responsive iOS app (iPhone and iPad) that empowers primary homeow * **FR-HOME-01** (Priority: High) — Add/edit home properties with address, photos (display-optimized at upload and cached locally for hero display), and key info. * **FR-HOME-02** (Priority: High) — Service provider directory (propane, electric, internet, lawn care, etc.) with contacts and notes. * **FR-HOME-03** (Priority: High) — Editable, categorized documents for important details (UI section label: **Files**). Permitted users add files from the **camera**, **photo library**, or **file browser**; file detail offers **Preview** via system Quick Look (zoom, PDF, video, audio where supported), with metadata and actions below. +* **FR-HOME-04** (Priority: Low) — Home display names MUST be normalized for display by trimming leading/trailing whitespace and collapsing internal runs of whitespace to a single space. Pure helper; no UI, networking, or sync behavior. * **FR-NAV-01** (Priority: High) — Home detail MUST expose four sections labeled **Procedures**, **Contacts**, **Files**, and **People** with device-appropriate navigation (iPhone: hero + horizontal tabs; iPad: compact left-column hero + vertical icon tabs, with a **three-panel** layout — sidebar, section list, and section detail — for every section). * **FR-PROC-01** (Priority: High) — Add/edit procedure lists (e.g., winterizing, arrival prep) with persistent status (Not Started / In Progress / Complete / N/A). * **FR-PROC-02** (Priority: High) — Procedures contain ordered steps, each with its own status. Owner and Manager users can **create, rename, reorder, and delete** steps on procedures they can modify (per visibility). Guests have read-only access to step content and status. @@ -65,7 +66,6 @@ HomesFlow is a responsive iOS app (iPhone and iPad) that empowers primary homeow * **FR-NOTIF-01** (Priority: Medium) — Optional push notifications for changed statuses and new assignments. * **FR-LOG-01** (Priority: Medium) — History log of changes for accountability. * **FR-LOG-02** (Priority: Medium) — User-authored **Log Book**: Owners and Managers write free-form log entries at household scope or attached to a procedure, viewable in a unified chronological log alongside activity history. Entries are append-only offline, editable only within a 10-minute grace window that starts at server receipt. Guests have no Log Book access. -* **FR-CLEW-01** (Priority: Low) — **Clewseau trial.** Pure helper `HomeDisplayName.normalized(_:)` collapses whitespace in home display names (no UI, no sync). Temporary probe ID — tombstone after the cold-agent trial. --- @@ -224,6 +224,10 @@ Imagine Diane, a homeowner who spends most of her time in Florida, but owns a ch * **AC-HOME-13** — Given a user opens a file's detail, when they tap **Preview**, then the system Quick Look preview opens with zoom, scroll, and playback for supported types (images, PDFs, video, audio); detail shows a file summary, metadata, Preview action, share/download, and management actions below. * **AC-HOME-14** — Given an Owner or Manager adds a file, when they choose the file source, then camera capture, photo library, and file browser options are all offered, and content from any source uploads through the same metadata flow (title, category, visibility). +### FR-HOME-04 — Home display-name normalization + +* **AC-HOME-15** — Given a raw home display name with leading/trailing whitespace and internal whitespace runs (spaces, tabs, or newlines), when it is normalized for display, then the result has no leading or trailing whitespace and every internal whitespace run is collapsed to a single space. + ### US-ADMIN-02 / FR-USER-01 — Owner invites users * **AC-USER-01** — Given an Owner provides an email/phone and role and sends an invite, when the invite is accepted, then the invitee is added to the home with the assigned role and receives appropriate permissions immediately. @@ -291,12 +295,6 @@ Imagine Diane, a homeowner who spends most of her time in Florida, but owns a ch * **AC-A11Y-02** — Given VoiceOver is enabled, when the user navigates home section tabs (horizontal on iPhone, vertical on iPad), then each tab exposes a combined accessibility label (section name + role), selected state is announced, and interactive controls have meaningful hints where non-obvious. * **AC-A11Y-03** — Given Reduce Motion is enabled in iOS Settings, when the user navigates between sections or primary screens, then non-essential motion animations are omitted or reduced per system preference. -### US-CLEW-01 / FR-CLEW-01 — Clewseau cold-agent trial (temporary) - -> Temporary probe for stock Spec Kit + Clewseau cold-agent validation. Implement as a pure Swift helper + unit test only. Tombstone these IDs after the trial; do not reuse numbers. - -* **AC-CLEW-01** — Given a home display name string, when `HomeDisplayName.normalized(_:)` runs, then leading and trailing whitespace are stripped and any internal run of whitespace collapses to a single space. - --- ## ID Registry (authoritative) @@ -310,13 +308,14 @@ Imagine Diane, a homeowner who spends most of her time in Florida, but owns a ch | US-EDIT-02 | User Story | Manager user manages providers | | US-GUEST-01 | User Story | Guest sees limited info | | US-GUEST-02 | User Story | Guest views guest procedures | -| US-CLEW-01 | User Story | Clewseau cold-agent trial (temporary) | +| US-SHARE-01 | User Story | Share a procedure via read-only link (anointed backlog) | | FR-USER-01 | FR | Multi-role access per home | | FR-AUTH-01 | FR | OAuth / Apple sign-in | | FR-USER-02 | FR | Owner user management | | FR-HOME-01 | FR | Home CRUD | | FR-HOME-02 | FR | Service provider directory | | FR-HOME-03 | FR | Document library (UI: Files) | +| FR-HOME-04 | FR | Home display-name normalization | | FR-NAV-01 | FR | Home section navigation shell | | FR-PROC-01 | FR | Procedure lists with status | | FR-PROC-02 | FR | Procedure steps: status + CRUD/reorder | @@ -326,7 +325,6 @@ Imagine Diane, a homeowner who spends most of her time in Florida, but owns a ch | FR-NOTIF-01 | FR | Push notifications | | FR-LOG-01 | FR | Activity history log | | FR-LOG-02 | FR | User-authored Log Book | -| FR-CLEW-01 | FR | Clewseau trial: HomeDisplayName.normalized (temporary) | | NFR-OFFL-01 | NFR | Offline sync core requirement | | NFR-SYNC-01 | NFR | Sync under 1 second | | NFR-PERF-01 | NFR | Screen load under 2s; streaming file preview; throttled prefetch | @@ -334,11 +332,14 @@ Imagine Diane, a homeowner who spends most of her time in Florida, but owns a ch | NFR-SEC-01 | NFR | Encrypted storage | | NFR-SCALE-01 | NFR | 100k concurrent users | | NFR-A11Y-01 | NFR | iOS accessibility compliance | -| AC-HOME-01 … AC-HOME-14 | AC | Home, provider, navigation & files scenarios | +| AC-HOME-01 … AC-HOME-15 | AC | Home, provider, navigation, files & display-name scenarios | | AC-USER-01 … AC-USER-07 | AC | User invite & role scenarios | | AC-PROC-01 … AC-PROC-08 | AC | Procedure step scenarios | | AC-GUEST-01 … AC-GUEST-05 | AC | Guest access scenarios | | AC-SYNC-01 … AC-SYNC-07 | AC | Offline sync scenarios (05–07: data-type-aware model) | | AC-LOG-01 … AC-LOG-06 | AC | Log Book scenarios | | AC-A11Y-01 … AC-A11Y-03 | AC | Accessibility scenarios | -| AC-CLEW-01 | AC | Clewseau trial: display-name normalize (temporary) | + +### Retired IDs + +A temporary validation-probe requirement family (a pure display-name normalizer) was withdrawn during the SpecAssay migration. Its capability is being delivered as a first-class HomesFlow requirement. Retired ID slots are not reused. diff --git a/glossary.md b/glossary.md index 040087f..da6d22e 100644 --- a/glossary.md +++ b/glossary.md @@ -39,23 +39,23 @@ Domain language for specs, plans, and code. When wording is ambiguous, this file On **iPad**, each tab uses a three-panel layout: sidebar (home context + tabs) + section list + section detail (**FR-NAV-01**, **AC-HOME-10**). -## Clewseau vocabulary +## SpecAssay vocabulary -Traceability terms from the Clewseau Spec Kit overlay (also in constitution Article: Clewseau vocabulary). Do not invent synonyms — especially not “dossier” for the Gate artifact. +Traceability terms from the SpecAssay Spec Kit overlay (also in constitution Article: SpecAssay vocabulary). Do not invent synonyms — especially not “dossier” for the Gate artifact. | Term | Meaning | |------|---------| -| **clew** | The Gate-emitted traceability artifact (`format: "clew"`). Default filename `clew.json`. | -| **clew.json** | Usual on-disk path for a clew (configurable via Gate `clew_path`). | -| **Clewseau** | Spec Kit overlay: durable IDs, Gate 2, clew emission. Not Thorsten Schlathölter’s open-source `clew` tool. | -| **clewloupe** | Viewer that consumes a clew only — no target re-scan. | +| **trace-manifest** | The Gate-emitted traceability artifact (`format: "trace-manifest"`). Default filename `trace-manifest.json`. | +| **trace-manifest.json** | Usual on-disk path for a trace-manifest (configurable via Gate `manifest_path`). | +| **SpecAssay** | Spec Kit overlay: durable IDs, Gate 2, trace-manifest emission. | +| **Loupe** | Viewer that reads a trace-manifest only — no target re-scan. | | **verified** | Named carrier exists (AC proof and/or `@covers` / proof for US/FR/NFR). | -| **tracked-debt** | Incomplete, but declared on an open task with `Traces:`. | -| **GAP** | Silent AC gap — neither proof nor open debt; Gate refuses; thread frays. | +| **tracked-debt** | Incomplete, but declared on an open task with `Carries:`. | +| **GAP** | Silent AC gap — neither proof nor open debt; Gate refuses; the Golden Thread frays. | | **backlog** | US/FR/NFR with no own carrier — planning altitude, not a silent gap. | -| **Gate 2** | Deterministic Clewseau check + clew emit (`speckit.clewseau-gate.check`). | +| **Gate 2** | Deterministic SpecAssay check + trace-manifest emit (`speckit.specassay.check`). | -SDLC worked examples (AC-HOME-09 / AC-HOME-10 and peers): Clewseau repo `docs/reading-a-clew.md`. +SDLC worked examples (AC-HOME-09 / AC-HOME-10 and peers): SpecAssay repo `docs/reading-a-manifest.md`. ## MVP exclusions diff --git a/ios/HomesFlow/Core/Home/HomeDisplayName.swift b/ios/HomesFlow/Core/Home/HomeDisplayName.swift new file mode 100644 index 0000000..37f07d5 --- /dev/null +++ b/ios/HomesFlow/Core/Home/HomeDisplayName.swift @@ -0,0 +1,16 @@ +import Foundation + +// @covers FR-HOME-04, AC-HOME-15 + +/// Pure display-name normalization for homes (FR-HOME-04): trims leading and +/// trailing whitespace and collapses every internal run of whitespace +/// (spaces, tabs, newlines) to a single space. No UI, networking, or sync. +enum HomeDisplayName { + static func normalized(_ raw: String) -> String { + let separators = CharacterSet.whitespacesAndNewlines + return raw + .components(separatedBy: separators) + .filter { !$0.isEmpty } + .joined(separator: " ") + } +} diff --git a/ios/HomesFlowTests/HomeDisplayNameTests.swift b/ios/HomesFlowTests/HomeDisplayNameTests.swift new file mode 100644 index 0000000..bd8ba2a --- /dev/null +++ b/ios/HomesFlowTests/HomeDisplayNameTests.swift @@ -0,0 +1,27 @@ +import XCTest +@testable import HomesFlow + +final class HomeDisplayNameTests: XCTestCase { + func test_AC_HOME_15_normalizes_whitespace() { + // Leading/trailing whitespace is trimmed. + XCTAssertEqual(HomeDisplayName.normalized(" Rockport Cottage "), "Rockport Cottage") + + // Internal runs of spaces collapse to a single space. + XCTAssertEqual(HomeDisplayName.normalized("Rockport Cottage"), "Rockport Cottage") + + // Tabs and newlines are treated as whitespace and collapsed. + XCTAssertEqual(HomeDisplayName.normalized("Rockport\t\tCottage"), "Rockport Cottage") + XCTAssertEqual(HomeDisplayName.normalized("Rockport\nCottage"), "Rockport Cottage") + + // Mixed whitespace kinds in one run collapse to a single space, with trimming. + XCTAssertEqual(HomeDisplayName.normalized("\n Rockport \t\n Cottage \tby the Sea \n"), + "Rockport Cottage by the Sea") + + // Already-clean input is returned unchanged. + XCTAssertEqual(HomeDisplayName.normalized("Rockport Cottage"), "Rockport Cottage") + + // Empty and whitespace-only input normalize to an empty string. + XCTAssertEqual(HomeDisplayName.normalized(""), "") + XCTAssertEqual(HomeDisplayName.normalized(" \t\n "), "") + } +} diff --git a/specs/001-mvp/tasks.md b/specs/001-mvp/tasks.md index d38ebd2..0b5cdf4 100644 --- a/specs/001-mvp/tasks.md +++ b/specs/001-mvp/tasks.md @@ -25,7 +25,7 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ## Format -- **Traces**: AC/FR/NFR ID(s) implemented +- **Carries**: AC/FR/NFR ID(s) implemented - **[P]**: Parallelizable - **[US-*]**: User story label @@ -35,9 +35,9 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en **Purpose**: Repo tooling and project skeleton -- [x] T001 Create `ios/HomesFlow.xcodeproj` — universal iOS 17+ SwiftUI app target + unit/UI test targets per plan.md — **Traces**: plan Phase 0 (infrastructure) -- [x] T002 [P] Initialize `supabase/` with `config.toml` and local dev setup per quickstart.md — **Traces**: plan Phase 0 (infrastructure) -- [x] T003 [P] Add `.gitignore` entries for `Secrets.xcconfig`, DerivedData (verify ios secrets not committed) — **Traces**: NFR-SEC-01 +- [x] T001 Create `ios/HomesFlow.xcodeproj` — universal iOS 17+ SwiftUI app target + unit/UI test targets per plan.md — **Carries**: plan Phase 0 (infrastructure) +- [x] T002 [P] Initialize `supabase/` with `config.toml` and local dev setup per quickstart.md — **Carries**: plan Phase 0 (infrastructure) +- [x] T003 [P] Add `.gitignore` entries for `Secrets.xcconfig`, DerivedData (verify ios secrets not committed) — **Carries**: NFR-SEC-01 --- @@ -45,16 +45,16 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en **Purpose**: Backend schema, auth, local cache, sync skeleton — **no user story UI until complete** -- [x] T004 Write `supabase/migrations/001_initial_schema.sql` from data-model.md (enums, tables, indexes) — **Traces**: FR-HOME-01, FR-USER-01, FR-PROC-01 -- [x] T005 Implement RLS policies + `get_user_role()` per contracts/rls-permissions.md — **Traces**: FR-USER-01, FR-GUEST-01, AC-GUEST-02 -- [x] T006 [P] Configure Supabase Auth: Apple + email/password providers — **Traces**: FR-AUTH-01 — *email/password done; Apple app-side shipped 2026-07-08; cloud dashboard provider remains ops step (D12)* -- [x] T007 [P] Create Supabase Storage buckets + policies (home-photos, documents, procedure-attachments) — **Traces**: FR-HOME-01, FR-HOME-03, FR-PROC-03 — *002_storage_profiles_invites.sql* -- [x] T008 Implement `ios/HomesFlow/Core/Supabase/SupabaseClientProvider.swift` + Keychain session — **Traces**: FR-AUTH-01, NFR-SEC-01 — *supabase-swift session + authStateChanges* -- [x] T009 Define SwiftData models mirroring server tables + `MutationOutbox` in `ios/HomesFlow/Core/Models/` — **Traces**: NFR-OFFL-01 -- [x] T010 Implement `PermissionService` matching RLS matrix in `ios/HomesFlow/Core/Permissions/` — **Traces**: FR-USER-01, AC-PROC-02, AC-GUEST-02 -- [x] T011 Implement `SyncEngine` skeleton (outbox enqueue, push, pull, revert-on-deny) in `ios/HomesFlow/Core/Sync/` — **Traces**: NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03 — *homes push/pull; partial AC-SYNC-02* -- [x] T012 Implement `ActivityLogService` append + fetch in `ios/HomesFlow/Core/ActivityLog/` — **Traces**: FR-LOG-01 — *append done; fetch UI pending* -- [x] T013 Wire `AppRouter` + root auth gate in `ios/HomesFlow/App/` — **Traces**: FR-AUTH-01 +- [x] T004 Write `supabase/migrations/001_initial_schema.sql` from data-model.md (enums, tables, indexes) — **Carries**: FR-HOME-01, FR-USER-01, FR-PROC-01 +- [x] T005 Implement RLS policies + `get_user_role()` per contracts/rls-permissions.md — **Carries**: FR-USER-01, FR-GUEST-01, AC-GUEST-02 +- [x] T006 [P] Configure Supabase Auth: Apple + email/password providers — **Carries**: FR-AUTH-01 — *email/password done; Apple app-side shipped 2026-07-08; cloud dashboard provider remains ops step (D12)* +- [x] T007 [P] Create Supabase Storage buckets + policies (home-photos, documents, procedure-attachments) — **Carries**: FR-HOME-01, FR-HOME-03, FR-PROC-03 — *002_storage_profiles_invites.sql* +- [x] T008 Implement `ios/HomesFlow/Core/Supabase/SupabaseClientProvider.swift` + Keychain session — **Carries**: FR-AUTH-01, NFR-SEC-01 — *supabase-swift session + authStateChanges* +- [x] T009 Define SwiftData models mirroring server tables + `MutationOutbox` in `ios/HomesFlow/Core/Models/` — **Carries**: NFR-OFFL-01 +- [x] T010 Implement `PermissionService` matching RLS matrix in `ios/HomesFlow/Core/Permissions/` — **Carries**: FR-USER-01, AC-PROC-02, AC-GUEST-02 +- [x] T011 Implement `SyncEngine` skeleton (outbox enqueue, push, pull, revert-on-deny) in `ios/HomesFlow/Core/Sync/` — **Carries**: NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03 — *homes push/pull; partial AC-SYNC-02* +- [x] T012 Implement `ActivityLogService` append + fetch in `ios/HomesFlow/Core/ActivityLog/` — **Carries**: FR-LOG-01 — *append done; fetch UI pending* +- [x] T013 Wire `AppRouter` + root auth gate in `ios/HomesFlow/App/` — **Carries**: FR-AUTH-01 **Checkpoint**: Sign-in works; empty authenticated shell loads; migrations apply cleanly. ✅ @@ -66,13 +66,13 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T014 [US-ADMIN-01] Auth screens: email/password sign-up, sign-in, Sign in with Apple in `ios/HomesFlow/Features/Auth/` — **Traces**: FR-AUTH-01 — *Apple wired 2026-07-08* -- [x] T015 [US-ADMIN-01] Dashboard home list view (SwiftUI) — full-bleed photo hero cards with name/address in `ios/HomesFlow/Features/Dashboard/` — **Traces**: FR-HOME-01, AC-SYNC-04 — *procedure count placeholder not yet* -- [x] T016 [P] [US-ADMIN-01] Adaptive layout: iPhone `NavigationStack`, iPad `NavigationSplitView` shell for dashboard — **Traces**: NFR-PERF-01 +- [x] T014 [US-ADMIN-01] Auth screens: email/password sign-up, sign-in, Sign in with Apple in `ios/HomesFlow/Features/Auth/` — **Carries**: FR-AUTH-01 — *Apple wired 2026-07-08* +- [x] T015 [US-ADMIN-01] Dashboard home list view (SwiftUI) — full-bleed photo hero cards with name/address in `ios/HomesFlow/Features/Dashboard/` — **Carries**: FR-HOME-01, AC-SYNC-04 — *procedure count placeholder not yet* +- [x] T016 [P] [US-ADMIN-01] Adaptive layout: iPhone `NavigationStack`, iPad `NavigationSplitView` shell for dashboard — **Carries**: NFR-PERF-01 ### Tests -- [ ] T017 [P] [US-ADMIN-01] XCUITest: sign-in with email → dashboard visible — **Traces**: FR-AUTH-01 +- [ ] T017 [P] [US-ADMIN-01] XCUITest: sign-in with email → dashboard visible — **Carries**: FR-AUTH-01 --- @@ -82,26 +82,26 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T018 [US-ADMIN-01] Home create/edit form + validation in `ios/HomesFlow/Features/HomeSetup/` — **Traces**: AC-HOME-01, AC-HOME-02, FR-HOME-01 -- [x] T019 [US-ADMIN-01] Home photo pick + upload to Supabase Storage — **Traces**: AC-HOME-01, AC-HOME-08, FR-HOME-01 — *sync-before-upload* -- [x] T019a [P] [US-ADMIN-01] Home photo display optimization: resize on upload, disk/memory cache, signed-URL reuse, throttled dashboard prefetch (max 2 concurrent) in `ios/HomesFlow/Core/Storage/` — **Traces**: AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01 -- [x] T020 [US-ADMIN-01] Home edit offline + sync conflict (timestamp wins + activity log) — **Traces**: AC-HOME-03, AC-SYNC-01, FR-LOG-01 — *HomeConflictResolver + merge; full offline E2E pending* -- [x] T021 [P] [US-ADMIN-01] Home detail full-bleed photo hero header + tab bar shell (Procedures | Contacts | Documents | People) — **Traces**: FR-HOME-01 — *superseded by T021a–b* -- [x] T021a [P] [US-ADMIN-01] iPad home detail: compact left-column hero + vertical icon tabs; trailing area three-panel (list | detail) for **all** sections — **Traces**: FR-NAV-01, AC-HOME-09, AC-HOME-10 — *ContactsView, FilesView, MembersView split shells* -- [x] T021b [P] [US-ADMIN-01] Rename Documents UI tab to **Files**; add SF Symbol icons to all four section tabs (iPhone horizontal + iPad vertical list) — **Traces**: FR-NAV-01, AC-HOME-11, AC-A11Y-02 -- [x] T021c [P] [US-ADMIN-01] iPad: **All Homes** navigation from home detail back to dashboard (sidebar not persistent home picker) — **Traces**: AC-HOME-10, FR-NAV-01 +- [x] T018 [US-ADMIN-01] Home create/edit form + validation in `ios/HomesFlow/Features/HomeSetup/` — **Carries**: AC-HOME-01, AC-HOME-02, FR-HOME-01 +- [x] T019 [US-ADMIN-01] Home photo pick + upload to Supabase Storage — **Carries**: AC-HOME-01, AC-HOME-08, FR-HOME-01 — *sync-before-upload* +- [x] T019a [P] [US-ADMIN-01] Home photo display optimization: resize on upload, disk/memory cache, signed-URL reuse, throttled dashboard prefetch (max 2 concurrent) in `ios/HomesFlow/Core/Storage/` — **Carries**: AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01 +- [x] T020 [US-ADMIN-01] Home edit offline + sync conflict (timestamp wins + activity log) — **Carries**: AC-HOME-03, AC-SYNC-01, FR-LOG-01 — *HomeConflictResolver + merge; full offline E2E pending* +- [x] T021 [P] [US-ADMIN-01] Home detail full-bleed photo hero header + tab bar shell (Procedures | Contacts | Documents | People) — **Carries**: FR-HOME-01 — *superseded by T021a–b* +- [x] T021a [P] [US-ADMIN-01] iPad home detail: compact left-column hero + vertical icon tabs; trailing area three-panel (list | detail) for **all** sections — **Carries**: FR-NAV-01, AC-HOME-09, AC-HOME-10 — *ContactsView, FilesView, MembersView split shells* +- [x] T021b [P] [US-ADMIN-01] Rename Documents UI tab to **Files**; add SF Symbol icons to all four section tabs (iPhone horizontal + iPad vertical list) — **Carries**: FR-NAV-01, AC-HOME-11, AC-A11Y-02 +- [x] T021c [P] [US-ADMIN-01] iPad: **All Homes** navigation from home detail back to dashboard (sidebar not persistent home picker) — **Carries**: AC-HOME-10, FR-NAV-01 ### Tests -- [ ] T022 [P] [US-ADMIN-01] Unit test `test_AC_HOME_01_valid_home_created` — **Traces**: AC-HOME-01 — *validator only; integration test pending* -- [x] T023 [P] [US-ADMIN-01] Unit test `test_AC_HOME_02_invalid_home_rejected` — **Traces**: AC-HOME-02 -- [x] T024 [US-ADMIN-01] Unit test `test_AC_HOME_03_offline_edit_conflict_logged` — **Traces**: AC-HOME-03 — *HomeConflictResolverTests; activity log integration pending* -- [x] T024a [P] [US-ADMIN-01] Unit test `test_AC_HOME_06_upload_resizes_before_storage` — **Traces**: AC-HOME-06 — *HomePhotoTests* -- [x] T024b [P] [US-ADMIN-01] Unit test `test_AC_HOME_07_hero_renders_from_local_cache` — **Traces**: AC-HOME-07 — *HomePhotoTests: memory + disk layers* -- [x] T024c [P] [US-ADMIN-01] Unit test `test_AC_HOME_08_photo_blocked_until_home_synced` — **Traces**: AC-HOME-08 — *HomePhotoSyncGate extracted from HomeRepository* -- [ ] T024d [P] [US-ADMIN-01] Snapshot or UI test: iPad home detail trailing column has no hero/segmented tabs — **Traces**: AC-HOME-09 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* -- [ ] T024e [P] [US-ADMIN-01] Snapshot or UI test: iPad leading column shows compact home hero + vertical icon section tabs — **Traces**: AC-HOME-10 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* -- [ ] T024f [P] [US-ADMIN-01] UI test: Contacts, Files, and People sections use the three-panel layout on iPad — **Traces**: AC-HOME-11 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* +- [ ] T022 [P] [US-ADMIN-01] Unit test `test_AC_HOME_01_valid_home_created` — **Carries**: AC-HOME-01 — *validator only; integration test pending* +- [x] T023 [P] [US-ADMIN-01] Unit test `test_AC_HOME_02_invalid_home_rejected` — **Carries**: AC-HOME-02 +- [x] T024 [US-ADMIN-01] Unit test `test_AC_HOME_03_offline_edit_conflict_logged` — **Carries**: AC-HOME-03 — *HomeConflictResolverTests; activity log integration pending* +- [x] T024a [P] [US-ADMIN-01] Unit test `test_AC_HOME_06_upload_resizes_before_storage` — **Carries**: AC-HOME-06 — *HomePhotoTests* +- [x] T024b [P] [US-ADMIN-01] Unit test `test_AC_HOME_07_hero_renders_from_local_cache` — **Carries**: AC-HOME-07 — *HomePhotoTests: memory + disk layers* +- [x] T024c [P] [US-ADMIN-01] Unit test `test_AC_HOME_08_photo_blocked_until_home_synced` — **Carries**: AC-HOME-08 — *HomePhotoSyncGate extracted from HomeRepository* +- [ ] T024d [P] [US-ADMIN-01] Snapshot or UI test: iPad home detail trailing column has no hero/segmented tabs — **Carries**: AC-HOME-09 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* +- [ ] T024e [P] [US-ADMIN-01] Snapshot or UI test: iPad leading column shows compact home hero + vertical icon section tabs — **Carries**: AC-HOME-10 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* +- [ ] T024f [P] [US-ADMIN-01] UI test: Contacts, Files, and People sections use the three-panel layout on iPad — **Carries**: AC-HOME-11 — *deferred: needs snapshot/XCUITest infra; manual iPad pass until then* --- @@ -111,21 +111,21 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T025 [US-ADMIN-02] Invite flow: create/revoke invite token, share invite link in `ios/HomesFlow/Features/Members/` — **Traces**: AC-USER-01, AC-USER-02, AC-USER-07, FR-GUEST-02, FR-USER-02 — *share sheet; no email/SMS send* -- [x] T026 [US-ADMIN-02] Accept invite → create membership with role — **Traces**: AC-USER-01, AC-USER-07 — *paste token + RPC; deep link pending* -- [ ] T027 [US-ADMIN-02] Offline invite conflict resolution — **Traces**: AC-USER-03, AC-SYNC-01 -- [x] T028 [US-ADMIN-03] Members list (People tab) + role assignment UI — **Traces**: AC-USER-04, AC-USER-05, AC-USER-06, FR-USER-02 -- [x] T029 [US-ADMIN-03] Role change sync + audit entry for prior role — **Traces**: AC-USER-06, FR-LOG-01 — *activity log append; concurrent conflict partial* +- [x] T025 [US-ADMIN-02] Invite flow: create/revoke invite token, share invite link in `ios/HomesFlow/Features/Members/` — **Carries**: AC-USER-01, AC-USER-02, AC-USER-07, FR-GUEST-02, FR-USER-02 — *share sheet; no email/SMS send* +- [x] T026 [US-ADMIN-02] Accept invite → create membership with role — **Carries**: AC-USER-01, AC-USER-07 — *paste token + RPC; deep link pending* +- [ ] T027 [US-ADMIN-02] Offline invite conflict resolution — **Carries**: AC-USER-03, AC-SYNC-01 +- [x] T028 [US-ADMIN-03] Members list (People tab) + role assignment UI — **Carries**: AC-USER-04, AC-USER-05, AC-USER-06, FR-USER-02 +- [x] T029 [US-ADMIN-03] Role change sync + audit entry for prior role — **Carries**: AC-USER-06, FR-LOG-01 — *activity log append; concurrent conflict partial* ### Tests -- [x] T030 [P] [US-ADMIN-02] Unit test `test_AC_USER_01_invite_accepted_grants_role` — **Traces**: AC-USER-01 — *MemberInviteTests over InvitePolicy* -- [x] T031 [P] [US-ADMIN-02] Unit test `test_AC_USER_02_revoked_token_invalid` — **Traces**: AC-USER-02 — *MemberInviteTests; server enforcement in accept_invite RPC* -- [x] T032 [US-ADMIN-03] Unit test `test_AC_USER_04_edit_role_can_modify_procedures` — **Traces**: AC-USER-04 — *MemberInviteTests; exposed PermissionService visibility gap on create/update/delete, fixed* -- [x] T033 [US-ADMIN-03] Unit test `test_AC_USER_05_guest_role_read_only` — **Traces**: AC-USER-05 — *GuestTests* -- [ ] T033a [P] [US-ADMIN-02] Unit test `test_AC_USER_03_offline_invite_conflict` — **Traces**: AC-USER-03 -- [x] T033b [US-ADMIN-03] Unit test `test_AC_USER_06_concurrent_role_change_audit` — **Traces**: AC-USER-06 — *MemberInviteTests over RoleChangeAudit + MembershipMerge* -- [x] T033c [P] [US-ADMIN-02] Unit test `test_AC_USER_07_paste_token_accepts_invite` — **Traces**: AC-USER-07 — *MemberInviteTests; AcceptInviteView now accepts full invite links via InvitePolicy.extractToken* +- [x] T030 [P] [US-ADMIN-02] Unit test `test_AC_USER_01_invite_accepted_grants_role` — **Carries**: AC-USER-01 — *MemberInviteTests over InvitePolicy* +- [x] T031 [P] [US-ADMIN-02] Unit test `test_AC_USER_02_revoked_token_invalid` — **Carries**: AC-USER-02 — *MemberInviteTests; server enforcement in accept_invite RPC* +- [x] T032 [US-ADMIN-03] Unit test `test_AC_USER_04_edit_role_can_modify_procedures` — **Carries**: AC-USER-04 — *MemberInviteTests; exposed PermissionService visibility gap on create/update/delete, fixed* +- [x] T033 [US-ADMIN-03] Unit test `test_AC_USER_05_guest_role_read_only` — **Carries**: AC-USER-05 — *GuestTests* +- [ ] T033a [P] [US-ADMIN-02] Unit test `test_AC_USER_03_offline_invite_conflict` — **Carries**: AC-USER-03 +- [x] T033b [US-ADMIN-03] Unit test `test_AC_USER_06_concurrent_role_change_audit` — **Carries**: AC-USER-06 — *MemberInviteTests over RoleChangeAudit + MembershipMerge* +- [x] T033c [P] [US-ADMIN-02] Unit test `test_AC_USER_07_paste_token_accepts_invite` — **Carries**: AC-USER-07 — *MemberInviteTests; AcceptInviteView now accepts full invite links via InvitePolicy.extractToken* --- @@ -135,17 +135,17 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T034 [NFR-OFFL-01] Network reachability → trigger `SyncEngine.run()` on reconnect in `ios/HomesFlow/Core/Sync/` — **Traces**: AC-SYNC-01 -- [ ] T035 [NFR-OFFL-01] Field-level merge for non-conflicting offline edits — **Traces**: AC-SYNC-02 — *deferred post-MVP (2026-07-03 conflict model decision); pairs with version vectors* -- [x] T036 [NFR-OFFL-01] Stale-permission revert + user-facing error — **Traces**: AC-SYNC-03 — *revert + SyncNotification* -- [x] T037 [P] [NFR-OFFL-01] In-app conflict/overwrite notification banners + pending-sync dashboard indicators — **Traces**: AC-SYNC-01, AC-SYNC-04, AC-PROC-03, AC-HOME-05 — *dashboard sync banners, cloud icons, pull-to-refresh* +- [x] T034 [NFR-OFFL-01] Network reachability → trigger `SyncEngine.run()` on reconnect in `ios/HomesFlow/Core/Sync/` — **Carries**: AC-SYNC-01 +- [ ] T035 [NFR-OFFL-01] Field-level merge for non-conflicting offline edits — **Carries**: AC-SYNC-02 — *deferred post-MVP (2026-07-03 conflict model decision); pairs with version vectors* +- [x] T036 [NFR-OFFL-01] Stale-permission revert + user-facing error — **Carries**: AC-SYNC-03 — *revert + SyncNotification* +- [x] T037 [P] [NFR-OFFL-01] In-app conflict/overwrite notification banners + pending-sync dashboard indicators — **Carries**: AC-SYNC-01, AC-SYNC-04, AC-PROC-03, AC-HOME-05 — *dashboard sync banners, cloud icons, pull-to-refresh* ### Tests -- [x] T038 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_01_offline_overwrite_notifies_loser` — **Traces**: AC-SYNC-01 — *SyncConflictMatrixTests over OverwriteNotificationPolicy; SyncEngine home merge now posts loser notification (steps/providers already did)* -- [ ] T039 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_02_disjoint_fields_merge` — **Traces**: AC-SYNC-02 — *deferred post-MVP with T035* -- [x] T040 [NFR-OFFL-01] Unit test `test_AC_SYNC_03_stale_permission_reverts` — **Traces**: AC-SYNC-03 — *covered by SyncConflictMatrixTests.test_AC_SYNC_03_permission_denied_revert_matrix over PermissionRevertPolicy* -- [x] T040a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_04_pending_sync_visible_on_dashboard` — **Traces**: AC-SYNC-04 — *SyncIndicatorTests over SyncIndicatorPolicy* +- [x] T038 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_01_offline_overwrite_notifies_loser` — **Carries**: AC-SYNC-01 — *SyncConflictMatrixTests over OverwriteNotificationPolicy; SyncEngine home merge now posts loser notification (steps/providers already did)* +- [ ] T039 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_02_disjoint_fields_merge` — **Carries**: AC-SYNC-02 — *deferred post-MVP with T035* +- [x] T040 [NFR-OFFL-01] Unit test `test_AC_SYNC_03_stale_permission_reverts` — **Carries**: AC-SYNC-03 — *covered by SyncConflictMatrixTests.test_AC_SYNC_03_permission_denied_revert_matrix over PermissionRevertPolicy* +- [x] T040a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_04_pending_sync_visible_on_dashboard` — **Carries**: AC-SYNC-04 — *SyncIndicatorTests over SyncIndicatorPolicy* **Checkpoint**: P1 stories independently testable. ⏳ *Pending T027, sync tests, Procedures for full demo* @@ -157,27 +157,27 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T041 [US-EDIT-01] Procedures list with progress (e.g. 2/6) in `ios/HomesFlow/Features/Procedures/` — **Traces**: FR-PROC-01, FR-PROC-02 -- [x] T042 [US-EDIT-01] Procedure detail: step checklist, status toggle, N/A option — **Traces**: FR-PROC-02, AC-PROC-01 -- [x] T043 [US-EDIT-01] Step notes + photo attach (Storage) — **Traces**: FR-PROC-03 -- [x] T044 [US-EDIT-01] Block step update when permission insufficient — **Traces**: AC-PROC-02 -- [x] T045 [US-EDIT-01] Offline step conflict + notification — **Traces**: AC-PROC-03, AC-SYNC-01 -- [x] T046 [P] [US-EDIT-01] Recent activity section on procedure detail — **Traces**: FR-LOG-01, AC-PROC-01 -- [x] T047 [P] [US-EDIT-01] iPad: procedure list + detail columns — **Traces**: NFR-PERF-01 -- [x] T047a [US-EDIT-01] Step structure sync: create, rename, delete, reorder in `ProcedureRepository` + `SyncEngine` — **Traces**: FR-PROC-02, AC-PROC-06 -- [x] T047b [US-EDIT-01] Long-press step context menu (rename, delete, move up/down) + Add step on Steps section — **Traces**: AC-PROC-04, AC-PROC-05 -- [x] T047c [US-EDIT-01] Hide step structure controls for Guest (read-only) — **Traces**: AC-PROC-07, AC-GUEST-04 -- [x] T047d [US-EDIT-01] Step row UI: notes below title, pencil Edit, tappable photo preview, ellipsis rightmost — **Traces**: AC-PROC-08 +- [x] T041 [US-EDIT-01] Procedures list with progress (e.g. 2/6) in `ios/HomesFlow/Features/Procedures/` — **Carries**: FR-PROC-01, FR-PROC-02 +- [x] T042 [US-EDIT-01] Procedure detail: step checklist, status toggle, N/A option — **Carries**: FR-PROC-02, AC-PROC-01 +- [x] T043 [US-EDIT-01] Step notes + photo attach (Storage) — **Carries**: FR-PROC-03 +- [x] T044 [US-EDIT-01] Block step update when permission insufficient — **Carries**: AC-PROC-02 +- [x] T045 [US-EDIT-01] Offline step conflict + notification — **Carries**: AC-PROC-03, AC-SYNC-01 +- [x] T046 [P] [US-EDIT-01] Recent activity section on procedure detail — **Carries**: FR-LOG-01, AC-PROC-01 +- [x] T047 [P] [US-EDIT-01] iPad: procedure list + detail columns — **Carries**: NFR-PERF-01 +- [x] T047a [US-EDIT-01] Step structure sync: create, rename, delete, reorder in `ProcedureRepository` + `SyncEngine` — **Carries**: FR-PROC-02, AC-PROC-06 +- [x] T047b [US-EDIT-01] Long-press step context menu (rename, delete, move up/down) + Add step on Steps section — **Carries**: AC-PROC-04, AC-PROC-05 +- [x] T047c [US-EDIT-01] Hide step structure controls for Guest (read-only) — **Carries**: AC-PROC-07, AC-GUEST-04 +- [x] T047d [US-EDIT-01] Step row UI: notes below title, pencil Edit, tappable photo preview, ellipsis rightmost — **Carries**: AC-PROC-08 ### Tests -- [x] T048 [P] [US-EDIT-01] Unit test `test_AC_PROC_01_step_complete_creates_log` — **Traces**: AC-PROC-01 — *ProcedureAggregatorTests* -- [x] T049 [P] [US-EDIT-01] Unit test `test_AC_PROC_02_permission_denied_blocks_update` — **Traces**: AC-PROC-02 -- [x] T050 [US-EDIT-01] Unit test `test_AC_PROC_03_offline_step_conflict` — **Traces**: AC-PROC-03 -- [x] T050a [P] [US-EDIT-01] Unit test `test_AC_PROC_04_manager_can_manage_step_structure` — **Traces**: AC-PROC-04, AC-PROC-05 — *StepStructureTests* -- [x] T050b [P] [US-EDIT-01] Unit test `test_AC_PROC_06_step_structure_change_logged` — **Traces**: AC-PROC-06 — *StepStructureTests* -- [x] T050c [P] [US-EDIT-01] Unit test `test_AC_PROC_07_guest_no_step_structure_controls` — **Traces**: AC-PROC-07 — *StepStructureTests* -- [x] T050d [P] [US-EDIT-01] Unit test `test_AC_PROC_08_photo_indicator_and_edit_controls` — **Traces**: AC-PROC-08 — *StepRowPresentationTests over StepRowPresentation (indicator, edit gating, strike-through, tap toggle); XCUITest of preview sheet deferred with T024d–f UI-test infra* +- [x] T048 [P] [US-EDIT-01] Unit test `test_AC_PROC_01_step_complete_creates_log` — **Carries**: AC-PROC-01 — *ProcedureAggregatorTests* +- [x] T049 [P] [US-EDIT-01] Unit test `test_AC_PROC_02_permission_denied_blocks_update` — **Carries**: AC-PROC-02 +- [x] T050 [US-EDIT-01] Unit test `test_AC_PROC_03_offline_step_conflict` — **Carries**: AC-PROC-03 +- [x] T050a [P] [US-EDIT-01] Unit test `test_AC_PROC_04_manager_can_manage_step_structure` — **Carries**: AC-PROC-04, AC-PROC-05 — *StepStructureTests* +- [x] T050b [P] [US-EDIT-01] Unit test `test_AC_PROC_06_step_structure_change_logged` — **Carries**: AC-PROC-06 — *StepStructureTests* +- [x] T050c [P] [US-EDIT-01] Unit test `test_AC_PROC_07_guest_no_step_structure_controls` — **Carries**: AC-PROC-07 — *StepStructureTests* +- [x] T050d [P] [US-EDIT-01] Unit test `test_AC_PROC_08_photo_indicator_and_edit_controls` — **Carries**: AC-PROC-08 — *StepRowPresentationTests over StepRowPresentation (indicator, edit gating, strike-through, tap toggle); XCUITest of preview sheet deferred with T024d–f UI-test infra* --- @@ -187,15 +187,15 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T051 [US-EDIT-02] Service provider list + search in `ios/HomesFlow/Features/Providers/` (UI tab: Contacts) — **Traces**: FR-HOME-02, AC-HOME-04 -- [x] T052 [US-EDIT-02] Provider create/edit form (company, type, phone, website, hours, notes) — **Traces**: AC-HOME-04 -- [x] T053 [US-EDIT-02] Provider edit vs delete sync conflict — **Traces**: AC-HOME-05, AC-SYNC-01 -- [x] T054 [P] [US-EDIT-02] Tap phone → `tel:` link — **Traces**: FR-HOME-02 +- [x] T051 [US-EDIT-02] Service provider list + search in `ios/HomesFlow/Features/Providers/` (UI tab: Contacts) — **Carries**: FR-HOME-02, AC-HOME-04 +- [x] T052 [US-EDIT-02] Provider create/edit form (company, type, phone, website, hours, notes) — **Carries**: AC-HOME-04 +- [x] T053 [US-EDIT-02] Provider edit vs delete sync conflict — **Carries**: AC-HOME-05, AC-SYNC-01 +- [x] T054 [P] [US-EDIT-02] Tap phone → `tel:` link — **Carries**: FR-HOME-02 ### Tests -- [x] T055 [P] [US-EDIT-02] Unit test `test_AC_HOME_04_provider_edit_propagates` — **Traces**: AC-HOME-04 — *ProviderTests* -- [x] T056 [US-EDIT-02] Unit test `test_AC_HOME_05_delete_wins_over_edit` — **Traces**: AC-HOME-05 — *ProviderTests* +- [x] T055 [P] [US-EDIT-02] Unit test `test_AC_HOME_04_provider_edit_propagates` — **Carries**: AC-HOME-04 — *ProviderTests* +- [x] T056 [US-EDIT-02] Unit test `test_AC_HOME_05_delete_wins_over_edit` — **Carries**: AC-HOME-05 — *ProviderTests* --- @@ -205,20 +205,20 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T057 [US-GUEST-01] Visibility filtering on providers, documents, procedures — **Traces**: AC-GUEST-01, FR-GUEST-01 — *repository filters + guest tabs; documents via DocumentRepository* -- [x] T058 [US-GUEST-01] Navigation guard: deny restricted content with message — **Traces**: AC-GUEST-02 — *GuestAccessDeniedView + access state checks* -- [x] T059 [US-GUEST-01] Offline visibility sync — **Traces**: AC-GUEST-03 — *timestamp-wins procedure/provider merge* -- [x] T060 [US-GUEST-02] Guest procedure detail read-only (no status edits) — **Traces**: AC-GUEST-04 — *canEdit false + footer hint* -- [x] T061 [US-GUEST-02] Reject guest step status change + audit unauthorized attempt — **Traces**: AC-GUEST-05, FR-LOG-01 — *permission gate + activity log* +- [x] T057 [US-GUEST-01] Visibility filtering on providers, documents, procedures — **Carries**: AC-GUEST-01, FR-GUEST-01 — *repository filters + guest tabs; documents via DocumentRepository* +- [x] T058 [US-GUEST-01] Navigation guard: deny restricted content with message — **Carries**: AC-GUEST-02 — *GuestAccessDeniedView + access state checks* +- [x] T059 [US-GUEST-01] Offline visibility sync — **Carries**: AC-GUEST-03 — *timestamp-wins procedure/provider merge* +- [x] T060 [US-GUEST-02] Guest procedure detail read-only (no status edits) — **Carries**: AC-GUEST-04 — *canEdit false + footer hint* +- [x] T061 [US-GUEST-02] Reject guest step status change + audit unauthorized attempt — **Carries**: AC-GUEST-05, FR-LOG-01 — *permission gate + activity log* ### Tests -- [x] T062 [P] [US-GUEST-01] Unit test `test_AC_GUEST_01_guest_fields_only` — **Traces**: AC-GUEST-01 — *GuestTests* -- [x] T063 [P] [US-GUEST-02] Unit test `test_AC_GUEST_05_unauthorized_step_rejected` — **Traces**: AC-GUEST-05 — *PermissionServiceTests* -- [x] T063a [P] [US-GUEST-01] Unit test `test_AC_GUEST_02_restricted_deep_link_denied` — **Traces**: AC-GUEST-02 — *GuestTests* -- [x] T063b [P] [US-GUEST-01] Unit test `test_AC_GUEST_03_offline_visibility_sync` — **Traces**: AC-GUEST-03 — *GuestTests* -- [x] T063c [P] [US-GUEST-02] Unit test `test_AC_GUEST_04_guest_procedure_read_only` — **Traces**: AC-GUEST-04 — *GuestTests* -- [ ] T064 [US-GUEST-01] XCUITest: guest cannot edit provider — **Traces**: AC-GUEST-01, SC-03 +- [x] T062 [P] [US-GUEST-01] Unit test `test_AC_GUEST_01_guest_fields_only` — **Carries**: AC-GUEST-01 — *GuestTests* +- [x] T063 [P] [US-GUEST-02] Unit test `test_AC_GUEST_05_unauthorized_step_rejected` — **Carries**: AC-GUEST-05 — *PermissionServiceTests* +- [x] T063a [P] [US-GUEST-01] Unit test `test_AC_GUEST_02_restricted_deep_link_denied` — **Carries**: AC-GUEST-02 — *GuestTests* +- [x] T063b [P] [US-GUEST-01] Unit test `test_AC_GUEST_03_offline_visibility_sync` — **Carries**: AC-GUEST-03 — *GuestTests* +- [x] T063c [P] [US-GUEST-02] Unit test `test_AC_GUEST_04_guest_procedure_read_only` — **Carries**: AC-GUEST-04 — *GuestTests* +- [ ] T064 [US-GUEST-01] XCUITest: guest cannot edit provider — **Carries**: AC-GUEST-01, SC-03 --- @@ -228,33 +228,33 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T065 [FR-HOME-03] Files tab: documents list + upload + visibility in `ios/HomesFlow/Features/Documents/` — **Traces**: FR-HOME-03, AC-GUEST-01, AC-HOME-11 -- [x] T065a [FR-HOME-03] Standardize section add actions on the Contacts pattern (plus icon, primary action, accessible label) across Contacts, Files, People — **Traces**: AC-HOME-12 -- [x] T065b [FR-HOME-03] File detail: Preview via system Quick Look (zoom, PDF, media); summary + metadata and actions below — **Traces**: AC-HOME-13 -- [x] T065c [FR-HOME-03] File sources: camera capture, photo library, and file browser feeding one metadata flow — **Traces**: AC-HOME-14 -- [x] T065e [FR-HOME-03] Stream file download to temp for Quick Look preview (no full in-memory buffer) — **Traces**: AC-HOME-13, NFR-PERF-01 -- [x] T065d [P] [FR-HOME-03] Tests for section add parity, file preview, and file sources — **Traces**: AC-HOME-12, AC-HOME-13, AC-HOME-14 -- [x] T066 [P] [FR-NOTIF-01] Settings screen: account, notification toggle disabled (“Coming soon”) in `ios/HomesFlow/Features/Settings/` — **Traces**: FR-NOTIF-01 — *gear icon on dashboard; email, sign out with confirmation, version* -- [x] T066a [P] Accessibility baseline: Dynamic Type layouts, VoiceOver labels on section tabs, Reduce Motion, 44pt targets across dashboard/home/procedures — **Traces**: NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03 — *AccessibilityBaseline helper: scaled hero heights, 44pt step actions, step status VoiceOver value; manual VoiceOver pass remains T069a* -- [x] T067 [P] Sign out clears session — **Traces**: FR-AUTH-01 — *moved to Settings screen (T066) with confirmation dialog* -- [x] T068 Owner revoke member access → lose access on next sync — **Traces**: FR-USER-02 — *MemberRemovalPolicy + removeMember; swipe + detail actions with confirmation; MemberRemovalTests* +- [x] T065 [FR-HOME-03] Files tab: documents list + upload + visibility in `ios/HomesFlow/Features/Documents/` — **Carries**: FR-HOME-03, AC-GUEST-01, AC-HOME-11 +- [x] T065a [FR-HOME-03] Standardize section add actions on the Contacts pattern (plus icon, primary action, accessible label) across Contacts, Files, People — **Carries**: AC-HOME-12 +- [x] T065b [FR-HOME-03] File detail: Preview via system Quick Look (zoom, PDF, media); summary + metadata and actions below — **Carries**: AC-HOME-13 +- [x] T065c [FR-HOME-03] File sources: camera capture, photo library, and file browser feeding one metadata flow — **Carries**: AC-HOME-14 +- [x] T065e [FR-HOME-03] Stream file download to temp for Quick Look preview (no full in-memory buffer) — **Carries**: AC-HOME-13, NFR-PERF-01 +- [x] T065d [P] [FR-HOME-03] Tests for section add parity, file preview, and file sources — **Carries**: AC-HOME-12, AC-HOME-13, AC-HOME-14 +- [x] T066 [P] [FR-NOTIF-01] Settings screen: account, notification toggle disabled (“Coming soon”) in `ios/HomesFlow/Features/Settings/` — **Carries**: FR-NOTIF-01 — *gear icon on dashboard; email, sign out with confirmation, version* +- [x] T066a [P] Accessibility baseline: Dynamic Type layouts, VoiceOver labels on section tabs, Reduce Motion, 44pt targets across dashboard/home/procedures — **Carries**: NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03 — *AccessibilityBaseline helper: scaled hero heights, 44pt step actions, step status VoiceOver value; manual VoiceOver pass remains T069a* +- [x] T067 [P] Sign out clears session — **Carries**: FR-AUTH-01 — *moved to Settings screen (T066) with confirmation dialog* +- [x] T068 Owner revoke member access → lose access on next sync — **Carries**: FR-USER-02 — *MemberRemovalPolicy + removeMember; swipe + detail actions with confirmation; MemberRemovalTests* ### Tests -- [ ] T069 [P] XCUITest end-to-end: sign-in → create home → invite → update step → guest read-only — **Traces**: SC-01, SC-02, SC-03 -- [ ] T069a [P] Manual accessibility pass: largest Dynamic Type + VoiceOver on dashboard, home sections, procedure checklist — **Traces**: AC-A11Y-01, AC-A11Y-02, AC-A11Y-03 +- [ ] T069 [P] XCUITest end-to-end: sign-in → create home → invite → update step → guest read-only — **Carries**: SC-01, SC-02, SC-03 +- [ ] T069a [P] Manual accessibility pass: largest Dynamic Type + VoiceOver on dashboard, home sections, procedure checklist — **Carries**: AC-A11Y-01, AC-A11Y-02, AC-A11Y-03 --- ## Phase 11: Hardening -- [ ] T070 Run `/speckit.analyze` and fix all traceability violations — **Traces**: constitution traceability article -- [ ] T071 [P] RLS integration tests against contracts/rls-permissions.md — **Traces**: FR-USER-01, FR-GUEST-01 -- [x] T072 [P] Sync conflict matrix — target SC-04 (95% scripted scenarios) — **Traces**: SC-04, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03, NFR-SYNC-01 — *SyncConflictMatrixTests: timestamp-wins, delete-wins, idempotency, permission revert; AC-SYNC-02 field-merge scenarios pending T035* -- [ ] T072a [P] Performance smoke: screen load & sync latency baselines — **Traces**: NFR-PERF-01, NFR-SYNC-01 -- [ ] T072b [P] Document NFR-SCALE-01 architecture assumptions in plan.md (no load test in MVP) — **Traces**: NFR-SCALE-01 -- [ ] T072c [P] Enable Xcode crash report collection / document NFR-REL-01 monitoring path — **Traces**: NFR-REL-01 -- [x] T073 Update quickstart.md with any dev-setup deltas discovered during implement — **Traces**: plan Phase 0 (infrastructure) +- [ ] T070 Run `/speckit.analyze` and fix all traceability violations — **Carries**: constitution traceability article +- [ ] T071 [P] RLS integration tests against contracts/rls-permissions.md — **Carries**: FR-USER-01, FR-GUEST-01 +- [x] T072 [P] Sync conflict matrix — target SC-04 (95% scripted scenarios) — **Carries**: SC-04, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03, NFR-SYNC-01 — *SyncConflictMatrixTests: timestamp-wins, delete-wins, idempotency, permission revert; AC-SYNC-02 field-merge scenarios pending T035* +- [ ] T072a [P] Performance smoke: screen load & sync latency baselines — **Carries**: NFR-PERF-01, NFR-SYNC-01 +- [ ] T072b [P] Document NFR-SCALE-01 architecture assumptions in plan.md (no load test in MVP) — **Carries**: NFR-SCALE-01 +- [ ] T072c [P] Enable Xcode crash report collection / document NFR-REL-01 monitoring path — **Carries**: NFR-REL-01 +- [x] T073 Update quickstart.md with any dev-setup deltas discovered during implement — **Carries**: plan Phase 0 (infrastructure) --- @@ -262,12 +262,12 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en **Goal**: Data-type-aware conflict handling replacing blanket timestamp-wins — **NFR-OFFL-01** (see spec User Story 6 evolution note) -- [x] T074 [NFR-OFFL-01] Protect terminal step statuses: sync never silently regresses Complete/N/A; surface conflicting update — **Traces**: AC-SYNC-05 — *StepStatusConflictPolicy + ProcedureRepository merge; activity log + in-app notification* -- [x] T074a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_05_terminal_status_never_silently_regressed` — **Traces**: AC-SYNC-05 — *StepStatusConflictPolicyTests* -- [x] T075 [NFR-OFFL-01] Auto-resolve status conflicts (timestamp, subject to AC-SYNC-05) + notify losing user with activity-log reference and re-apply guidance — no resolution UI (2026-07-03 decision) — **Traces**: AC-SYNC-06 — *StepStatusConflictPolicy auto-resolve messages; resolveStepConflict + procedure merge* -- [x] T075a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_06_losing_user_notified_with_reapply_guidance` — **Traces**: AC-SYNC-06 — *StepStatusConflictPolicyTests* -- [x] T076 [NFR-OFFL-01] Connectivity-gate structural actions (step/procedure/provider CRUD, membership) when offline — **Traces**: AC-SYNC-07 -- [x] T076a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_07_structural_actions_blocked_offline` — **Traces**: AC-SYNC-07 +- [x] T074 [NFR-OFFL-01] Protect terminal step statuses: sync never silently regresses Complete/N/A; surface conflicting update — **Carries**: AC-SYNC-05 — *StepStatusConflictPolicy + ProcedureRepository merge; activity log + in-app notification* +- [x] T074a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_05_terminal_status_never_silently_regressed` — **Carries**: AC-SYNC-05 — *StepStatusConflictPolicyTests* +- [x] T075 [NFR-OFFL-01] Auto-resolve status conflicts (timestamp, subject to AC-SYNC-05) + notify losing user with activity-log reference and re-apply guidance — no resolution UI (2026-07-03 decision) — **Carries**: AC-SYNC-06 — *StepStatusConflictPolicy auto-resolve messages; resolveStepConflict + procedure merge* +- [x] T075a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_06_losing_user_notified_with_reapply_guidance` — **Carries**: AC-SYNC-06 — *StepStatusConflictPolicyTests* +- [x] T076 [NFR-OFFL-01] Connectivity-gate structural actions (step/procedure/provider CRUD, membership) when offline — **Carries**: AC-SYNC-07 +- [x] T076a [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_07_structural_actions_blocked_offline` — **Carries**: AC-SYNC-07 --- @@ -281,19 +281,19 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en ### Implementation -- [x] T077 [FR-LOG-02] LogBookEntry model + migration + RLS (Owner/Manager read-write, Guest none) — **Traces**: FR-LOG-02, AC-LOG-06 -- [x] T078 [FR-LOG-02] Write household-scope log entry — **Traces**: AC-LOG-01 -- [x] T079 [FR-LOG-02] Write procedure-scope log entry from procedure detail — **Traces**: AC-LOG-02 -- [x] T080 [FR-LOG-02] Offline append-only log entry sync — **Traces**: AC-LOG-03 -- [x] T081 [FR-LOG-02] Grace-window editing (10 minutes from server receipt; immutable after) — **Traces**: AC-LOG-04 -- [x] T082 [FR-LOG-02] Unified chronological log view with scope filter — **Traces**: AC-LOG-05 -- [x] T083 [FR-LOG-02] Deny Guest access to Log Book including deep links — **Traces**: AC-LOG-06 +- [x] T077 [FR-LOG-02] LogBookEntry model + migration + RLS (Owner/Manager read-write, Guest none) — **Carries**: FR-LOG-02, AC-LOG-06 +- [x] T078 [FR-LOG-02] Write household-scope log entry — **Carries**: AC-LOG-01 +- [x] T079 [FR-LOG-02] Write procedure-scope log entry from procedure detail — **Carries**: AC-LOG-02 +- [x] T080 [FR-LOG-02] Offline append-only log entry sync — **Carries**: AC-LOG-03 +- [x] T081 [FR-LOG-02] Grace-window editing (10 minutes from server receipt; immutable after) — **Carries**: AC-LOG-04 +- [x] T082 [FR-LOG-02] Unified chronological log view with scope filter — **Carries**: AC-LOG-05 +- [x] T083 [FR-LOG-02] Deny Guest access to Log Book including deep links — **Carries**: AC-LOG-06 ### Tests -- [x] T084 [P] [FR-LOG-02] Unit tests `test_AC_LOG_01_household_entry_appears_in_log`, `test_AC_LOG_02_procedure_entry_attached_and_in_log` — **Traces**: AC-LOG-01, AC-LOG-02 -- [x] T085 [P] [FR-LOG-02] Unit tests `test_AC_LOG_03_offline_entry_syncs_append_only`, `test_AC_LOG_04_edit_only_within_grace_window` — **Traces**: AC-LOG-03, AC-LOG-04 -- [x] T086 [P] [FR-LOG-02] Unit tests `test_AC_LOG_05_unified_log_chronological_and_filterable`, `test_AC_LOG_06_guest_denied_log_access` — **Traces**: AC-LOG-05, AC-LOG-06 +- [x] T084 [P] [FR-LOG-02] Unit tests `test_AC_LOG_01_household_entry_appears_in_log`, `test_AC_LOG_02_procedure_entry_attached_and_in_log` — **Carries**: AC-LOG-01, AC-LOG-02 +- [x] T085 [P] [FR-LOG-02] Unit tests `test_AC_LOG_03_offline_entry_syncs_append_only`, `test_AC_LOG_04_edit_only_within_grace_window` — **Carries**: AC-LOG-03, AC-LOG-04 +- [x] T086 [P] [FR-LOG-02] Unit tests `test_AC_LOG_05_unified_log_chronological_and_filterable`, `test_AC_LOG_06_guest_denied_log_access` — **Carries**: AC-LOG-05, AC-LOG-06 --- @@ -301,10 +301,10 @@ Partial deliverables documented in [dev-notes.md](./dev-notes.md). **Do not** en **Goal**: PR-based delivery to `main` with the same gates that already run on push — no silent admin bypass; Sonar suppressions authoritative in git. -- [ ] T087 [P] Enforce PR-only merge to `main`: branch protection requires pull request, `craft-gate` + `SonarCloud Code Analysis`, and disallow admin bypass — **Traces**: plan Phase 0 (infrastructure), NFR-SEC-01 — *script ready: `bash scripts/finish-phase-e.sh` (admin); run after this PR merges and CI Sonar is green* -- [x] T088 [P] Add `.github/pull_request_template.md`: task ID(s), `Traces:` AC/FR IDs, test evidence, Gate 2 status — **Traces**: plan Phase 0 (infrastructure) — *golden thread visible at review time* -- [x] T089 [P] Run SonarCloud from CI (`.github/workflows/ci.yml` or dedicated job) so `sonar-project.properties` multicriteria suppressions apply; retire automatic-analysis-only suppressions where redundant — **Traces**: plan Phase 0 (infrastructure) — *job `sonar` + `SONAR_TOKEN`; disable Automatic Analysis in SonarCloud UI after first green CI scan* -- [x] T090 [P] Document delivery workflow in `craft-conventions.md` + README: feature branch → PR → green gates → merge; link `finish-phase-e.sh` for protection setup — **Traces**: plan Phase 0 (infrastructure) +- [ ] T087 [P] Enforce PR-only merge to `main`: branch protection requires pull request, `craft-gate` + `SonarCloud Code Analysis`, and disallow admin bypass — **Carries**: plan Phase 0 (infrastructure), NFR-SEC-01 — *script ready: `bash scripts/finish-phase-e.sh` (admin); run after this PR merges and CI Sonar is green* +- [x] T088 [P] Add `.github/pull_request_template.md`: task ID(s), `Traces:` AC/FR IDs, test evidence, Gate 2 status — **Carries**: plan Phase 0 (infrastructure) — *golden thread visible at review time* +- [x] T089 [P] Run SonarCloud from CI (`.github/workflows/ci.yml` or dedicated job) so `sonar-project.properties` multicriteria suppressions apply; retire automatic-analysis-only suppressions where redundant — **Carries**: plan Phase 0 (infrastructure) — *job `sonar` + `SONAR_TOKEN`; disable Automatic Analysis in SonarCloud UI after first green CI scan* +- [x] T090 [P] Document delivery workflow in `craft-conventions.md` + README: feature branch → PR → green gates → merge; link `finish-phase-e.sh` for protection setup — **Carries**: plan Phase 0 (infrastructure) --- diff --git a/specs/002-home-name-normalize/spec.md b/specs/002-home-name-normalize/spec.md new file mode 100644 index 0000000..3230e9f --- /dev/null +++ b/specs/002-home-name-normalize/spec.md @@ -0,0 +1,71 @@ +# Feature Specification: Home Display-Name Normalization + +**Feature Branch**: `002-home-name-normalize` + +**Created**: 2026-08-07 + +**Status**: Draft + +**Input**: `HomesFlow.prd.md` — FR-HOME-04 (home display-name normalization). + +> IDs inherited from PRD. Do not mint new IDs in this file. See PRD § ID Registry. + +## Intended Use + +Home display names are entered by people and often arrive with stray whitespace — leading/trailing spaces from copy-paste, double spaces between words, or embedded tabs and newlines. HomesFlow normalizes these names for display so the dashboard and home detail render clean, consistent titles. This is a pure, deterministic string helper: no UI wiring, no networking, and no sync behavior. + +## Risk & failure modes + +| Failure | User impact | Mitigation / trace | +|---------|-------------|-------------------| +| Ragged whitespace in a pasted name | Home title renders with double spaces or leading indentation | Normalize on display via pure helper; AC-HOME-15 | +| Tabs/newlines embedded in a name | Title wraps or shows control whitespace | Collapse all internal whitespace runs to a single space; AC-HOME-15 | +| Over-eager trimming of interior content | Words run together / meaning lost | Collapse runs to one space (never remove interior separators); AC-HOME-15 | + +## User Scenarios & Testing + +### User Story — Clean home display names (Priority: P3) + +**ID**: FR-HOME-04 + +As a homeowner, I want home names to display without stray whitespace so my dashboard looks tidy regardless of how the name was typed or pasted. + +**Why this priority**: Cosmetic polish on top of existing home CRUD (FR-HOME-01); depends on nothing and blocks nothing. + +**Independent Test**: Feed representative raw names (leading/trailing whitespace, internal runs, tabs/newlines, already-clean, empty) to the normalizer and assert the collapsed, trimmed output. + +**Acceptance Scenarios**: + +1. **AC-HOME-15** — Given a raw home display name with leading/trailing whitespace and internal whitespace runs (spaces, tabs, or newlines), when it is normalized for display, then the result has no leading or trailing whitespace and every internal whitespace run is collapsed to a single space. + +--- + +### Edge Cases + +- Already-clean input returns unchanged. +- Empty string (or whitespace-only) normalizes to an empty string. +- Mixed whitespace kinds (space + tab + newline) in a single run collapse to one space. + +## Requirements + +### Functional Requirements + +- **FR-HOME-04**: The system MUST normalize home display names for display by trimming leading/trailing whitespace and collapsing internal runs of whitespace to a single space. Pure helper; no UI, networking, or sync behavior (**AC-HOME-15**). + +### Key Entities + +- **Home** — Second-home property whose display `name` is normalized for presentation (FR-HOME-01 owns the entity; this feature only reads/normalizes the name string). + +## Success Criteria + +- **SC-01**: For any input string, `HomeDisplayName.normalized(_:)` returns a string with no leading/trailing whitespace and no internal whitespace run longer than a single space. + +## Assumptions + +- Whitespace is defined by Unicode/Swift `CharacterSet.whitespacesAndNewlines` (spaces, tabs, newlines, and related whitespace). +- Normalization is applied at display time only; the stored/entered value is not mutated. + +## Out of Scope + +- Any UI, view-model, networking, or sync changes. +- Case folding, Unicode NFC/NFD normalization, or profanity/length validation. diff --git a/specs/002-home-name-normalize/tasks.md b/specs/002-home-name-normalize/tasks.md new file mode 100644 index 0000000..2f6e332 --- /dev/null +++ b/specs/002-home-name-normalize/tasks.md @@ -0,0 +1,20 @@ +# Tasks: Home Display-Name Normalization + +**Input**: [spec.md](./spec.md) + +**Feature**: `002-home-name-normalize` | **Created**: 2026-08-07 + +## Format + +- **Carries**: AC/FR/NFR ID(s) implemented +- **[P]**: Parallelizable + +--- + +## Phase 1: Implementation + +- [x] T001 Implement `HomeDisplayName.normalized(_:)` pure helper in `ios/HomesFlow/Core/Home/HomeDisplayName.swift` — trims leading/trailing whitespace and collapses internal whitespace runs to a single space; no UI, networking, or sync — **Carries**: FR-HOME-04, AC-HOME-15 + +## Phase 2: Tests + +- [x] T002 [P] Unit test `test_AC_HOME_15_normalizes_whitespace` in `ios/HomesFlowTests/HomeDisplayNameTests.swift` — covers leading/trailing whitespace, internal runs, tabs/newlines, already-clean input, and empty string — **Carries**: AC-HOME-15 diff --git a/specs/backlog/tasks.md b/specs/backlog/tasks.md index bc9964d..21b1a5e 100644 --- a/specs/backlog/tasks.md +++ b/specs/backlog/tasks.md @@ -5,4 +5,4 @@ is this TODO. Minting into backlog is deliberate — the TODO proves intent and names who is carrying what. Moving an item out of backlog means creating its feature spec and tasks, which claims the ID and retires the line here. -- [ ] T900 Deliver `HomeDisplayName.normalized(_:)` natively in HomesFlow (whitespace trim + collapse; the Clewseau trial slice, delivered here as well) — **Traces**: US-CLEW-01, FR-CLEW-01, AC-CLEW-01 +- [ ] T901 Deliver read-only procedure sharing link — **Carries**: US-SHARE-01 diff --git a/trace-manifest.json b/trace-manifest.json new file mode 100644 index 0000000..c7bb067 --- /dev/null +++ b/trace-manifest.json @@ -0,0 +1,3259 @@ +{ + "schemaVersion": 3, + "format": "trace-manifest", + "emitter": "specassay-check", + "targetName": "HomesFlow", + "repoPath": "/workspace/homesflow", + "generatedAt": "2026-08-07T19:37:04.136Z", + "gate": { + "ok": true, + "failures": [] + }, + "totals": { + "registryIdCount": 82, + "acCount": 51, + "coveredCount": 46 + }, + "statusCounts": { + "verified": 67, + "tracked-debt": 10, + "GAP": 0, + "backlog": 5 + }, + "rows": [ + { + "id": "AC-A11Y-01", + "type": "AC", + "statement": "AC-A11Y-01** \u2014 Given the user has increased text size via iOS Settings (Dynamic Type / Accessibility sizes), when they view dashboard, home detail, or procedure screens, then primary text and controls scale, layouts reflow without clipping essential actions, and primary reading content does not require horizontal scrolling.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 294 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/UI/AccessibilityBaseline.swift", + "line": 3, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + }, + { + "path": "ios/HomesFlow/Features/Home/HomeHeroCard.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, AC-HOME-07, AC-A11Y-01, NFR-A11Y-01" + }, + { + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 5, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "proofs": [ + { + "name": "test_AC_A11Y_01_hero_height_grows_with_text_size", + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 11 + }, + { + "name": "test_AC_A11Y_01_scale_factor_is_monotonic_across_sizes", + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 22 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 245, + "excerpt": "- [ ] T069a [P] Manual accessibility pass: largest Dynamic Type + VoiceOver on dashboard, home sections, procedure checklist \u2014 **Carries**: AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "attestedBy": null + }, + { + "id": "AC-A11Y-02", + "type": "AC", + "statement": "AC-A11Y-02** \u2014 Given VoiceOver is enabled, when the user navigates home section tabs (horizontal on iPhone, vertical on iPad), then each tab exposes a combined accessibility label (section name + role), selected state is announced, and interactive controls have meaningful hints where non-obvious.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 295 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/UI/AccessibilityBaseline.swift", + "line": 3, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 5, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "proofs": [ + { + "name": "test_AC_A11Y_02_every_section_tab_has_meaningful_voiceover_text", + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 34 + }, + { + "name": "test_AC_A11Y_02_step_status_values_cover_all_statuses", + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 42 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 245, + "excerpt": "- [ ] T069a [P] Manual accessibility pass: largest Dynamic Type + VoiceOver on dashboard, home sections, procedure checklist \u2014 **Carries**: AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "attestedBy": null + }, + { + "id": "AC-A11Y-03", + "type": "AC", + "statement": "AC-A11Y-03** \u2014 Given Reduce Motion is enabled in iOS Settings, when the user navigates between sections or primary screens, then non-essential motion animations are omitted or reduced per system preference.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 296 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/UI/AccessibilityBaseline.swift", + "line": 3, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 5, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "proofs": [ + { + "name": "test_AC_A11Y_03_reduce_motion_disables_animation", + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 53 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 245, + "excerpt": "- [ ] T069a [P] Manual accessibility pass: largest Dynamic Type + VoiceOver on dashboard, home sections, procedure checklist \u2014 **Carries**: AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "attestedBy": null + }, + { + "id": "AC-GUEST-01", + "type": "AC", + "statement": "AC-GUEST-01** \u2014 Given a Guest signs in to a home, when they view info, then only fields marked for Guest visibility are shown and edit controls are disabled.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 262 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-GUEST-01, FR-GUEST-01" + }, + { + "path": "ios/HomesFlow/Core/Documents/DocumentDTO.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-03, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_GUEST_01_guest_fields_only", + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 10 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 221, + "excerpt": "- [ ] T064 [US-GUEST-01] XCUITest: guest cannot edit provider \u2014 **Carries**: AC-GUEST-01, SC-03" + } + ], + "attestedBy": null + }, + { + "id": "AC-GUEST-02", + "type": "AC", + "statement": "AC-GUEST-02** \u2014 Given a Guest tries to access a restricted procedure or document via deep link, when the app evaluates permissions, then access is denied and a clear message is shown explaining limitations.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 263 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Models/EntityAccessState.swift", + "line": 3, + "excerpt": "// @covers AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Guest/GuestAccessDeniedView.swift", + "line": 3, + "excerpt": "// @covers AC-GUEST-02, FR-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProceduresView.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02, NFR-PERF-01, AC-GUEST-02, AC-GUEST-04" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_GUEST_02_restricted_deep_link_denied", + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 40 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-GUEST-03", + "type": "AC", + "statement": "AC-GUEST-03** \u2014 Given the Guest receives updated guest-visibility content while offline and another actor changes visibility before sync, when devices sync, then the most recent timestamp determines visibility and Guests see the latest allowed content.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 264 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_GUEST_03_offline_visibility_sync", + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 50 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-GUEST-04", + "type": "AC", + "statement": "AC-GUEST-04** \u2014 Given a Guest opens a guest procedure, when they view it, then they can see step descriptions and read-only status but cannot change step statuses.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 268 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProceduresView.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02, NFR-PERF-01, AC-GUEST-02, AC-GUEST-04" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_GUEST_04_guest_procedure_read_only", + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 71 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-GUEST-05", + "type": "AC", + "statement": "AC-GUEST-05** \u2014 Given a Guest attempts to mark a guest step complete, when they submit, then the app rejects the change and logs the attempted unauthorized action for audit.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 269 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_GUEST_05_guest_cannot_update_step", + "path": "ios/HomesFlowTests/PermissionServiceTests.swift", + "line": 7 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-01", + "type": "AC", + "statement": "AC-HOME-01** \u2014 Given an authenticated Owner on a connected device, when they submit valid home details and photos, then the home is created and visible in their dashboard with correct data.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 214 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoService.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeRepository.swift", + "line": 6, + "excerpt": "// @covers FR-HOME-01, AC-HOME-01, AC-HOME-02, AC-HOME-08" + }, + { + "path": "ios/HomesFlow/Features/HomeSetup/HomeSetupView.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-02, AC-HOME-03, FR-HOME-01" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_01_valid_home_passes_validation", + "path": "ios/HomesFlowTests/HomeValidatorTests.swift", + "line": 5 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 96, + "excerpt": "- [ ] T022 [P] [US-ADMIN-01] Unit test `test_AC_HOME_01_valid_home_created` \u2014 **Carries**: AC-HOME-01 \u2014 *validator only; integration test pending*" + } + ], + "attestedBy": null + }, + { + "id": "AC-HOME-02", + "type": "AC", + "statement": "AC-HOME-02** \u2014 Given an Owner submits incomplete/invalid home details, when they attempt to save, then validation errors are shown and the home is not created.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 215 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Home/HomeRepository.swift", + "line": 6, + "excerpt": "// @covers FR-HOME-01, AC-HOME-01, AC-HOME-02, AC-HOME-08" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeDTO.swift", + "line": 70, + "excerpt": "/// @covers AC-HOME-02" + }, + { + "path": "ios/HomesFlow/Features/HomeSetup/HomeSetupView.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-02, AC-HOME-03, FR-HOME-01" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_02_empty_name_rejected", + "path": "ios/HomesFlowTests/HomeValidatorTests.swift", + "line": 10 + }, + { + "name": "test_AC_HOME_02_empty_address_rejected", + "path": "ios/HomesFlowTests/HomeValidatorTests.swift", + "line": 17 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-03", + "type": "AC", + "statement": "AC-HOME-03** \u2014 Given an Owner edits home details while offline and another device edits the same home before sync, when both devices reconnect, then the version with the most recent timestamp is applied and a conflict log entry is created for audit.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 216 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/OutboxSyncPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-01, AC-HOME-03" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeRepository.swift", + "line": 96, + "excerpt": "/// @covers AC-HOME-03" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeConflictResolver.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-03" + }, + { + "path": "ios/HomesFlow/Features/HomeSetup/HomeSetupView.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-02, AC-HOME-03, FR-HOME-01" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_03_older_pending_edit_does_not_push_over_newer_server", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 146 + }, + { + "name": "test_AC_HOME_03_server_newer_overwrites_pending_local", + "path": "ios/HomesFlowTests/HomeConflictResolverTests.swift", + "line": 5 + }, + { + "name": "test_AC_HOME_03_local_newer_keeps_pending_local", + "path": "ios/HomesFlowTests/HomeConflictResolverTests.swift", + "line": 17 + }, + { + "name": "test_AC_HOME_03_synced_home_applies_server", + "path": "ios/HomesFlowTests/HomeConflictResolverTests.swift", + "line": 29 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-04", + "type": "AC", + "statement": "AC-HOME-04** \u2014 Given a Manager user opens the service provider directory, when they search and select a provider, then they can edit contact details and changes are saved and propagated to permitted users.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 257 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Providers/ProviderConflictResolver.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderFieldMerge.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-04, AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderDTO.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04" + }, + { + "path": "ios/HomesFlow/Features/Providers/ContactsView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, FR-NAV-01, AC-HOME-04, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, FR-HOME-02" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_04_provider_edit_propagates", + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 10 + }, + { + "name": "test_AC_HOME_04_local_newer_edit_is_kept", + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 29 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-05", + "type": "AC", + "statement": "AC-HOME-05** \u2014 Given a Manager user edits a provider entry that an Owner later deletes on another device before sync, when both devices sync, then the most recent timestamp between edit and delete determines final state; if delete is most recent the provider is removed and the editor receives a notification that their edit was removed due to delete.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 258 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Providers/ProviderConflictResolver.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, FR-HOME-02" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_05_delete_wins_over_edit", + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 42 + }, + { + "name": "test_AC_HOME_05_synced_row_removed_silently", + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 52 + }, + { + "name": "test_AC_HOME_05_local_only_insert_survives_pull", + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 62 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-06", + "type": "AC", + "statement": "AC-HOME-06** \u2014 Given an Owner or Manager user uploads a home photo, when the photo is saved to Storage, then the client uploads a display-optimized JPEG bounded to a maximum pixel dimension (not full camera resolution).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 217 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentStorageService.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-03, AC-HOME-06" + }, + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoCache.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, FR-HOME-01" + }, + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoService.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01" + }, + { + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, AC-HOME-08" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_06_upload_resizes_before_storage", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 10 + }, + { + "name": "test_AC_HOME_06_small_image_not_upscaled", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 25 + }, + { + "name": "test_AC_HOME_06_invalid_image_data_rejected", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 33 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-07", + "type": "AC", + "statement": "AC-HOME-07** \u2014 Given a user has previously loaded a home photo on this device, when they view the dashboard or home detail again, then the hero photo renders from local cache without re-downloading from Storage.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 218 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoCache.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, FR-HOME-01" + }, + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoService.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Features/Home/HomeHeroCard.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, AC-HOME-07, AC-A11Y-01, NFR-A11Y-01" + }, + { + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, AC-HOME-08" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_07_hero_renders_from_local_cache", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 46 + }, + { + "name": "test_AC_HOME_07_removed_photo_no_longer_cached", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 66 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-08", + "type": "AC", + "statement": "AC-HOME-08** \u2014 Given a home record has not yet synced to the server, when an Owner or Manager user attempts to upload a photo, then the upload is blocked and the user sees actionable guidance to sync first (e.g., pull to refresh while online).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 219 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Home/HomeRepository.swift", + "line": 6, + "excerpt": "// @covers FR-HOME-01, AC-HOME-01, AC-HOME-02, AC-HOME-08" + }, + { + "path": "ios/HomesFlow/Core/Home/HomePhotoSyncGate.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-08" + }, + { + "path": "ios/HomesFlow/Features/Dashboard/DashboardView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, US-ADMIN-01, AC-HOME-08, AC-SYNC-04, FR-NAV-01" + }, + { + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, AC-HOME-08" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_08_photo_blocked_until_home_synced", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 83 + }, + { + "name": "test_AC_HOME_08_offline_edit_without_photo_defers_sync", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 103 + }, + { + "name": "test_AC_HOME_08_connected_upload_runs_sync_then_passes_when_synced", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 110 + }, + { + "name": "test_AC_HOME_08_blocked_errors_carry_actionable_guidance", + "path": "ios/HomesFlowTests/HomePhotoTests.swift", + "line": 118 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-09", + "type": "AC", + "statement": "AC-HOME-09** \u2014 Given a user views home detail on iPad (regular horizontal size class), when any section is selected, then the trailing area shows section content only \u2014 no full-bleed home hero and no horizontal section tab bar at the home level (nested section list | detail splits are allowed inside the trailing area).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 220 + }, + "status": "tracked-debt", + "implementations": [ + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 102, + "excerpt": "- [ ] T024d [P] [US-ADMIN-01] Snapshot or UI test: iPad home detail trailing column has no hero/segmented tabs \u2014 **Carries**: AC-HOME-09 \u2014 *deferred: needs snapshot/XCUITest infra; manual iPad pas" + } + ], + "attestedBy": null + }, + { + "id": "AC-HOME-10", + "type": "AC", + "statement": "AC-HOME-10** \u2014 Given a user opens a home on iPad, when home detail is shown, then the leading column displays a compact home hero (photo, name, address) and vertically stacked tappable section entries with icon and label for Procedures, Contacts, Files, and People; the trailing area shows the selected section using a three-panel layout (sidebar already visible + section list + section detail) for **all four** sections.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 221 + }, + "status": "tracked-debt", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/PeopleSelection.swift", + "line": 3, + "excerpt": "// @covers FR-NAV-01, AC-USER-02, AC-HOME-10" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/PeopleSelectionTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-10, AC-USER-02" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 103, + "excerpt": "- [ ] T024e [P] [US-ADMIN-01] Snapshot or UI test: iPad leading column shows compact home hero + vertical icon section tabs \u2014 **Carries**: AC-HOME-10 \u2014 *deferred: needs snapshot/XCUITest infra; ma" + } + ], + "attestedBy": null + }, + { + "id": "AC-HOME-11", + "type": "AC", + "statement": "AC-HOME-11** \u2014 Given a user views home detail on any device, when section navigation is shown, then section labels read Procedures, Contacts, Files, and People (Files implements the document library).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 222 + }, + "status": "tracked-debt", + "implementations": [ + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 104, + "excerpt": "- [ ] T024f [P] [US-ADMIN-01] UI test: Contacts, Files, and People sections use the three-panel layout on iPad \u2014 **Carries**: AC-HOME-11 \u2014 *deferred: needs snapshot/XCUITest infra; manual iPad pas" + } + ], + "attestedBy": null + }, + { + "id": "AC-HOME-12", + "type": "AC", + "statement": "AC-HOME-12** \u2014 Given a user with permission to add content views a home section list (Contacts, Files, People), when the section is shown, then a single add action appears as the toolbar primary action using a parallel construction across sections \u2014 a plus icon with an accessible label naming the action (e.g., Add contact, Add file, Invite member) that opens the section's create sheet; users without permission see no add action.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 223 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/UI/SectionAddAction.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-12" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/Providers/ContactsView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, FR-NAV-01, AC-HOME-04, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-12, AC-HOME-13, AC-HOME-14" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_12_section_add_actions_use_parallel_construction", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 11 + }, + { + "name": "test_AC_HOME_12_contacts_and_files_add_for_owner_and_manager", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 20 + }, + { + "name": "test_AC_HOME_12_people_add_owner_only", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 33 + }, + { + "name": "test_AC_HOME_12_guest_has_no_section_add_actions", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 39 + }, + { + "name": "test_AC_HOME_12_matches_repository_manage_flags", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 48 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-13", + "type": "AC", + "statement": "AC-HOME-13** \u2014 Given a user opens a file's detail, when they tap **Preview**, then the system Quick Look preview opens with zoom, scroll, and playback for supported types (images, PDFs, video, audio); detail shows a file summary, metadata, Preview action, share/download, and management actions below.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 224 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentPreviewSupport.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-13, AC-HOME-14" + }, + { + "path": "ios/HomesFlow/Features/Documents/DocumentQuickLookPreview.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-HOME-13, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-12, AC-HOME-13, AC-HOME-14" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_13_preview_icon_maps_by_extension", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 61 + }, + { + "name": "test_AC_HOME_13_local_file_name_uses_storage_path", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 70 + }, + { + "name": "test_AC_HOME_13_local_file_name_falls_back_to_id_and_extension", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 85 + }, + { + "name": "test_AC_HOME_13_streams_download_to_preview_directory", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 101 + }, + { + "name": "test_AC_HOME_13_non_success_download_throws", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 136 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-14", + "type": "AC", + "statement": "AC-HOME-14** \u2014 Given an Owner or Manager adds a file, when they choose the file source, then camera capture, photo library, and file browser options are all offered, and content from any source uploads through the same metadata flow (title, category, visibility).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 225 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentPreviewSupport.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-13, AC-HOME-14" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-12, AC-HOME-13, AC-HOME-14" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_14_offers_library_and_file_browser_sources", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 173 + }, + { + "name": "test_AC_HOME_14_includes_camera_when_available", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 178 + }, + { + "name": "test_AC_HOME_14_apply_pick_fills_title_from_file_name", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 183 + }, + { + "name": "test_AC_HOME_14_apply_pick_preserves_existing_title", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 196 + }, + { + "name": "test_AC_HOME_14_upload_requires_valid_draft_and_file_data", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 207 + }, + { + "name": "test_AC_HOME_14_camera_file_name_is_dated_jpeg", + "path": "ios/HomesFlowTests/FilesFeatureTests.swift", + "line": 217 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-HOME-15", + "type": "AC", + "statement": "AC-HOME-15** \u2014 Given a raw home display name with leading/trailing whitespace and internal whitespace runs (spaces, tabs, or newlines), when it is normalized for display, then the result has no leading or trailing whitespace and every internal whitespace run is collapsed to a single space.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 229 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Home/HomeDisplayName.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-04, AC-HOME-15" + } + ], + "proofs": [ + { + "name": "test_AC_HOME_15_normalizes_whitespace", + "path": "ios/HomesFlowTests/HomeDisplayNameTests.swift", + "line": 5 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-01", + "type": "AC", + "statement": "AC-LOG-01** \u2014 Given an Owner or Manager opens the Log Book for a home, when they write and save a household-scope entry, then the entry appears in the unified log with author and timestamp.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 285 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookEntryDTO.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlow/Features/LogBook/LogEntryEditorSheet.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-04" + }, + { + "path": "ios/HomesFlow/Features/LogBook/CommunicationsLogView.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-05, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_01_household_entry_appears_in_log", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 9 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-02", + "type": "AC", + "statement": "AC-LOG-02** \u2014 Given an Owner or Manager views a procedure, when they write a procedure-scope log entry, then the entry is attached to that procedure and also appears in the unified log.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 286 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookEntryDTO.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlow/Features/LogBook/LogEntryEditorSheet.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-04" + }, + { + "path": "ios/HomesFlow/Features/LogBook/CommunicationsLogView.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-05, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_02_procedure_entry_attached_and_in_log", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 28 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-03", + "type": "AC", + "statement": "AC-LOG-03** \u2014 Given a permitted user writes a log entry while offline, when the device reconnects, then the entry syncs append-only (no conflict resolution needed) and appears for other permitted users.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 287 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookEntryDTO.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_03_offline_entry_syncs_append_only", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 51 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-04", + "type": "AC", + "statement": "AC-LOG-04** \u2014 Given a log entry has synced to the server, when the author attempts to edit it, then editing is allowed only within a **10-minute** grace window that starts at server receipt; after the window the entry is immutable.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 288 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookGraceWindowPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-LOG-04, FR-LOG-02" + }, + { + "path": "ios/HomesFlow/Features/LogBook/LogEntryEditorSheet.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-04" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_04_edit_only_within_grace_window", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 58 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-05", + "type": "AC", + "statement": "AC-LOG-05** \u2014 Given a permitted user opens the unified log view, when entries exist at both household and procedure scope, then entries are shown chronologically and can be filtered by scope.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 289 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookScopeFilter.swift", + "line": 3, + "excerpt": "// @covers AC-LOG-05, FR-LOG-02" + }, + { + "path": "ios/HomesFlow/Features/LogBook/CommunicationsLogView.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-05, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_05_unified_log_chronological_and_filterable", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 77 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-LOG-06", + "type": "AC", + "statement": "AC-LOG-06** \u2014 Given a Guest is signed in, when they attempt to view the Log Book directly or via deep link, then access is denied with a clear message.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 290 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlow/Features/LogBook/CommunicationsLogView.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-05, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [ + { + "name": "test_AC_LOG_06_guest_denied_log_access", + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 107 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-01", + "type": "AC", + "statement": "AC-PROC-01** \u2014 Given a Manager user views a procedure, when they mark a step Complete, then the step status updates for all users with appropriate visibility, the procedure's aggregate status and progress in the procedures list update immediately, and an activity log entry is created.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 246 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/ActivityLog/ActivityLogService.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-01, AC-PROC-01" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_01_completed_step_counts_toward_progress", + "path": "ios/HomesFlowTests/ProcedureTests.swift", + "line": 5 + }, + { + "name": "test_AC_PROC_01_final_step_completion_marks_procedure_complete", + "path": "ios/HomesFlowTests/ProcedureTests.swift", + "line": 16 + }, + { + "name": "test_AC_PROC_01_complete_and_na_steps_mark_procedure_complete", + "path": "ios/HomesFlowTests/ProcedureTests.swift", + "line": 25 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-02", + "type": "AC", + "statement": "AC-PROC-02** \u2014 Given a Manager user attempts to update a step beyond their permission (e.g., another home's owner-only procedure or step), when they submit the change, then the app blocks the update and shows a permission error.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 247 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_02_manager_cannot_update_owner_only_step", + "path": "ios/HomesFlowTests/PermissionServiceTests.swift", + "line": 27 + }, + { + "name": "test_AC_PROC_02_guest_cannot_update_guest_visible_step", + "path": "ios/HomesFlowTests/PermissionServiceTests.swift", + "line": 37 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-03", + "type": "AC", + "statement": "AC-PROC-03** \u2014 Given a Manager user updates a step while offline and another user updates the same step before sync, when both devices reconnect, then the update with the most recent timestamp is persisted and the other user receives a notification about the overwritten change with reference to the activity log.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 248 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_03_server_newer_overwrites_pending_local", + "path": "ios/HomesFlowTests/ProcedureTests.swift", + "line": 48 + }, + { + "name": "test_AC_PROC_03_local_newer_keeps_pending_local", + "path": "ios/HomesFlowTests/ProcedureTests.swift", + "line": 60 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-04", + "type": "AC", + "statement": "AC-PROC-04** \u2014 Given an Owner or Manager user long-presses a step on a procedure they can modify, when the context menu appears, then Edit, Delete, Move Up, and Move Down are available.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 249 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 4, + "excerpt": "// @covers AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_04_owner_can_manage_step_structure", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 10 + }, + { + "name": "test_AC_PROC_04_manager_can_manage_step_structure", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 23 + }, + { + "name": "test_AC_PROC_04_manager_cannot_manage_owner_only_procedure_steps", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 36 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-05", + "type": "AC", + "statement": "AC-PROC-05** \u2014 Given an Owner or Manager user taps Add on the Steps section, when they enter a title and save, then a new step is appended at the next sort order and appears for permitted users after sync.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 250 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureDTO.swift", + "line": 97, + "excerpt": "/// @covers AC-PROC-05, AC-PROC-06" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 4, + "excerpt": "// @covers AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_05_new_step_appends_at_end", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 68 + }, + { + "name": "test_AC_PROC_05_move_up_swaps_with_previous_step", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 74 + }, + { + "name": "test_AC_PROC_05_move_down_swaps_with_next_step", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 80 + }, + { + "name": "test_AC_PROC_05_move_is_noop_at_list_boundaries", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 86 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-06", + "type": "AC", + "statement": "AC-PROC-06** \u2014 Given an Owner or Manager user creates, renames, reorders, or deletes a step, when the change syncs, then it persists for permitted users and an activity log entry is created.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 251 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureDTO.swift", + "line": 97, + "excerpt": "/// @covers AC-PROC-05, AC-PROC-06" + }, + { + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 4, + "excerpt": "// @covers AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_06_structure_changes_produce_activity_summaries", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 93 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-07", + "type": "AC", + "statement": "AC-PROC-07** \u2014 Given a Guest views a procedure, when they interact with steps, then step structure controls (long-press menu, Add step) are not available and step status remains read-only.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 252 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 87, + "excerpt": "// @covers AC-PROC-07" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 4, + "excerpt": "// @covers AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_07_guest_cannot_manage_step_structure", + "path": "ios/HomesFlowTests/StepStructureTests.swift", + "line": 50 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-PROC-08", + "type": "AC", + "statement": "AC-PROC-08** \u2014 Given a user views a step with an attached photo, when they tap **Photo attached**, then the photo opens in a modal preview; step rows show notes below the title, a tappable photo indicator when present, a pencil **Edit** control (when permitted) to the left of the status ellipsis menu, and the ellipsis menu remains rightmost.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 253 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/StepRowPresentation.swift", + "line": 3, + "excerpt": "// @covers AC-PROC-08" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/StepRowPresentationTests.swift", + "line": 4, + "excerpt": "// @covers AC-PROC-08" + } + ], + "proofs": [ + { + "name": "test_AC_PROC_08_photo_indicator_and_edit_controls", + "path": "ios/HomesFlowTests/StepRowPresentationTests.swift", + "line": 10 + }, + { + "name": "test_AC_PROC_08_terminal_statuses_strike_through", + "path": "ios/HomesFlowTests/StepRowPresentationTests.swift", + "line": 24 + }, + { + "name": "test_AC_PROC_08_tap_toggle_status_mapping", + "path": "ios/HomesFlowTests/StepRowPresentationTests.swift", + "line": 31 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-SYNC-01", + "type": "AC", + "statement": "> Conflict model evolution (decided 2026-07-03, source: story map; refined same day): v1 ships timestamp-wins (AC-SYNC-01, implemented and verified). The model is refined to be **data-type-aware**: terminal step statuses are protected (AC-SYNC-05), other status conflicts resolve automatically with loser notification \u2014 no human conflict-resolution UI (AC-SYNC-06), and structural actions are connectivity-gated (AC-SYNC-07). Field-level merge (AC-SYNC-02) is deferred to a later phase alongside version vectors.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 273 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/OverwriteNotificationPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Sync/SyncEngine.swift", + "line": 5, + "excerpt": "// @covers NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03" + }, + { + "path": "ios/HomesFlow/Core/Sync/OutboxSyncPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-01, AC-HOME-03" + }, + { + "path": "ios/HomesFlow/Core/Providers/ProviderConflictResolver.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderFieldMerge.swift", + "line": 3, + "excerpt": "// @covers AC-HOME-04, AC-SYNC-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_01_home_timestamp_wins_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 38 + }, + { + "name": "test_AC_SYNC_01_provider_timestamp_wins_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 48 + }, + { + "name": "test_AC_SYNC_01_conflict_decision_is_idempotent", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 60 + }, + { + "name": "test_AC_SYNC_01_server_delete_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 80 + }, + { + "name": "test_AC_SYNC_01_offline_overwrite_notifies_loser", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 109 + } + ], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 116, + "excerpt": "- [ ] T027 [US-ADMIN-02] Offline invite conflict resolution \u2014 **Carries**: AC-USER-03, AC-SYNC-01" + } + ], + "attestedBy": null + }, + { + "id": "AC-SYNC-02", + "type": "AC", + "statement": "> Conflict model evolution (decided 2026-07-03, source: story map; refined same day): v1 ships timestamp-wins (AC-SYNC-01, implemented and verified). The model is refined to be **data-type-aware**: terminal step statuses are protected (AC-SYNC-05), other status conflicts resolve automatically with loser notification \u2014 no human conflict-resolution UI (AC-SYNC-06), and structural actions are connectivity-gated (AC-SYNC-07). Field-level merge (AC-SYNC-02) is deferred to a later phase alongside version vectors.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 273 + }, + "status": "tracked-debt", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/SyncEngine.swift", + "line": 5, + "excerpt": "// @covers NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 139, + "excerpt": "- [ ] T035 [NFR-OFFL-01] Field-level merge for non-conflicting offline edits \u2014 **Carries**: AC-SYNC-02 \u2014 *deferred post-MVP (2026-07-03 conflict model decision); pairs with version vectors*" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 146, + "excerpt": "- [ ] T039 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_02_disjoint_fields_merge` \u2014 **Carries**: AC-SYNC-02 \u2014 *deferred post-MVP with T035*" + } + ], + "attestedBy": null + }, + { + "id": "AC-SYNC-03", + "type": "AC", + "statement": "AC-SYNC-03** \u2014 Given a user attempts an action that depends on stale permissions cached offline, when syncing, then if the server denies the action, the client reverts the change and shows a permission error with guidance to retry.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 277 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/PermissionRevertPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-03" + }, + { + "path": "ios/HomesFlow/Core/Sync/SyncEngine.swift", + "line": 5, + "excerpt": "// @covers NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_03_permission_denied_revert_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 160 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-SYNC-04", + "type": "AC", + "statement": "AC-SYNC-04** \u2014 Given local changes or homes are pending sync to the server, when the user views the dashboard, then unsynced homes are visibly indicated and the user can pull to refresh to retry sync while online.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 278 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/SyncIndicatorPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-04" + }, + { + "path": "ios/HomesFlow/Features/Dashboard/DashboardView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, US-ADMIN-01, AC-HOME-08, AC-SYNC-04, FR-NAV-01" + }, + { + "path": "ios/HomesFlowTests/SyncIndicatorTests.swift", + "line": 4, + "excerpt": "// @covers AC-SYNC-04" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_04_pending_sync_visible_on_dashboard", + "path": "ios/HomesFlowTests/SyncIndicatorTests.swift", + "line": 19 + }, + { + "name": "test_AC_SYNC_04_pending_state_announced_to_voiceover", + "path": "ios/HomesFlowTests/SyncIndicatorTests.swift", + "line": 34 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-SYNC-05", + "type": "AC", + "statement": "> Conflict model evolution (decided 2026-07-03, source: story map; refined same day): v1 ships timestamp-wins (AC-SYNC-01, implemented and verified). The model is refined to be **data-type-aware**: terminal step statuses are protected (AC-SYNC-05), other status conflicts resolve automatically with loser notification \u2014 no human conflict-resolution UI (AC-SYNC-06), and structural actions are connectivity-gated (AC-SYNC-07). Field-level merge (AC-SYNC-02) is deferred to a later phase alongside version vectors.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 273 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/StepStatusConflictPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-05, AC-SYNC-06" + }, + { + "path": "ios/HomesFlowTests/StepStatusConflictPolicyTests.swift", + "line": 4, + "excerpt": "// @covers AC-SYNC-05, AC-SYNC-06" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_05_terminal_step_protection_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 185 + }, + { + "name": "test_AC_SYNC_05_terminal_status_never_silently_regressed", + "path": "ios/HomesFlowTests/StepStatusConflictPolicyTests.swift", + "line": 14 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-SYNC-06", + "type": "AC", + "statement": "> Conflict model evolution (decided 2026-07-03, source: story map; refined same day): v1 ships timestamp-wins (AC-SYNC-01, implemented and verified). The model is refined to be **data-type-aware**: terminal step statuses are protected (AC-SYNC-05), other status conflicts resolve automatically with loser notification \u2014 no human conflict-resolution UI (AC-SYNC-06), and structural actions are connectivity-gated (AC-SYNC-07). Field-level merge (AC-SYNC-02) is deferred to a later phase alongside version vectors.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 273 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/StepStatusConflictPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-05, AC-SYNC-06" + }, + { + "path": "ios/HomesFlowTests/StepStatusConflictPolicyTests.swift", + "line": 4, + "excerpt": "// @covers AC-SYNC-05, AC-SYNC-06" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_06_losing_user_notified_with_reapply_guidance", + "path": "ios/HomesFlowTests/StepStatusConflictPolicyTests.swift", + "line": 90 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-SYNC-07", + "type": "AC", + "statement": "> Conflict model evolution (decided 2026-07-03, source: story map; refined same day): v1 ships timestamp-wins (AC-SYNC-01, implemented and verified). The model is refined to be **data-type-aware**: terminal step statuses are protected (AC-SYNC-05), other status conflicts resolve automatically with loser notification \u2014 no human conflict-resolution UI (AC-SYNC-06), and structural actions are connectivity-gated (AC-SYNC-07). Field-level merge (AC-SYNC-02) is deferred to a later phase alongside version vectors.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 273 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Sync/StructuralActionPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-07, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Members/MemberRepository.swift", + "line": 5, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Providers/ContactsView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, FR-NAV-01, AC-HOME-04, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/InviteMemberView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-07, FR-GUEST-02, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/StructuralActionPolicyTests.swift", + "line": 4, + "excerpt": "// @covers AC-SYNC-07" + } + ], + "proofs": [ + { + "name": "test_AC_SYNC_07_structural_offline_gate_matrix", + "path": "ios/HomesFlowTests/SyncConflictMatrixTests.swift", + "line": 246 + }, + { + "name": "test_AC_SYNC_07_structural_actions_blocked_offline", + "path": "ios/HomesFlowTests/StructuralActionPolicyTests.swift", + "line": 8 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-01", + "type": "AC", + "statement": "AC-USER-01** \u2014 Given an Owner provides an email/phone and role and sends an invite, when the invite is accepted, then the invitee is added to the home with the assigned role and receives appropriate permissions immediately.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 233 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/InvitePolicy.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-06, AC-USER-07" + }, + { + "path": "ios/HomesFlow/Core/Members/MemberRepository.swift", + "line": 5, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/InviteMemberView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-07, FR-GUEST-02, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 4, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-04, AC-USER-06, AC-USER-07" + } + ], + "proofs": [ + { + "name": "test_AC_USER_01_invite_accepted_grants_role", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 11 + }, + { + "name": "test_AC_USER_01_invite_email_validation_and_token_shape", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 52 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-02", + "type": "AC", + "statement": "AC-USER-02** \u2014 Given an Owner re-sends or revokes an invite before acceptance, when they revoke, then the invite token becomes invalid and the invitee cannot join with that token.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 235 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/InvitePolicy.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-06, AC-USER-07" + }, + { + "path": "ios/HomesFlow/Core/Members/PeopleSelection.swift", + "line": 3, + "excerpt": "// @covers FR-NAV-01, AC-USER-02, AC-HOME-10" + }, + { + "path": "ios/HomesFlow/Core/Members/MemberRepository.swift", + "line": 5, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/PendingInviteDetailView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-02, AC-USER-07, FR-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/PeopleSelectionTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-10, AC-USER-02" + }, + { + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 4, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-04, AC-USER-06, AC-USER-07" + } + ], + "proofs": [ + { + "name": "test_AC_USER_02_revoked_token_invalid", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 70 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-03", + "type": "AC", + "statement": "AC-USER-03** \u2014 Given an Owner invites a user while offline and the invite is processed on another device first, when sync occurs, then the most recent action (invite or revoke) by timestamp determines invite state and the Owner receives a notification of the resolved outcome.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 236 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 116, + "excerpt": "- [ ] T027 [US-ADMIN-02] Offline invite conflict resolution \u2014 **Carries**: AC-USER-03, AC-SYNC-01" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 126, + "excerpt": "- [ ] T033a [P] [US-ADMIN-02] Unit test `test_AC_USER_03_offline_invite_conflict` \u2014 **Carries**: AC-USER-03" + } + ], + "attestedBy": null + }, + { + "id": "AC-USER-04", + "type": "AC", + "statement": "AC-USER-04** \u2014 Given an Owner assigns a Manager role to a user, when the user signs in, then they can create and modify procedures and service providers for that home.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 240 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/MemberRepository.swift", + "line": 5, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 4, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-04, AC-USER-06, AC-USER-07" + } + ], + "proofs": [ + { + "name": "test_AC_USER_04_manager_can_update_step", + "path": "ios/HomesFlowTests/PermissionServiceTests.swift", + "line": 17 + }, + { + "name": "test_AC_USER_04_edit_role_can_modify_procedures", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 84 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-05", + "type": "AC", + "statement": "AC-USER-05** \u2014 Given an Owner assigns a Guest role to a user, when the guest signs in, then they see only guest-appropriate fields (e.g., WiFi, guest procedures) and cannot edit protected data.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 241 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 4, + "excerpt": "// @covers AC-GUEST-01, AC-GUEST-02, AC-GUEST-03, AC-GUEST-04, AC-GUEST-05, AC-USER-05" + } + ], + "proofs": [ + { + "name": "test_AC_USER_05_guest_role_read_only", + "path": "ios/HomesFlowTests/GuestTests.swift", + "line": 88 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-06", + "type": "AC", + "statement": "AC-USER-06** \u2014 Given concurrent role changes occur on multiple devices, when devices sync, then the change with the most recent timestamp wins and an owner-visible audit entry records the prior role.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 242 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/InvitePolicy.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-06, AC-USER-07" + }, + { + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 4, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-04, AC-USER-06, AC-USER-07" + } + ], + "proofs": [ + { + "name": "test_AC_USER_06_concurrent_role_change_audit", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 106 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "AC-USER-07", + "type": "AC", + "statement": "AC-USER-07** \u2014 Given an Owner creates an invite in MVP, when the invitee receives the shared invite link or token and signs in with the invited email, then they can accept the invite (paste token or open link) and join the home with the assigned role.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 234 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/InvitePolicy.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-06, AC-USER-07" + }, + { + "path": "ios/HomesFlow/Features/Members/InviteMemberView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-07, FR-GUEST-02, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/PendingInviteDetailView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-02, AC-USER-07, FR-GUEST-02" + }, + { + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 4, + "excerpt": "// @covers AC-USER-01, AC-USER-02, AC-USER-04, AC-USER-06, AC-USER-07" + } + ], + "proofs": [ + { + "name": "test_AC_USER_07_paste_token_accepts_invite", + "path": "ios/HomesFlowTests/MemberInviteTests.swift", + "line": 141 + } + ], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-AUTH-01", + "type": "FR", + "statement": "FR-AUTH-01** (Priority: Critical) \u2014 Secure user authentication via OAuth or Apple ID sign-in.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 54 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Supabase/SupabaseConfig.swift", + "line": 3, + "excerpt": "// @covers FR-AUTH-01, NFR-SEC-01" + }, + { + "path": "ios/HomesFlow/Core/Supabase/SupabaseClientProvider.swift", + "line": 4, + "excerpt": "// @covers FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/Core/Auth/AppleSignInPolicy.swift", + "line": 4, + "excerpt": "// @covers FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/Core/Sync/LocalDataStore.swift", + "line": 4, + "excerpt": "// @covers NFR-SEC-01, FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/App/AppRouter.swift", + "line": 4, + "excerpt": "// @covers FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/Features/Auth/AuthView.swift", + "line": 4, + "excerpt": "// @covers FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/Features/Settings/SettingsView.swift", + "line": 3, + "excerpt": "// @covers FR-NOTIF-01, FR-AUTH-01" + }, + { + "path": "ios/HomesFlowTests/AppleSignInPolicyTests.swift", + "line": 4, + "excerpt": "// @covers FR-AUTH-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 75, + "excerpt": "- [ ] T017 [P] [US-ADMIN-01] XCUITest: sign-in with email \u2192 dashboard visible \u2014 **Carries**: FR-AUTH-01" + } + ], + "attestedBy": null + }, + { + "id": "FR-GUEST-01", + "type": "FR", + "statement": "FR-GUEST-01** (Priority: Medium) \u2014 Guest users view only approved procedures and info.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 64 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-GUEST-01, FR-GUEST-01" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Features/Guest/GuestAccessDeniedView.swift", + "line": 3, + "excerpt": "// @covers AC-GUEST-02, FR-GUEST-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 252, + "excerpt": "- [ ] T071 [P] RLS integration tests against contracts/rls-permissions.md \u2014 **Carries**: FR-USER-01, FR-GUEST-01" + } + ], + "attestedBy": null + }, + { + "id": "FR-GUEST-02", + "type": "FR", + "statement": "FR-GUEST-02** (Priority: Medium) \u2014 Quick guest onboarding via email or SMS invite.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 65 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Features/Members/InviteMemberView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-01, AC-USER-07, FR-GUEST-02, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/PendingInviteDetailView.swift", + "line": 3, + "excerpt": "// @covers AC-USER-02, AC-USER-07, FR-GUEST-02" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-HOME-01", + "type": "FR", + "statement": "FR-HOME-01** (Priority: High) \u2014 Add/edit home properties with address, photos (display-optimized at upload and cached locally for hero display), and key info.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 56 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoCache.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-06, AC-HOME-07, FR-HOME-01" + }, + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoService.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Core/Models/SwiftDataModels.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, FR-USER-01, FR-PROC-01, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeRepository.swift", + "line": 6, + "excerpt": "// @covers FR-HOME-01, AC-HOME-01, AC-HOME-02, AC-HOME-08" + }, + { + "path": "ios/HomesFlow/Features/Home/HomeHeroCard.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, AC-HOME-07, AC-A11Y-01, NFR-A11Y-01" + }, + { + "path": "ios/HomesFlow/Features/HomeSetup/HomeSetupView.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-02, AC-HOME-03, FR-HOME-01" + }, + { + "path": "ios/HomesFlow/Features/Dashboard/DashboardView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, US-ADMIN-01, AC-HOME-08, AC-SYNC-04, FR-NAV-01" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-HOME-02", + "type": "FR", + "statement": "FR-HOME-02** (Priority: High) \u2014 Service provider directory (propane, electric, internet, lawn care, etc.) with contacts and notes.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 57 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04, AC-GUEST-01, FR-GUEST-01, AC-HOME-05, AC-SYNC-01, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Core/Providers/ServiceProviderDTO.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, AC-HOME-04" + }, + { + "path": "ios/HomesFlow/Features/Providers/ContactsView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, FR-NAV-01, AC-HOME-04, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlowTests/ProviderTests.swift", + "line": 4, + "excerpt": "// @covers AC-HOME-04, AC-HOME-05, FR-HOME-02" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-HOME-03", + "type": "FR", + "statement": "FR-HOME-03** (Priority: High) \u2014 Editable, categorized documents for important details (UI section label: **Files**). Permitted users add files from the **camera**, **photo library**, or **file browser**; file detail offers **Preview** via system Quick Look (zoom, PDF, video, audio where supported), with metadata and actions below.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 58 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Documents/DocumentStorageService.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-03, AC-HOME-06" + }, + { + "path": "ios/HomesFlow/Core/Documents/DocumentRepository.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-GUEST-01, FR-GUEST-01" + }, + { + "path": "ios/HomesFlow/Core/Documents/DocumentDTO.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-03, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/Documents/DocumentQuickLookPreview.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-HOME-13, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-HOME-04", + "type": "FR", + "statement": "FR-HOME-04** (Priority: Low) \u2014 Home display names MUST be normalized for display by trimming leading/trailing whitespace and collapsing internal runs of whitespace to a single space. Pure helper; no UI, networking, or sync behavior.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 59 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Home/HomeDisplayName.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-04, AC-HOME-15" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-LOG-01", + "type": "FR", + "statement": "FR-LOG-01** (Priority: Medium) \u2014 History log of changes for accountability.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 67 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/ActivityLog/ActivityLogDTO.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-01" + }, + { + "path": "ios/HomesFlow/Core/ActivityLog/ActivityLogService.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-01, AC-PROC-01" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-LOG-02", + "type": "FR", + "statement": "FR-LOG-02** (Priority: Medium) \u2014 User-authored **Log Book**: Owners and Managers write free-form log entries at household scope or attached to a procedure, viewable in a unified chronological log alongside activity history. Entries are append-only offline, editable only within a 10-minute grace window that starts at server receipt. Guests have no Log Book access.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 68 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/LogBook/LogBookScopeFilter.swift", + "line": 3, + "excerpt": "// @covers AC-LOG-05, FR-LOG-02" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookEntryDTO.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookRepository.swift", + "line": 5, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-06" + }, + { + "path": "ios/HomesFlow/Core/LogBook/LogBookGraceWindowPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-LOG-04, FR-LOG-02" + }, + { + "path": "ios/HomesFlow/Features/LogBook/LogEntryEditorSheet.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-04" + }, + { + "path": "ios/HomesFlow/Features/LogBook/CommunicationsLogView.swift", + "line": 3, + "excerpt": "// @covers FR-LOG-02, AC-LOG-01, AC-LOG-02, AC-LOG-05, AC-LOG-06" + }, + { + "path": "ios/HomesFlowTests/LogBookTests.swift", + "line": 4, + "excerpt": "// @covers AC-LOG-01, AC-LOG-02, AC-LOG-03, AC-LOG-04, AC-LOG-05, AC-LOG-06, FR-LOG-02" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-NAV-01", + "type": "FR", + "statement": "FR-NAV-01** (Priority: High) \u2014 Home detail MUST expose four sections labeled **Procedures**, **Contacts**, **Files**, and **People** with device-appropriate navigation (iPhone: hero + horizontal tabs; iPad: compact left-column hero + vertical icon tabs, with a **three-panel** layout \u2014 sidebar, section list, and section detail \u2014 for every section).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 60 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/PeopleSelection.swift", + "line": 3, + "excerpt": "// @covers FR-NAV-01, AC-USER-02, AC-HOME-10" + }, + { + "path": "ios/HomesFlow/Features/Documents/FilesView.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, FR-NAV-01, AC-HOME-11, AC-HOME-12, AC-HOME-13, AC-HOME-14, AC-GUEST-01" + }, + { + "path": "ios/HomesFlow/Features/Providers/ContactsView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-02, FR-NAV-01, AC-HOME-04, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Dashboard/DashboardView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, US-ADMIN-01, AC-HOME-08, AC-SYNC-04, FR-NAV-01" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-NOTIF-01", + "type": "FR", + "statement": "FR-NOTIF-01** (Priority: Medium) \u2014 Optional push notifications for changed statuses and new assignments.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 66 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Features/Settings/SettingsView.swift", + "line": 3, + "excerpt": "// @covers FR-NOTIF-01, FR-AUTH-01" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-PROC-01", + "type": "FR", + "statement": "FR-PROC-01** (Priority: High) \u2014 Add/edit procedure lists (e.g., winterizing, arrival prep) with persistent status (Not Started / In Progress / Complete / N/A).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 61 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Models/SwiftDataModels.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, FR-USER-01, FR-PROC-01, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureDTO.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProceduresView.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02, NFR-PERF-01, AC-GUEST-02, AC-GUEST-04" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-PROC-02", + "type": "FR", + "statement": "FR-PROC-02** (Priority: High) \u2014 Procedures contain ordered steps, each with its own status. Owner and Manager users can **create, rename, reorder, and delete** steps on procedures they can modify (per visibility). Guests have read-only access to step content and status.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 62 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureDTO.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProceduresView.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02, NFR-PERF-01, AC-GUEST-02, AC-GUEST-04" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-PROC-03", + "type": "FR", + "statement": "FR-PROC-03** (Priority: High) \u2014 Attach optional notes and photos to individual procedure steps (document attachments remain on the Files tab per FR-HOME-03).", + "registry": { + "path": "HomesFlow.prd.md", + "line": 63 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Storage/ProcedureAttachmentService.swift", + "line": 5, + "excerpt": "// @covers FR-PROC-03" + }, + { + "path": "ios/HomesFlow/Core/Procedures/ProcedureRepository.swift", + "line": 6, + "excerpt": "// @covers AC-PROC-01, FR-PROC-01, FR-PROC-02, FR-PROC-03, AC-PROC-02, AC-PROC-03, AC-PROC-04, AC-PROC-05, AC-PROC-06, AC-PROC-07, AC-GUEST-03, AC-GUEST-05, FR-" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProcedureDetailView.swift", + "line": 4, + "excerpt": "// @covers FR-PROC-02, FR-PROC-03, FR-LOG-01, AC-PROC-01, AC-PROC-02, AC-PROC-04, AC-PROC-05, AC-PROC-07, AC-PROC-08, AC-GUEST-04, AC-SYNC-07" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "FR-USER-01", + "type": "FR", + "statement": "FR-USER-01** (Priority: Critical) \u2014 Multi-role (Owner, Manager, Guest) invitation and access logic, scoped per home.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 53 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Models/SwiftDataModels.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, FR-USER-01, FR-PROC-01, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Permissions/PermissionService.swift", + "line": 3, + "excerpt": "// @covers FR-USER-01, FR-GUEST-01, AC-PROC-02, AC-PROC-04, AC-PROC-07, AC-GUEST-02" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeAccessPolicy.swift", + "line": 3, + "excerpt": "// @covers NFR-SEC-01, FR-USER-01" + }, + { + "path": "ios/HomesFlowTests/HomeAccessPolicyTests.swift", + "line": 4, + "excerpt": "// @covers NFR-SEC-01, FR-USER-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 252, + "excerpt": "- [ ] T071 [P] RLS integration tests against contracts/rls-permissions.md \u2014 **Carries**: FR-USER-01, FR-GUEST-01" + } + ], + "attestedBy": null + }, + { + "id": "FR-USER-02", + "type": "FR", + "statement": "FR-USER-02** (Priority: Critical) \u2014 Owners can add, edit, remove users and assign roles.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 55 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Members/MemberRemovalPolicy.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02" + }, + { + "path": "ios/HomesFlow/Core/Members/MemberRepository.swift", + "line": 5, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/Members/MembersView.swift", + "line": 3, + "excerpt": "// @covers FR-USER-02, AC-USER-01, AC-USER-02, AC-USER-04\u202606, FR-NAV-01, AC-HOME-12, AC-SYNC-07" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/MemberRemovalTests.swift", + "line": 4, + "excerpt": "// @covers FR-USER-02" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "NFR-A11Y-01", + "type": "NFR", + "statement": "NFR-A11Y-01** \u2014 UI MUST respect iOS accessibility settings (Dynamic Type, VoiceOver, Reduce Motion, contrast) and remain usable at all supported content size categories", + "registry": { + "path": "HomesFlow.prd.md", + "line": 145 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/UI/AccessibilityBaseline.swift", + "line": 3, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + }, + { + "path": "ios/HomesFlow/Features/Home/HomeHeroCard.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, AC-HOME-07, AC-A11Y-01, NFR-A11Y-01" + }, + { + "path": "ios/HomesFlow/Features/HomeDetail/HomeDetailView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, FR-NAV-01, FR-USER-02, AC-HOME-09, AC-HOME-10, AC-HOME-11, AC-A11Y-02, AC-A11Y-03, NFR-A11Y-01, AC-GUEST-01, AC-USER-05" + }, + { + "path": "ios/HomesFlowTests/AccessibilityBaselineTests.swift", + "line": 5, + "excerpt": "// @covers NFR-A11Y-01, AC-A11Y-01, AC-A11Y-02, AC-A11Y-03" + } + ], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "NFR-OFFL-01", + "type": "NFR", + "statement": "NFR-OFFL-01** \u2014 Local data caching and offline sync as a core requirement from day one", + "registry": { + "path": "HomesFlow.prd.md", + "line": 164 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Models/SwiftDataModels.swift", + "line": 4, + "excerpt": "// @covers FR-HOME-01, FR-USER-01, FR-PROC-01, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Sync/SyncEngine.swift", + "line": 5, + "excerpt": "// @covers NFR-OFFL-01, AC-SYNC-01, AC-SYNC-02, AC-SYNC-03" + }, + { + "path": "ios/HomesFlow/Core/Sync/StructuralActionPolicy.swift", + "line": 3, + "excerpt": "// @covers AC-SYNC-07, NFR-OFFL-01" + }, + { + "path": "ios/HomesFlow/Core/Sync/NetworkMonitor.swift", + "line": 4, + "excerpt": "// @covers NFR-OFFL-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 139, + "excerpt": "- [ ] T035 [NFR-OFFL-01] Field-level merge for non-conflicting offline edits \u2014 **Carries**: AC-SYNC-02 \u2014 *deferred post-MVP (2026-07-03 conflict model decision); pairs with version vectors*" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 146, + "excerpt": "- [ ] T039 [P] [NFR-OFFL-01] Unit test `test_AC_SYNC_02_disjoint_fields_merge` \u2014 **Carries**: AC-SYNC-02 \u2014 *deferred post-MVP with T035*" + } + ], + "attestedBy": null + }, + { + "id": "NFR-PERF-01", + "type": "NFR", + "statement": "NFR-PERF-01** \u2014 Sub-2s average screen load times (across devices); client streams large file previews to disk (avoids holding entire files in memory) and limits concurrent home-photo prefetch.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 144 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Storage/HomePhotoService.swift", + "line": 5, + "excerpt": "// @covers AC-HOME-01, AC-HOME-06, AC-HOME-07, FR-HOME-01, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Features/Documents/DocumentQuickLookPreview.swift", + "line": 5, + "excerpt": "// @covers FR-HOME-03, AC-HOME-13, NFR-PERF-01" + }, + { + "path": "ios/HomesFlow/Features/Procedures/ProceduresView.swift", + "line": 3, + "excerpt": "// @covers FR-PROC-01, FR-PROC-02, NFR-PERF-01, AC-GUEST-02, AC-GUEST-04" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 254, + "excerpt": "- [ ] T072a [P] Performance smoke: screen load & sync latency baselines \u2014 **Carries**: NFR-PERF-01, NFR-SYNC-01" + } + ], + "attestedBy": null + }, + { + "id": "NFR-REL-01", + "type": "NFR", + "statement": "NFR-REL-01** \u2014 99.9% crash-free sessions", + "registry": { + "path": "HomesFlow.prd.md", + "line": 142 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 256, + "excerpt": "- [ ] T072c [P] Enable Xcode crash report collection / document NFR-REL-01 monitoring path \u2014 **Carries**: NFR-REL-01" + } + ], + "attestedBy": null + }, + { + "id": "NFR-SCALE-01", + "type": "NFR", + "statement": "NFR-SCALE-01** \u2014 Designed for 100,000 concurrent users", + "registry": { + "path": "HomesFlow.prd.md", + "line": 181 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 255, + "excerpt": "- [ ] T072b [P] Document NFR-SCALE-01 architecture assumptions in plan.md (no load test in MVP) \u2014 **Carries**: NFR-SCALE-01" + } + ], + "attestedBy": null + }, + { + "id": "NFR-SEC-01", + "type": "NFR", + "statement": "NFR-SEC-01** \u2014 Encrypted storage for all personal/home/procedure data", + "registry": { + "path": "HomesFlow.prd.md", + "line": 175 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Core/Supabase/SupabaseConfig.swift", + "line": 3, + "excerpt": "// @covers FR-AUTH-01, NFR-SEC-01" + }, + { + "path": "ios/HomesFlow/Core/Sync/LocalDataStore.swift", + "line": 4, + "excerpt": "// @covers NFR-SEC-01, FR-AUTH-01" + }, + { + "path": "ios/HomesFlow/Core/Home/HomeAccessPolicy.swift", + "line": 3, + "excerpt": "// @covers NFR-SEC-01, FR-USER-01" + }, + { + "path": "ios/HomesFlowTests/HomeAccessPolicyTests.swift", + "line": 4, + "excerpt": "// @covers NFR-SEC-01, FR-USER-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 304, + "excerpt": "- [ ] T087 [P] Enforce PR-only merge to `main`: branch protection requires pull request, `craft-gate` + `SonarCloud Code Analysis`, and disallow admin bypass \u2014 **Carries**: plan Phase 0 (infrastruct" + } + ], + "attestedBy": null + }, + { + "id": "NFR-SYNC-01", + "type": "NFR", + "statement": "NFR-SYNC-01** \u2014 Time-to-sync for updates below 1 second", + "registry": { + "path": "HomesFlow.prd.md", + "line": 143 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 254, + "excerpt": "- [ ] T072a [P] Performance smoke: screen load & sync latency baselines \u2014 **Carries**: NFR-PERF-01, NFR-SYNC-01" + } + ], + "attestedBy": null + }, + { + "id": "US-ADMIN-01", + "type": "US", + "statement": "US-ADMIN-01** \u2014 As an Owner, I want to add my second home's details, so that I can manage it remotely.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 31 + }, + "status": "verified", + "implementations": [ + { + "path": "ios/HomesFlow/Features/Dashboard/DashboardView.swift", + "line": 3, + "excerpt": "// @covers FR-HOME-01, US-ADMIN-01, AC-HOME-08, AC-SYNC-04, FR-NAV-01" + } + ], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 75, + "excerpt": "- [ ] T017 [P] [US-ADMIN-01] XCUITest: sign-in with email \u2192 dashboard visible \u2014 **Carries**: FR-AUTH-01" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 96, + "excerpt": "- [ ] T022 [P] [US-ADMIN-01] Unit test `test_AC_HOME_01_valid_home_created` \u2014 **Carries**: AC-HOME-01 \u2014 *validator only; integration test pending*" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 102, + "excerpt": "- [ ] T024d [P] [US-ADMIN-01] Snapshot or UI test: iPad home detail trailing column has no hero/segmented tabs \u2014 **Carries**: AC-HOME-09 \u2014 *deferred: needs snapshot/XCUITest infra; manual iPad pas" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 103, + "excerpt": "- [ ] T024e [P] [US-ADMIN-01] Snapshot or UI test: iPad leading column shows compact home hero + vertical icon section tabs \u2014 **Carries**: AC-HOME-10 \u2014 *deferred: needs snapshot/XCUITest infra; ma" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 104, + "excerpt": "- [ ] T024f [P] [US-ADMIN-01] UI test: Contacts, Files, and People sections use the three-panel layout on iPad \u2014 **Carries**: AC-HOME-11 \u2014 *deferred: needs snapshot/XCUITest infra; manual iPad pas" + } + ], + "attestedBy": null + }, + { + "id": "US-ADMIN-02", + "type": "US", + "statement": "US-ADMIN-02** \u2014 As an Owner, I want to invite adult children and service providers as users, so that they can help manage the home or access info they need.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 32 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 116, + "excerpt": "- [ ] T027 [US-ADMIN-02] Offline invite conflict resolution \u2014 **Carries**: AC-USER-03, AC-SYNC-01" + }, + { + "path": "specs/001-mvp/tasks.md", + "line": 126, + "excerpt": "- [ ] T033a [P] [US-ADMIN-02] Unit test `test_AC_USER_03_offline_invite_conflict` \u2014 **Carries**: AC-USER-03" + } + ], + "attestedBy": null + }, + { + "id": "US-ADMIN-03", + "type": "US", + "statement": "US-ADMIN-03** \u2014 As an Owner, I want to assign permissions to Manager or Guest users for each home, so that only trusted users see confidential info.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 33 + }, + "status": "backlog", + "implementations": [], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "US-EDIT-01", + "type": "US", + "statement": "US-EDIT-01** \u2014 As a Manager user, I want to update maintenance tasks, so that others can see what's done or outstanding.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 37 + }, + "status": "backlog", + "implementations": [], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "US-EDIT-02", + "type": "US", + "statement": "US-EDIT-02** \u2014 As a Manager user, I want to view and modify service provider details, so I can coordinate repairs or services if needed.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 38 + }, + "status": "backlog", + "implementations": [], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "US-GUEST-01", + "type": "US", + "statement": "US-GUEST-01** \u2014 As a Guest, I want to see only guest-appropriate information (like WiFi codes/rules), so my access is controlled and simple.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 42 + }, + "status": "tracked-debt", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/001-mvp/tasks.md", + "line": 221, + "excerpt": "- [ ] T064 [US-GUEST-01] XCUITest: guest cannot edit provider \u2014 **Carries**: AC-GUEST-01, SC-03" + } + ], + "attestedBy": null + }, + { + "id": "US-GUEST-02", + "type": "US", + "statement": "US-GUEST-02** \u2014 As a Guest, I want to view house procedures for guests, so I know what is expected during my stay.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 43 + }, + "status": "backlog", + "implementations": [], + "proofs": [], + "debtTasks": [], + "attestedBy": null + }, + { + "id": "US-SHARE-01", + "type": "US", + "statement": "US-SHARE-01** \u2014 As an Owner, I want to share a single procedure as a read-only link with a non-member helper, so that a one-off task can be followed without granting a full Guest invite.", + "registry": { + "path": "HomesFlow.prd.md", + "line": 47 + }, + "status": "backlog", + "implementations": [], + "proofs": [], + "debtTasks": [ + { + "path": "specs/backlog/tasks.md", + "line": 8, + "excerpt": "- [ ] T901 Deliver read-only procedure sharing link \u2014 **Carries**: US-SHARE-01" + } + ], + "attestedBy": null + } + ] +}