Files
archy/.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-15-PLAN.md
T

22 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
13-aiui-functional-conversational-node-control-and-content-surf 15 execute 8
13-06
13-09
13-14
.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
false
AIUI-06
truths artifacts key_links
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)
path provides
.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-UAT.md The on-device acceptance record: what was exercised, on what hardware, at what viewport, with what result
path provides contains
.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md Per-Task Verification Map with Task ID / Plan / Wave / Threat Ref filled and every row statused nyquist_compliant
from to via pattern
.planning/phases/13-.../13-VALIDATION.md .planning/phases/13-.../13-UAT.md each manual-only row cites the UAT section that discharged it 13-UAT
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.

<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>

@.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.

Task 1: Run everything, then fill the validation map from what actually ran .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md - `.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. 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. cd core && CARGO_INCREMENTAL=0 cargo test --package archipelago 2>&1 | tail -20 cd neode-ui && npx vitest run 2>&1 | tail -20 cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run 2>&1 | tail -20 grep -c 'TBD' .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-VALIDATION.md <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> Every validation row is owned, statused from a real run, and traceable to the plan and threat that discharged it.
Task 2: The real embedded iframe, on real hardware, desktop and mobile .planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-UAT.md - `.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. 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. 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 ""` and `bash tests/production-quality/aiui-proxy-closed.sh `. 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. <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> 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.

<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>
- 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

<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>

Create `.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-15-SUMMARY.md` when done