Version. 0.1
Date. 2026-08-01
Status. Read-only inspection findings. Completes change request A's Step 0, items two and three. No draft, no build, no change request.
Author. Claude Code on DUNIN7-M4, one Operator Layer session. Operator: Marvin Percival.
Brief. inspection-briefs/loomworks-b5-operator-layer-inspection-brief-v0_1.md, filed at record fce6ef1.
Companion. loomworks-b29-b27-b5-engine-inspection-findings-v0_1.md — the engine half, complete. Its §4.5 and §8 named this read as owed.
| Fact | Value |
|---|---|
| Operator Layer repository | /Users/dunin7/loomworks |
| Operator Layer HEAD | 99f7f642c56e65b988461c730ec69a3b198e42bf — Merge Item 7a into main: /claim standing→credit fall-through + admin claim_url |
| Branch | main, up to date with origin/main |
| Working tree | Clean. git status --porcelain returns zero lines. Nothing unstaged, nothing untracked. |
| .git/index.lock | Absent in both repositories checked. |
| Record HEAD at time of write | fce6ef196a830d53e254535d8203e3bb33581551 — B-5 Operator Layer inspection brief v0.1 |
| Record working tree | Only the known untracked candidate-seeds/loomworks/loomworks-seed-transfer-v0_1.zip. Left untouched, not cleaned. |
Fences honoured. loomworks-engine was not touched — not read, not git-inspected, not opened. No build, no npm install, no npm run dev, no test-suite run, no Playwright run, no call against :8000 or any other perimeter. Every finding below is a static read of source.
Carried-in position on HEAD — confirmed. The brief §3 recorded 99f7f64 as sourced from past-session retrieval rather than read. It is exact, and the tree is clean. No halt condition fired.
Verdict. The carried-in position is confirmed for Manifestation and Shaping, and incomplete for Rendering.
The four rooms are declared in one file and dispatched from one switch.
The room table — src/lib/room-labels.ts:24-29:
| Room key | label | content discriminator | Renders as |
|---|---|---|---|
| memory | Memory | "memory" | MemoryRoom — real implementation |
| manifestation | Manifestation | "compose" | EmptyRoom — placeholder |
| shaping | Shaping | "shape" | EmptyRoom — placeholder |
| rendering | Rendering | "rendering" | RenderingRoom — real implementation |
The dispatcher — RoomView.tsx:43-67, a four-case switch on room.content. EmptyRoom is defined locally at RoomView.tsx:14-21 — a two-paragraph <div data-testid="room-empty"> carrying a message and a hint, nothing else. Manifestation's case is RoomView.tsx:56-62; Shaping's is RoomView.tsx:63-66.
| Component | Path (under src/app/operator/engagement/[engagement_address]/) | Lines | Real or placeholder |
|---|---|---|---|
| RoomView | RoomView.tsx | 68 | dispatcher |
| MemoryRoom | MemoryRoom.tsx | 645 | real |
| RenderingRoom | RenderingRoom.tsx | 126 | real |
| (Manifestation) | — no file exists — | 0 | EmptyRoom, 7 lines at call site |
| (Shaping) | — no file exists — | 0 | EmptyRoom, 4 lines at call site |
There is no ManifestationRoom.tsx and no ShapingRoom.tsx. B-5 creates its component from nothing; it does not fill in a stub.
The Memory room in detail, as the pattern B-5 follows. MemoryRoom.tsx is the largest component in the surface. Its shape:
held and settled (:407-427), each a usePagedList<MemoryAssertion> keyed on ` ${projectId}:${pageSize}:${refreshNonce} (:421, :426`).HeldCard (:44-273) and SettledCard (:275-331) — both built on the shared WorkCard primitive (./WorkCard), with a badgeRow, title, sub, action, and optional extra.ReadonlySet<string> keyed by assertion id and its own error string: confirm (:429-458), confirm-with-voice (:460-491), dismiss (:493-515), move (:519-553), and comment entry (:372-374).LoadMoreControl structure per section (:564-634).
Correction — the brief's framing of "the one room believed to work." Brief §4 V1 says "Report the Memory room in the most detail. It is the one room believed to work." Two rooms have real implementations, not one. RenderingRoom is live engine data via fetchRendersPage (RenderingRoom.tsx:14, :79-97), with cards, pagination and an honest empty state. It is a materially better structural model for B-5 than the Memory room — it is a single simple list at 126 lines, where the Memory room's 645 lines are dominated by five mutating Operator actions that the Manifestation room's read-only display does not need. Noted under §8.
No halt. The architecture is what the carried-in position describes in the respect that matters — Manifestation is an unimplemented placeholder — so V2 through V10 ask the right questions and were completed.
What it settles for B-5. The build target is a new ManifestationRoom.tsx plus one changed line in RoomView.tsx:56-62, replacing the compose case's EmptyRoom with the component. The room-labels table needs no change: manifestation already has its key, label and discriminator.
Verdict. A hand-written per-domain adapter layer over one thin fetch wrapper. No generated client. The pattern is unambiguous and B-5 follows it without inventing anything.
The wrapper — src/lib/api.ts, 113 lines, one exported function api<T>(path, options) (:70-113).
API_BASE = process.env.NEXT_PUBLIC_API_URL || "/api" (:33). Relative by default and deliberately so: the comment at :16-25 records that the browser never addresses the engine directly, next.config.ts rewrites /api/* server-side, and the previous absolute http://localhost:8000 default was wrong in production.credentials: "include" (:73). No token handling, no Authorization header. On 401 the wrapper redirects window.location.href = "/auth" (:92) unless the caller is already on an auth-flow path (:46-49) or passes bypassUnauthorizedRedirect (:67).ApiError (:51-61) carrying status and detail; thrown for any non-ok response (:110). 204 returns undefined (:97-99).
Generated client — none. package.json contains no openapi, swagger, codegen, orval or graphql dependency. The client layer is 26 hand-written adapter modules in src/lib/api/.
The adapter pattern, read from src/lib/api/memory.ts (189 lines) as the canonical example:
:1-18).MemoryAssertion (:43-79), camelCase, documented field by field.interface Wire*, isolated to the file (:87-127).projectActor (:129-132), projectAssertion (:134-152) — mapping wire → Operator vocabulary.PagedResult<T> via toPagedResult (:178-189).
Loading, error and empty in the fetch layer. The fetch layer itself represents none of them — it returns a promise and throws ApiError. All three live one level up, in usePagedList (§7).
What it settles for B-5. A new src/lib/api/manifestations.ts adapter is required and its shape is fully determined: wire-boundary comment, Manifestation Operator-vocab types, Wire snake_case interfaces, projection functions, fetch functions. It must be added to WIRE_BOUNDARY_FILES in tests/components/vocabulary-wall.test.ts:43-80 — see §8.
memory-status is already consumedVerdict. It is not called anywhere. Plainly: no hits.
Swept src/ and tests/ for all four spellings the brief named — memory-status, memory_status, memoryStatus, MemoryStatus. Zero occurrences in either tree.
What it settles for B-5. The staleness wire is entirely unbuilt on the client side. The engine half established that GET /engagements/{id}/memory-status returns both halves of the state contract in one call, so no engine work is implicated — but the adapter function, its types, and its consumption in the room are all new. This is the one place the engine half's "wire, not a build" finding does not shrink the Operator Layer work: the wire exists, and nothing is plugged into it.
Verdict. None of the four routes is called from the Operator Layer. There is no Manifestation adapter at all.
Swept src/ and tests/ for manifestations — zero occurrences. Swept case-insensitively for manifestation; every hit is one of three non-API kinds:
| Kind | Anchors |
|---|---|
| The room label and its discriminator | src/lib/room-labels.ts:26 |
| Spend-surface labels (a different feature — per-room spend) | src/lib/strings.ts:372, :585, :588; src/lib/api/me-spend.ts:14, :19, :28; src/app/settings/components/SpendPausePreferenceSection.tsx:12-13 |
| Test fixtures and the vocabulary-wall test's own term list | tests/components/vocabulary-wall.test.ts:29, :82, :90; tests/components/engagement/WorkspacePane.test.tsx:37, :63; others |
There is no src/lib/api/manifestations.ts. None of POST …/manifestations/preview, POST …/manifestations/derive, GET …/manifestations, or GET …/manifestations/{id} has a client-side caller.
On the version query parameter. The question of whether the client layer would surface or hide the pin cannot arise yet, because no client layer for these routes exists. What can be settled is that the adapter pattern imposes no obstacle: fetchAssertionsPage already builds a URLSearchParams and conditionally sets keys (memory.ts:183-184), which is exactly the mechanism a version pin needs. The walk audit's failure mode — reading an OpenAPI document for a path segment and missing a query parameter — is a reading failure, not a client-architecture one, and does not reproduce here.
What it settles for B-5. All four routes are new client work. The adapter is greenfield, so the version pin can be surfaced deliberately from the first line rather than retrofitted.
Verdict. No fetch-an-assertion-by-id-and-version path exists on the client side, and nothing does N-at-a-time fetching. The Memory room never needs to fan out, because content arrives inline in its list response.
Swept src/ for at-version, atVersion, and /history — zero occurrences. Neither the assertion-side version route nor the assertion history route the engine half read at api/routers/assertions.py:774-799 and :748 has any client caller.
How the Memory room gets assertion content. It does not fetch it separately. WireAssertion.content is a field on the list response (memory.ts:99), projected straight through to MemoryAssertion.content (:136) and rendered as the card's title (MemoryRoom.tsx:151, :315). One list call per page yields fifteen assertions with their text already in hand (MEMORY_PAGE_SIZE = 15, MemoryRoom.tsx:37).
No N-at-a-time fetching pattern exists anywhere in the surface to copy. Every list surface reads whole objects from a paged endpoint.
What it settles for B-5, and the decision it touches. This is direct evidence bearing on §5 design question 3 — content fan-out or content inlining, and it is unfavourable to fan-out: the fan-out approach would be the first N-at-a-time fetch in the Operator Layer, with no established pattern, no batching helper, and no by-id-and-version adapter function to build on. The engine half found organized_groups carries references only (identifier and version), so a fan-out room must fetch each assertion's content itself. Reported as evidence; not resolved here — the choice is the Operator's.
Verdict. Three of the four states are represented and correctly distinguished. Empty and not-yet-loaded are NOT conflated. The fourth state — structurally null — has no representation anywhere, and no list-shaped surface has ever needed it.
The state machine is not in the room components. It is in src/hooks/usePagedList.ts (158 lines), which both real rooms consume.
The four flags — UsePagedListResult<T> (:38-50): loading ("first page in flight"), loadingMore ("a load-more in flight"), error, and items/hasMore/totalCount.
How each state is reached:
| State | Representation | Anchor |
|---|---|---|
| Not yet fetched | loading === true | useState(true) at :62 — the initial value is true, before any effect runs. Re-asserted at :88 on every reset. |
| Fetched, genuinely empty | loading === false, error === false, items.length === 0 | :99 sets items from the resolved response; :108 clears loading in .finally |
| Fetch failed | error === true | :104-106, the .catch |
| Fetched, structurally null | no representation | — |
Empty and not-yet-loaded are not conflated, and the initial loading = true at :62 is the reason. A room mounting for the first time reports loading, not empty. Both rooms then gate on it before ever testing emptiness — MemoryRoom.tsx:555-560 returns loading, then error, before reaching its per-section items.length === 0 checks at :566 and :613; RenderingRoom.tsx:99-111 does the same in a single ordered sequence. **This satisfies B-5's rule — a room cannot say nothing here without having looked — for the list case.**
Three structural observations B-5 inherits, each a real property of the existing hook:
loadMore's catch sets the same error flag (:136) that first-page failure sets. Both rooms test error before rendering items (MemoryRoom.tsx:558, RenderingRoom.tsx:102), so a failed second page replaces an already-rendered list with roomLoadError and the loaded items vanish from view. There is no partial-failure state.:90-92 sets items to [], hasMore false and totalCount 0 at the top of the effect, with setLoading(true) at :88 — so the transient is covered by the loading gate rather than showing as empty. The ordering is correct; it depends on that gate remaining ahead of the emptiness test in any new room.held.loading || settled.loading (:555) and held.error || settled.error (:558) mean either list's state governs the whole room. B-5's room reads a Manifestation and — if it wires staleness — a memory-status; the same either/or choice will present itself.
The fourth state is the gap, and it is a genuine one. usePagedList is a list hook. The Manifestation room's central value is a single object with a nullable field: the engine half established organized_groups is null on pre-Phase-19 Manifestations, where only counts remain. Nothing in the Operator Layer models "fetched successfully, and the value is legitimately null for a structural reason" as distinct from "fetched, and empty." No existing hook serves a single-object read at all — every consumer of the pattern is a paged list.
What it settles for B-5, and the decision it touches. This is the brief's most important verification and the answer is split: the rule is already honoured by the existing pattern, and the pattern itself does not fit B-5's shape. B-5 needs either a single-object fetch state or an explicit fourth state layered on. This is direct evidence bearing on §5 design question 4 — the empty state for pre-Phase-19 Manifestations — because it establishes there is no existing representation to fall back on. Reported as evidence; not resolved here.
Verdict. Every operator-visible string in the room surfaces is in plain terms. Zero offenders. But the wall test that is supposed to enforce the room-label restriction is case-sensitive, and therefore does not enforce it in the capitalized forms the Operator actually sees.
RoomView.tsx and the room components
All strings are centralised in src/lib/strings.ts under the SURFACE export; not one is written inline in a room component.
Dispatcher — RoomView.tsx (the two placeholder rooms' entire visible content):
| String | Value | Anchor | Plain? |
|---|---|---|---|
| composeEmpty | "Nothing organized into a picture yet." | strings.ts:1077 | ✅ |
| composeEmptyHint | "Committed memory is the raw material here." | strings.ts:1078 | ✅ |
| shapeEmpty | "No shapes waiting on you yet." | strings.ts:1079 | ✅ |
| shapeEmptyHint | "Shapes appear here once there's settled memory to shape." | strings.ts:1080 | ✅ |
Shared by both real rooms:
| String | Value | Anchor | Plain? |
|---|---|---|---|
| loading | "Loading…" | strings.ts:1084 | ✅ |
| roomLoadError | "Couldn't load this room. Try again in a moment." | strings.ts:1083 | ✅ |
Memory room — strings.ts:915-1006: heldSection "Held for you (n)" (:916), settledSection "Settled (n)" (:917), heldEmpty "Nothing held right now — {Companion} isn't waiting on you." (:918-919), settledEmpty "Nothing settled into the record yet." (:920), heldSub "Held, awaiting your commit" (:921), settledSub "In the record" (:922), notedBy "noted by {name}" (:923), kindLabel "Definition"/"Rule" (:924-925), forceLabel "Required"/"Recommended"/"" (:926-931), confirmAction "Confirm" (:932), confirming "Confirming…" (:933), confirmError (:934), dismissAction "Dismiss" (:935), dismissing "Dismissing…" (:936), dismissError (:937), sourceControlLabel "Replay"/"View source" (:945-946), confirmKeepVoiceAction "Confirm + Keep Voice" (:949), confirmingWithVoice "Saving…" (:950), confirmWithVoiceError (:951-952), replayPendingAction "Replay" (:965), operatorCommentPlaceholder (:983, used as both placeholder and aria-label at MemoryRoom.tsx:258-259), operatorCommentRequiredHint (:984-985), moveAction "Move to…" (:989), moving "Moving…" (:990), moveErrorGeneric (:991), moveErrorForbidden (:992-993).
Inline badge labels, the only strings not in strings.ts: "Held" (MemoryRoom.tsx:142), "In record" (:307), "Render" (RenderingRoom.tsx:50). All three are plain English display words, not wire tokens.
Rendering room — renderSub "Produced · ready to view" (strings.ts:1071), renderViewAction "View" (:1072), producedBy "produced by {name}" (:1073), renderingEmpty "No finished outputs yet." (:1074).
Room names — "Memory", "Manifestation", "Shaping", "Rendering" at room-labels.ts:25-28; "Companion" at strings.ts:913.
Result: no offenders. Every string uses plain terms. No engine shorthand (assertion_id, normative_force, grammar_element, shape_event, render_event) reaches any operator-visible string. The engine's grammar_element is displayed as "Definition"/"Rule"; normative_force as "Required"/"Recommended". strings.ts:999-1004 records a deliberate case where the wire value is deliberately re-mapped so the surface shows the same words the rest of the room uses.
tests/components/vocabulary-wall.test.ts scans src/ for a list of forbidden terms (:22-37), with two exception mechanisms: WIRE_BOUNDARY_FILES (:43-80, 13 adapter files) and a room-label carve-out permitting only "manifestation" and "shaping" and only in lib/room-labels.ts (:89-90).
The matching is case-sensitive. The comparison is line.includes(term) at :128, against lowercase terms at :29 and :30, with no normalisation on either side.
Consequence. The capitalized forms "Manifestation" and "Shaping" — which are precisely the operator-facing forms — pass the wall in every file, not only in room-labels.ts. This is not theoretical: src/lib/strings.ts:372 (roomLabelManifestation: "Manifestation"), :588, and src/app/settings/components/SpendPausePreferenceSection.tsx:12-13 all carry the word outside the sanctioned file and all pass. The carve-out at :89-90 announces a "tight distinction, not a blanket exception," but as implemented it binds only the lowercase spellings — which appear in code as wire tokens and in me-spend.ts:19 as a comparison literal.
This is reported, not fixed — per brief §2 and §8.
What it settles for B-5. Two concrete obligations. First, the new src/lib/api/manifestations.ts adapter must be added to WIRE_BOUNDARY_FILES (vocabulary-wall.test.ts:43-80) or the wall test fails the moment a snake_case wire interface lands — the lowercase manifestation in a route path string is caught. Second, B-5 should not rely on the wall to catch a capitalized leak in a new room component; it will not.
Verdict. The principle is observed rigorously and by explicit design throughout the room surfaces — with exactly two disabled attributes in one deliberate, documented case.
The two occurrences — MemoryRoom.tsx:224 and :241, both disabled={commentBlocksConfirm}, on "Confirm" and "Confirm + Keep Voice" respectively. commentBlocksConfirm is note.requiresOperatorComment && !comment.trim() (:130). The rendering path is CardAction → a <button disabled> styled disabled:cursor-not-allowed disabled:opacity-40 (WorkCard.tsx:136-137) — a greyed-out, non-clickable button.
Whether this is a violation is a genuine judgment call, and the code argues its own case. MemoryRoom.tsx:126-129 records the reasoning: a note recorded via the disable-interpretation toggle has no transcript, so Confirm is blocked until the Operator types a label — "Operator-authority: signal the requirement, don't silently allow past it." The action is available; it is the note that is incomplete, and the accompanying hint (:263-267) states what is missing. Removing the button entirely would leave the Operator with no visible path forward. Reported with anchors as the brief requires; not resolved here, since it is a seed-interpretation question rather than a read.
Everywhere else the principle is honoured by omission, not disablement — nine sites, each rendering null rather than a dead control:
| Control | Hidden when | Anchor |
|---|---|---|
| "View source" (held) | note has no source file | MemoryRoom.tsx:160-169 |
| "Replay" (pending blob) | no in-memory recording | :177-186 |
| "Move to…" | Operator has no other engagement | :190-203 (gate canMove, :403) |
| "Confirm + Keep Voice" | no parallel recording exists | :232-245 |
| Comment input | note doesn't require one | :252-269 |
| "View source" (settled) | note has no source file | :317-328 |
| Engagement picker | no move in progress | :635-642 |
| "View" (render) | output isn't viewable inline | RenderingRoom.tsx:60-66 |
| LoadMoreControl | no further pages | LoadMoreControl via hasMore |
Three of these carry explicit only-show-what-is-available comments naming the principle (MemoryRoom.tsx:73-74, :82-83, :187-189), as does RenderingRoom.tsx:7-9 ("no dead 'Download' button") and MemoryRoom.tsx:10-11. No greyed-out options, no aria-disabled, no opacity- dimming of unavailable features in any room component.
What it settles for B-5. The established idiom is conditional rendering to null, with a comment naming the principle. A Manifestation room with no derivation yet shows an honest empty state, not a greyed-out "Derive" button.
Verdict. Rooms are not routes. All four are client state within one engagement route, so the Manifestation room has no URL today and B-5 adds no route.
The URL shape — /operator/engagement/[engagement_address], one dynamic segment. The route file is src/app/operator/engagement/[engagement_address]/page.tsx (27 lines), an async server component that awaits params, decodes the segment, and renders <InEngagementSurface address={…} /> (:20-26). The address is tolerant of four forms — 0, Personal, E####, or a long-random identifier — resolved against the substrate because there is no per-engagement detail endpoint (page.tsx:9-12).
Room selection is useState, not routing. InEngagementSurface.tsx:66 — const [activeRoom, setActiveRoom] = useState<RoomKey>(ROOMS[0].key). The initial room is ROOMS[0], which is Memory. activeRoom is passed to the desktop workspace pane (:342) and the mobile surface (:352). There is no route, no path segment, no query parameter and no hash for a room, so a room is not linkable, not bookmarkable, and not restored on reload — the Operator always lands in Memory.
Route count — 19 page.tsx files under src/app.
Prerendering — not statically determinable, and reported as unread. No route in src/app declares export const dynamic, export const revalidate, force-dynamic, or generateStaticParams — the sweep returns nothing. What each route resolves to is decided by the Next.js build, and reading the build output is a run, which the fences forbid. What can be said from source: this route takes a dynamic segment with no generateStaticParams, and its entire subtree below page.tsx is "use client" (InEngagementSurface.tsx:1, RoomView.tsx:1, MemoryRoom.tsx:1, RenderingRoom.tsx:1), with all data fetched from effects in the browser.
What it settles for B-5. No routing work. The change is confined to the compose case in RoomView.tsx. If B-5 wants the Manifestation room to be linkable — for example so a staleness notice elsewhere can deep-link to it — that is new capability affecting the shared surface, not a Manifestation-room-local change, and it is not in the current architecture.
Verdict. Vitest with Testing Library and jsdom, one test file per component, co-located under tests/components/engagement/. The room-test pattern is well established and directly copyable.
| Fact | Value | Anchor |
|---|---|---|
| Unit framework | Vitest 4.1.5, "test": "vitest run" | package.json:10, :38 |
| DOM | jsdom 29.1.1 | package.json:35 |
| Component API | @testing-library/react | RenderingRoom.test.tsx:6 |
| E2E | Playwright 1.59.1, "test:e2e": "playwright test" | package.json:12, :23 |
| Config | vitest.config.mts, playwright.config.ts | repo root |
| Test files | 105 *.test.ts(x) | tests/ |
| Playwright specs | 1 | tests/ |
| Test count | 609 it( blocks at line start; 621 including nested/inline forms | static grep over tests/ |
Room tests live at tests/components/engagement/ — 17 files, including MemoryRoom.test.tsx, RenderingRoom.test.tsx, WorkspacePane.test.tsx, MobileSurface.test.tsx. There is no RoomView.test.tsx.
The established room-test pattern, read from RenderingRoom.test.tsx:1-60:
:1-5).vi.mock of the adapter module only — never the api wrapper, never fetch (:14-20).Partial<T> override parameter — output(over = {}) (:23-37).page(items, hasMore, totalCount, nextCursor) helper returning a PagedResult<T> (:39-46), so pagination states are cheap to express.afterEach(() => vi.clearAllMocks()) (:48).screen.getByTestId(…) against the component's data-testid, inside waitFor (:53-58).
Empty-state coverage already exists and is the closest precedent for B-5's rule: MemoryRoom.test.tsx:511 — "shows honest empty states when nothing is held or settled" — with :524 asserting no dead action buttons and no stray load-more control in an empty room.
What it settles for B-5. A tests/components/engagement/ManifestationRoom.test.tsx follows the six-step pattern exactly, mocking a new @/lib/api/manifestations. The data-testid convention (memory-held-card, rendering-card, room-empty) implies a manifestation-* testid on whatever card the room renders.
1. "The one room believed to work."
Prior — this brief §4 V1: "Report the Memory room in the most detail. It is the one room believed to work, and it is the pattern B-5 follows."
Current — two rooms have real implementations. RenderingRoom.tsx (126 lines) is a complete implementation on live engine data via fetchRendersPage (:14, :79-97), with cards, keyset pagination, an honest empty state, and its own test file. The brief's instruction to detail the Memory room was followed, but the claim that it is the only working room is overturned — and the correction matters for B-5, because the Rendering room is the closer structural model: a single read-only list, not a five-action mutation surface.
2. "Manifestation and Shaping are hardcoded placeholder EmptyRoom components."
Prior — the walk audit, carried in at brief §3 as a claim to confirm or overturn, source unverified.
Current — confirmed exactly, and now anchored. RoomView.tsx:56-62 and :63-66 render EmptyRoom, defined at RoomView.tsx:14-21, driven by the compose and shape discriminators at room-labels.ts:26-27. No ManifestationRoom.tsx or ShapingRoom.tsx exists. The walk audit was right.
3. Operator Layer HEAD 99f7f64.
Prior — brief §3: sourced from past-session retrieval, not read; confirm.
Current — confirmed exact, 99f7f642c56e65b988461c730ec69a3b198e42bf, main, clean tree. No correction needed; recording it as verified rather than inherited.
4. The vocabulary wall's room-label carve-out.
Prior — vocabulary-wall.test.ts:82-88: "They are permitted ONLY in their single source file, and ONLY those two words … This is a tight distinction, not a blanket exception."
Current — the restriction does not hold for the capitalized forms. Matching is case-sensitive (:128, terms at :29-30), so "Manifestation" and "Shaping" pass in every file. Live instances outside the sanctioned file: strings.ts:372, :588; SpendPausePreferenceSection.tsx:12-13. The comment describes an intent the implementation enforces only for the lowercase spellings.
Per brief §5, evidence found is reported and named against its decision, without resolving any of them.
| Decision | Evidence found | Where |
|---|---|---|
| 1 — Which staleness measure the room displays | No evidence. memory-status is not consumed anywhere in the Operator Layer, so the surface has taken no position on either measure. | §4 |
| 2 — Per-object Manifestation history | The client has no caller for the assertion-side /history route either, so there is no existing history-display pattern in the surface to extend. | §6 |
| 3 — Content fan-out or inlining | Unfavourable to fan-out: no by-id-and-version adapter exists, and no N-at-a-time fetching pattern exists anywhere in the Operator Layer. Every list surface reads whole objects with content inline. Fan-out would be the first of its kind. | §6 |
| 4 — The empty state for pre-Phase-19 Manifestations | No representation exists for "fetched, and structurally null." usePagedList models three states, all list-shaped; there is no single-object fetch hook at all. | §7 |
| # | What is unread | Why | What would settle it |
|---|---|---|---|
| 1 | Whether the 19 routes are prerendered, and how many of each kind. | Requires reading Next.js build output. No route declares dynamic/revalidate/generateStaticParams, so it is decided at build time. A build is forbidden by brief §2. | npm run build and read the route table. Partial answer from source in §10. |
| 2 | The passing test count. | 609 it( blocks counted statically; how many pass, skip or fail needs a suite run. Fenced by brief §2. | npm run test. |
| 3 | Whether the vocabulary wall currently passes. | The case-sensitivity weakness is read from source, but whether the suite is green at 99f7f64 needs a run. | npm run test -- vocabulary-wall. |
| 4 | Whether the two disabled attributes at MemoryRoom.tsx:224/:241 are a seed violation. | A seed-interpretation judgment, not a read. Both the principle and the code's stated counter-reasoning are recorded in §9. | An Operator ruling. |
| 5 | What the engine's Manifestation response looks like in practice. | Taken as given from the engine-half findings; verifying it directly would mean reading loomworks-engine, forbidden by brief §2, or a perimeter call. | The engine half, already complete. |
| 6 | Whether next.config.ts's /api rewrite is correct for the Manifestation routes specifically. | Read the rewrite's existence via api.ts:16-25's comment; did not read next.config.ts itself, as no verification asked for it. | A read of next.config.ts — inside the fences, simply not asked. |
loomworks-engine in no way whatsoever — not read, not git-inspected, not opened. The engine belongs to B-29./Users/dunin7/loomworks. Working tree clean at start and at finish; the only write anywhere is this document and its commit in loomworks-record.npm install, no build, no dev server, no Vitest, no Playwright, no perimeter call.disabled attributes. Both are B-5 build work or an Operator ruling.candidate-seeds/loomworks/loomworks-seed-transfer-v0_1.zip is known and stays.
DUNIN7 — Done In Seven LLC — Miami, Florida
Loomworks — B-5 Operator Layer inspection findings — v0.1 — 2026-08-01
Read-only. One Operator Layer session. Ten verifications, all completed. Manifestation is greenfield; the state contract lives in usePagedList; the vocabulary wall is case-sensitive.