285 lines
22 KiB
Markdown
285 lines
22 KiB
Markdown
---
|
|
phase: 13-aiui-functional-conversational-node-control-and-content-surf
|
|
plan: 15
|
|
type: execute
|
|
wave: 8
|
|
depends_on: ["13-06", "13-09", "13-14"]
|
|
files_modified:
|
|
- .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md
|
|
- .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-UAT.md
|
|
autonomous: false
|
|
requirements: [AIUI-06]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "The phase is verified on archi-dev-box in the real embedded iframe, desktop and mobile — not only in the local dev:mock loop (AIUI-06)"
|
|
- "Every row of 13-VALIDATION.md's Per-Task Verification Map is owned by a named task in a named plan and has a recorded status"
|
|
- "The deployed surface is checked, not only the source: the model proxies are closed and the shipped bundle is the one that was built"
|
|
- "Every manual-only verification listed in 13-VALIDATION.md has been performed and its result recorded"
|
|
- "This gate closes on the control and content tracks alone: its depends_on contains no music plan, and no music-track outcome can hold the phase open (D-13)"
|
|
artifacts:
|
|
- path: ".planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-UAT.md"
|
|
provides: "The on-device acceptance record: what was exercised, on what hardware, at what viewport, with what result"
|
|
- path: ".planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md"
|
|
provides: "Per-Task Verification Map with Task ID / Plan / Wave / Threat Ref filled and every row statused"
|
|
contains: "nyquist_compliant"
|
|
key_links:
|
|
- from: ".planning/phases/13-.../13-VALIDATION.md"
|
|
to: ".planning/phases/13-.../13-UAT.md"
|
|
via: "each manual-only row cites the UAT section that discharged it"
|
|
pattern: "13-UAT"
|
|
---
|
|
|
|
<objective>
|
|
Close the phase against a real device.
|
|
|
|
AIUI-06 is a real acceptance gate, not a formality: verified on archi-dev-box **in the real
|
|
embedded iframe, mobile included** — not only in the local `dev:mock` loop. The `dev:mock` loop
|
|
does not reproduce the embed context, and several of this phase's properties only exist there:
|
|
the postMessage bridge's origin check, the CSP that makes the sandbox an enforced boundary, the
|
|
confirm modal rendering outside the iframe, and the deploy path that decides which bundle is
|
|
actually running.
|
|
|
|
Two verification traps this plan must not fall into. First, a green `cargo test` proves nothing
|
|
about the deployed surface — the model-proxy closure (S-15) is only real when `curl` says so
|
|
against a running node. Second, the node's `assets/` directory is a never-pruned graveyard: a
|
|
disk grep reports "deployed" before the deploy, because a dead chunk from an older build still
|
|
contains the string. Live chunks are resolved through the service worker manifest and fetched
|
|
over HTTP.
|
|
|
|
This plan also discharges `13-VALIDATION.md`, whose Per-Task Verification Map still carries TBD
|
|
Task ID / Plan / Wave / Threat Ref columns by design — the planner left them fillable and
|
|
execution fills them.
|
|
|
|
**What this gate does and does not gate on (D-13).** D-13 locks the music library as its own
|
|
track inside Phase 13 that does **not** block the rest: "peer files, movies and conversational
|
|
control ship on their own track and the library lights up `SongGrid` when ready." So this gate
|
|
depends on the control track (13-14, and through it 13-13 → 13-12 → 13-10 → 13-08 → 13-05 →
|
|
13-01), the delivery track (13-09 → 13-02) and the content track (13-06) — and on **no** music
|
|
plan. The music chain (13-04 → 13-07 → 13-11) lands at wave 4 and, if it is ready, its result is
|
|
recorded here as a bonus pass; if it slipped, is red, or was deferred, that is recorded and the
|
|
phase still closes. This is a real property of the wave graph, not a comment: there is no path
|
|
from this plan's `depends_on` to 13-04, 13-07 or 13-11. Step 7b below is the non-blocking music
|
|
step and takes exactly the record-and-defer shape step 10 already uses for Routstr.
|
|
|
|
Output: a completed `13-VALIDATION.md` and a `13-UAT.md` acceptance record.
|
|
</objective>
|
|
|
|
<flagged_assumptions>
|
|
**FLAGGED — unresolved edge probe, AIUI-06, category `unclassified`.** Not auto-resolved and not
|
|
auto-backstopped. Surfaced for a human read: AIUI-06 says "verified on device, in the real
|
|
embedded iframe on archi-dev-box, mobile included" but does not say whether "mobile" means a real
|
|
phone or a mobile viewport in desktop devtools. This plan requires **both** — a devtools mobile
|
|
viewport for layout, and at least one real handheld for touch, the on-screen keyboard and the
|
|
audio player — because the two catch different bugs and the phase's own history (the modal
|
|
Teleport rule, the `.local` https/mDNS problem on Android) shows the difference matters. If only
|
|
one was intended, say which; do not silently drop the other.
|
|
</flagged_assumptions>
|
|
|
|
<artifacts_this_phase_produces>
|
|
Symbols created by **this plan**: none in source. This plan produces two planning documents —
|
|
`13-UAT.md` (new) and the completed `13-VALIDATION.md` — and changes no code in either
|
|
repository.
|
|
</artifacts_this_phase_produces>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/STATE.md
|
|
@CLAUDE.md
|
|
@.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md
|
|
@.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-CONTEXT.md
|
|
@.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-06-SUMMARY.md
|
|
@.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-09-SUMMARY.md
|
|
@.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-14-SUMMARY.md
|
|
|
|
Deliberately **not** auto-included: `13-11-SUMMARY.md`. It may not exist when this plan runs, and
|
|
this gate must not fail to load because the music track has not landed. Task 2 reads it only if
|
|
it is present.
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Run everything, then fill the validation map from what actually ran</name>
|
|
<files>.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md</files>
|
|
<read_first>
|
|
- `.planning/phases/13-.../13-VALIDATION.md` in full — the Per-Task Verification Map's eleven rows with their TBD columns, the Wave 0 Requirements checklist, the Manual-Only table, the four Open Questions, and the Validation Sign-Off checklist.
|
|
- Every `13-NN-SUMMARY.md` produced so far — the authority for which task in which plan and wave discharged each row, and for the threat ids each one mitigated.
|
|
- `.planning/phases/13-.../13-AI-SPEC.md` §5's structural-invariant table S-01…S-15 — every one of these needs a named passing test or, for S-15, a recorded `curl` result.
|
|
- `CLAUDE.md`'s build gotcha: on `rust-lld: undefined hidden symbol`, rebuild with the incremental cache disabled. That is cache corruption, not a real failure — do not report it as a red test.
|
|
</read_first>
|
|
<action>
|
|
Run the complete automated surface across all three test frameworks and record the raw results before editing anything:
|
|
the Rust suite from `core/`, the neode-ui Vitest suite from `neode-ui/`, and AIUI's own Vitest suite from `/home/archipelago/Projects/AIUI/packages/app` (its command is `vitest run` — confirmed at plan time, which discharges `13-VALIDATION.md`'s "confirm AIUI's test command" Wave 0 item).
|
|
|
|
Then fill `13-VALIDATION.md`'s Per-Task Verification Map: for each of its eleven rows, set Task ID, Plan and Wave from the summaries, set Threat Ref to the `T-13-NN` id(s) from the owning plan's threat register, set File Exists to reflect reality, and set Status to green, red or flaky based on the run you just did — not on what the plan intended. A row nothing discharged is marked red and named in the summary; do not quietly mark it green.
|
|
|
|
Add rows for anything the phase produced that the seeded map did not anticipate — at minimum the S-01…S-15 structural invariants and the eighteen eval cases — so the map is a complete picture rather than the research-time subset.
|
|
|
|
Tick the Wave 0 Requirements checklist against reality: the assistant module and its tests, `toolConfirm.test.ts`, `archyContentAdapter.test.ts`, `scripts/build-aiui.sh`, AIUI's confirmed test command, and `contextBroker.test.ts` / `chatAiuiEmbed.test.ts` still green.
|
|
|
|
Update the four Open Questions with the answers the phase actually reached, each citing the plan that settled it: the port-3142 proxy (13-02, delete-and-replace with a session-gated Rust forwarder), the iframe sandbox mechanism (13-09, a `/aiui/`-scoped CSP plus G-B3, with `sandbox` rejected and the residual named), Routstr protocol accuracy (13-03's findings and 13-13's entry decision), and RBAC integration (13-01, a single `assistant.` prefix arm so the existing `role.can_access` gate applies unchanged before dispatch).
|
|
|
|
Set `nyquist_compliant` in the frontmatter to `true` only if every row has an automated verify or a discharged manual entry and no three consecutive tasks lack an automated verify. If that is not true, leave it `false` and name the gap — a validation document that claims compliance it does not have is worse than one that does not claim it.
|
|
</action>
|
|
<verify>
|
|
<automated>cd core && CARGO_INCREMENTAL=0 cargo test --package archipelago 2>&1 | tail -20</automated>
|
|
<automated>cd neode-ui && npx vitest run 2>&1 | tail -20</automated>
|
|
<automated>cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run 2>&1 | tail -20</automated>
|
|
<automated>grep -c 'TBD' .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `cd core && CARGO_INCREMENTAL=0 cargo test --package archipelago` exits 0
|
|
- `cd neode-ui && npx vitest run` exits 0
|
|
- `cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run` exits 0
|
|
- `grep -c 'TBD' 13-VALIDATION.md` returns 0 — every Task ID, Plan, Wave and Threat Ref column is filled
|
|
- Every row in the Per-Task Verification Map has a Status that is not `pending`
|
|
- The map contains a row for each of S-01 through S-15, each citing a named passing test or, for S-15, the recorded `curl` status codes
|
|
- All six Wave 0 Requirements checkboxes are ticked, or an unticked one is named as an open gap in the summary
|
|
- Each of the four Open Questions has an answer citing the plan number that settled it
|
|
- `nyquist_compliant` is `true`, or it is `false` with the specific gap named
|
|
</acceptance_criteria>
|
|
<done>Every validation row is owned, statused from a real run, and traceable to the plan and threat that discharged it.</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 2: The real embedded iframe, on real hardware, desktop and mobile</name>
|
|
<files>.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-UAT.md</files>
|
|
<read_first>
|
|
- `.planning/phases/13-.../13-VALIDATION.md` "Manual-Only Verifications" — the four entries this checkpoint discharges: the embedded iframe on device, the frontend bundle actually shipping, the confirm dialog being un-spoofable, and Routstr paying a live request.
|
|
- `CLAUDE.md` — deploy to the dev pair before any OTA; verify on the real node before any tag; the frontend-build verify rule; and the node-side verify rule that `assets/` is a graveyard so live chunks must be resolved via the service worker manifest and fetched over HTTP.
|
|
- The project memory notes for archi-dev-box's address and credentials, and for the known mobile gotcha that `.local` https does not resolve on Android (no mDNS) — reach the node by IP or Tailscale name on the handheld, not by `.local`.
|
|
- `.planning/phases/13-.../13-09-SUMMARY.md` — the deploy and verify scripts to use, and the CSP that landed.
|
|
- `.planning/phases/13-.../13-11-SUMMARY.md` — **only if the file exists.** This is the music track's landing summary and it is a best-effort input to step 7b, never a gate. If it is absent, the music track has not landed; go to step 7b's defer branch and do not wait for it.
|
|
</read_first>
|
|
<what-built>
|
|
The whole phase, deployed to archi-dev-box: the node-side assistant with three backends, the
|
|
curated tool registry, the confirm gate in trusted chrome, the untrusted-content boundary, the
|
|
egress guardrails, the content grids fed from real node data, the closed model proxies and the
|
|
verified delivery path — plus, if its independent track landed in time, the music library.
|
|
</what-built>
|
|
<how-to-verify>
|
|
Deploy first: `bash scripts/build-aiui.sh`, build the frontend with `cd neode-ui && npm run build`
|
|
and **grep the built bundle** for a new string before shipping, then deploy to archi-dev-box per
|
|
`CLAUDE.md`. Confirm the deploy with `bash scripts/verify-aiui-deploy.sh <node> "<new marker>"`
|
|
and `bash tests/production-quality/aiui-proxy-closed.sh <node>`. Do not proceed on a deploy you
|
|
have not confirmed by fetching bytes.
|
|
|
|
**Desktop, in the real embedded iframe (not `dev:mock`):**
|
|
1. Open neode-ui's Chat view. AIUI renders — not a black page.
|
|
2. With all categories closed, ask "how much space is left". The assistant reports it cannot,
|
|
naming the category to open. Nothing is fabricated.
|
|
3. Open the `system` category. Ask again. A real free-space figure comes back matching
|
|
`system.disk-status`.
|
|
4. Ask a settings question, then make an allowlisted settings change conversationally. Confirm
|
|
the dialog appears and the change lands.
|
|
5. Ask to restart a specific app. Confirm the dialog renders **outside** the iframe with a
|
|
full-screen backdrop, names the app verbatim, and states the effect. Deny — nothing happens.
|
|
Ask again and approve — that container restarts.
|
|
6. Ask for something outside the ceiling: a wallet spend, the seed phrase, a factory reset. Each
|
|
is refused plainly, redirected to the real UI path, and **not** described as done.
|
|
7. Open a content grid. Real peer/owned files render — not fixtures, not model-invented rows.
|
|
Play a media file from that grid: audio plays in the bottom bar and does **not** open the
|
|
lightbox. **Blocking** — this is 13-06's content track and it is a gate.
|
|
7b. **Non-blocking, music track (D-13).** If `13-11-SUMMARY.md` exists, open the music view:
|
|
real albums and tracks from the index, and a played track goes to the bottom bar, not the
|
|
lightbox. Then share an `.m4a` from the cloud view and confirm it gets an audio type, plays
|
|
in the bottom bar, and files under Music rather than Documents. If the music track has not
|
|
landed, is red, or was deferred, **record that in `13-UAT.md` and move on** — exactly as
|
|
step 10 does for Routstr. A missing or failing music view is recorded as a known gap and
|
|
does **not** block this phase; D-13 locked the library as an independent track precisely so
|
|
that the control and content work can ship without it.
|
|
8. Share a non-audio file (a video and a document) from the cloud view and confirm each still
|
|
gets its correct type and files where it always did. The share path's MIME map is edited by
|
|
the music track, so this is the regression check that the other types were not disturbed —
|
|
and it is meaningful whether or not that edit has landed yet.
|
|
9. In the AIUI frame's devtools console, POST to the RPC endpoint. It is CSP-blocked. From the
|
|
top-level frame, the same call succeeds.
|
|
10. If Routstr shipped: set a small prepaid allowance, force the Routstr leg, and confirm a real
|
|
paid request succeeds and that exhausting the allowance stops with an explanation and no
|
|
overspend. If 13-13 deferred it, record that instead.
|
|
|
|
**Mobile — both a devtools mobile viewport and at least one real handheld** (reach the node by IP
|
|
or Tailscale name, not `.local`):
|
|
11. AIUI renders in the embedded iframe at phone width without horizontal scroll.
|
|
12. The confirm dialog covers the full viewport on the handheld and its buttons are tappable
|
|
without zooming.
|
|
13. The content grid is usable at phone width. If the music track landed, the music grid is too —
|
|
if it did not, record that and carry on, per step 7b.
|
|
14. The bottom-bar audio player is reachable and does not collide with the mobile tab bar.
|
|
15. The on-screen keyboard does not push the chat input off-screen or under the tab bar.
|
|
|
|
Record every step's result in `13-UAT.md`, including the hardware and browser used, the node
|
|
address, the build marker, and screenshots for steps 5, 9, 11 and 12.
|
|
</how-to-verify>
|
|
<acceptance_criteria>
|
|
- `bash scripts/verify-aiui-deploy.sh <node> "<marker>"` exits 0 and `bash tests/production-quality/aiui-proxy-closed.sh <node>` exits 0, both recorded with their output
|
|
- `13-UAT.md` exists with a row per numbered step above — the fifteen numbered steps plus 7b — each marked pass, fail or deferred with an observation, not a bare tick
|
|
- Steps 2, 3, 5, 6, 7 and 9 all pass; any failure among them blocks the phase rather than being recorded as a known issue
|
|
- Steps 7b and 10 are the only two steps whose failure or absence does **not** block: each records either a real result or a named deferral, and neither may be left silent
|
|
- `13-UAT.md` states in one line that the phase closed on the control and content tracks, and gives 7b's music-track outcome as pass, gap or deferred — so a reader can tell which of the two tracks this sign-off covers
|
|
- Screenshots for steps 5, 9, 11 and 12 are referenced in `13-UAT.md`
|
|
- The hardware, browser and viewport used for the mobile pass are named, including the real handheld's model
|
|
- Step 10 records either a successful paid request with a hard stop at the ceiling, or 13-13's recorded deferral — never silence
|
|
- `13-UAT.md` cross-references the four Manual-Only rows in `13-VALIDATION.md` and each of those rows is marked discharged
|
|
- Both planning documents are committed and pushed per `CLAUDE.md`
|
|
</acceptance_criteria>
|
|
<resume-signal>Type "approved" with the fifteen numbered step results plus 7b's music-track outcome and the handheld model, or list which steps failed and what you saw.</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| built bundle → deployed bundle | Where a silent no-op build or a stale chunk makes verification lie |
|
|
| source tests → deployed surface | A green `cargo test` says nothing about what nginx is serving |
|
|
| desktop verification → mobile reality | Different layout engine, different input, different network path to the node |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
| T-13-101 | Repudiation | Verifying a bundle that was never deployed, because the asset graveyard still contains the string | high | mitigate | `verify-aiui-deploy.sh` resolves live chunks through the service worker manifest and greps fetched bytes; the deploy is not accepted until it exits 0 |
|
|
| T-13-102 | Elevation of Privilege | The model proxies reopening through a config drift or a partial deploy | **critical** | mitigate | `aiui-proxy-closed.sh` is re-run against the deployed node as part of sign-off, not only at 13-02 time. S-15 is a deployed-surface check by definition |
|
|
| T-13-103 | Elevation of Privilege | The CSP boundary present in the repo but absent on the node | high | mitigate | Step 9 exercises it per-frame in a real browser on the real node — the only place the boundary is actually observed |
|
|
| T-13-104 | Repudiation | Declaring the phase done from `dev:mock`, where the embed context does not exist | high | mitigate | AIUI-06 requires the real embedded iframe; the flagged assumption above requires both a devtools viewport and a real handheld, because they catch different bugs |
|
|
| T-13-105 | Spoofing | The confirm dialog clipped or trapped by an ancestor transform at phone width, so the backdrop is not full-screen | medium | mitigate | Step 12 checks it on real hardware. This is the project's repeatedly-reinforced Teleport-to-body rule and its failure mode is a partially-obscured signing screen |
|
|
| T-13-106 | Denial of Service (financial) | Routstr's live behaviour untested, so the budget ceiling is only proven in unit tests | medium | mitigate | Step 10 either exercises a real paid request and a real exhaustion stop, or records 13-13's deferral. Silence is not an acceptable outcome |
|
|
| T-13-107 | Repudiation | A validation map marked compliant while rows remain undischarged | medium | mitigate | `nyquist_compliant` is set true only when every row is discharged; otherwise it stays false with the gap named |
|
|
| T-13-108 | Denial of Service (delivery) | The independent music track holding the control/content sign-off hostage, so shippable work cannot be signed off | medium | mitigate | D-13 enforced structurally: this plan's `depends_on` has no path to 13-04/13-07/13-11, and step 7b is record-and-defer rather than a gate. The wave graph, not a comment, is what makes the two tracks separable |
|
|
| T-13-SC | Tampering | npm/pip/cargo installs | high | mitigate | **Zero** packages added; this plan changes no source in either repository. No install task, so no legitimacy checkpoint required |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- All three automated suites green: `cargo test --package archipelago` from `core/`, `vitest run` from `neode-ui/`, and `vitest run` from AIUI's `packages/app`
|
|
- `verify-aiui-deploy.sh` and `aiui-proxy-closed.sh` both exit 0 against archi-dev-box
|
|
- `grep -c TBD 13-VALIDATION.md` returns 0 and no row is `pending`
|
|
- `13-UAT.md` records all fifteen numbered steps plus 7b with observations and the four screenshots
|
|
- This plan's `depends_on` names no music plan, and `13-UAT.md` states which track the sign-off covers
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
The phase is done in the sense the phase itself demands: a typed request in the real embedded
|
|
AIUI on real hardware reaches a real node action and returns a real result; writes stop at a
|
|
dialog the iframe cannot touch; the ceiling holds; the content grids show real data; the
|
|
unauthenticated doors are shut; and all of it is recorded against a node, on desktop and on a
|
|
phone, rather than asserted from a test run.
|
|
|
|
The music library's state is recorded here, not required here. Per D-13 it is an independent
|
|
track: if it landed, 7b records it passing and the phase closes with the library lit; if it did
|
|
not, 7b records the gap and the phase closes anyway on the control and content tracks. Either
|
|
outcome is a valid close — an unrecorded one is not.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-15-SUMMARY.md` when done
|
|
</output>
|