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 |
|
|
false |
|
|
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.mdDeliberately 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.
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 archipelagoexits 0cd neode-ui && npx vitest runexits 0cd /home/archipelago/Projects/AIUI/packages/app && npx vitest runexits 0grep -c 'TBD' 13-VALIDATION.mdreturns 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
curlstatus 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_compliantistrue, or it isfalsewith 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.
Desktop, in the real embedded iframe (not dev:mock):
- Open neode-ui's Chat view. AIUI renders — not a black page.
- With all categories closed, ask "how much space is left". The assistant reports it cannot, naming the category to open. Nothing is fabricated.
- Open the
systemcategory. Ask again. A real free-space figure comes back matchingsystem.disk-status. - Ask a settings question, then make an allowlisted settings change conversationally. Confirm the dialog appears and the change lands.
- 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.
- 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.
- 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.mdexists, 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.m4afrom 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 in13-UAT.mdand 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. - 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.
- 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.
- 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 andbash tests/production-quality/aiui-proxy-closed.sh <node>exits 0, both recorded with their output13-UAT.mdexists 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.mdstates 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.mdcross-references the four Manual-Only rows in13-VALIDATION.mdand 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> |
<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