🧠: Review of jgwill/Miadi 71387fae^..4ace34b8 (six commits, all #769)
Model class: Opus (claude-opus-5-5). Read-only review at 4ace34b8.
Scope and method#
Commits read: 71387fae (notebooks), f69a6a17 (prototypes), 43d99302 (tracing in the manifest), 45d5ad25 (@miadi/tracing over @miadi/tracing-langfuse), 82a282dd (addresses and doors), 4ace34b8 (sessions). 67 files, +7034/-46.
Probes run, all read-only:
- The new parsers on built
dist/withnode --input-type=module -e(no files written). esbuild --platform=browser --outfile=/dev/nullon@miadi/reference-uiand each@miadi/inquiry-weave/referencesmodule.- The schema readers over the 150 real
episode.yamlfiles in the chronicle (read, never written), plusgit config --get remote.origin.urlthere. - Field shapes of
/src/_sessiondata/data/terminal_bindings.jsonl(942 lines). Only the shapes were extracted, no argv or cwd values. @langfuse/client5.13.1 type definitions.- The medicine-wheel
PATCH /api/nodes/[id]handler. tsc --noEmit -p tsconfig.json --incremental falseon the app.
Writes beyond this report: npm test rebuilt each package's gitignored dist/ (git status is clean for the five packages afterwards). Test logs and the tsc log are in this scratchpad (tests/*.log, tsc-app.log).
Verified as holding#
- The editing address (
editor_url) and the provider page (ui_url) are never written toepisode.yamlor to a commit body. They live on the wheel node.GET .../prototypesandGET .../tracingadd them only whenmayWrite(request)is true. mayWritehas no loopback bypass for write (lib/api-gate.ts:297). It needs a writer token or a person with the write tier.- No app route proxies wheel node metadata to readers. Only
artefactRef.tsreads nodes, and it returnsnameandepisode_pathonly. - The wheel's PATCH merges
metadata(medicine-wheelapp/api/nodes/[id]/route.ts:74-98), so theeditor_url,ui_urlandcommitPATCHes do not wipe other node metadata. - The schema modules import no fs and no network.
- App type-check: 69 error lines, none in a file these commits touch.
- Sessions: the real binding line has one timestamp format and
episodeas{id, source}with the full folder name. On real data, no session ends up shown as live after it ended.
Test results (verbatim)#
episodic-memory-schema ℹ tests 167 ℹ pass 167 ℹ fail 0 EXIT 0
tracing ℹ tests 3 ℹ pass 3 ℹ fail 0 EXIT 0
tracing-langfuse ℹ tests 3 ℹ pass 3 ℹ fail 0 EXIT 0
inquiry-weave ℹ tests 260 ℹ pass 260 ℹ fail 0 EXIT 0
reference-ui ℹ tests 19 ℹ pass 19 ℹ fail 0 EXIT 0
All pass. F1, F2, F3 and F5 below pass these suites.
Findings#
F1. @miadi/inquiry-weave/references and @miadi/reference-ui no longer bundle for a browser#
- Where:
packages/inquiry-weave/src/rise-dsl.ts:21-22,references.ts:12,text-references.ts:39. - Defect:
rise-dsl.tsimportsnode:fsandnode:path, andreferences.tsre-exports it.text-references.tsimports it too, andmarkdown-directives.tsimportstext-references.ts. So the whole./referencessubpath, which was browser-clean before 82a282dd, now needs Node. - Scenario:
esbuild packages/reference-ui/dist/index.js --bundle --platform=browserfails withCould not resolve "node:fs"atinquiry-weave/dist/rise-dsl.js:20.- Per module: chronicle-dsl ok, foundation-dsl ok, artefact-dsl ok, text-references FAIL, markdown-directives FAIL, rise-dsl FAIL.
- In the app,
components/ceremony/turn-text.tsx:17-19is a"use client"component that imports both packages. Next 16's webpack client config falls back forprocessonly (next/dist/build/webpack-config.js:608-610). - No Next build has compiled these commits yet.
.next/BUILD_IDis dated 14:59 and 82a282dd landed at 21:25. Turbopack is untested here.
- Severity: blocks publish (
@miadi/reference-ui0.2.0 is sold as "any host", and inquiry-weave 0.17.0./referencesis what it uses). - Smallest fix: move
KNOWN_RISE_WORKSPACES,riseWorkspacePath,riseLocalPathand the two node imports out ofrise-dsl.tsinto a server-only module exported from the package root. They have no caller anyway (F21). Then rerun the esbuild line above.
F2. A versioned prompt address never resolves#
- Where:
packages/inquiry-weave/src/artefact-dsl.ts:101,:133-135. Consumed atapp/chronicle/lib/artefactRef.ts:59,:95. - Defect:
parseArtefactUrikeeps@<version>inid, andartefactNodeIdbuildstracing:<domain>/prompt/<name>@<v>. The door writes the node astracing:<domain>/prompt/<name>, with no version (tracing.ts:250viatracingNodeId). - Scenario (proven):
miadi-prompt:ceremony/miadi-ep120-fork@2givesartefactNodeId = tracing:ceremony/prompt/miadi-ep120-fork@2, but the door's node id istracing:ceremony/prompt/miadi-ep120-fork. The wheel answers 404, the chip renders unresolved, and/api/chronicle/openanswers 404 "Artefact not found". - Severity: fix before publish.
- Smallest fix: for
kind === "tracing",artefactNodeIdreturnstracingNodeId(parsed.tracing.domain, parsed.tracing.resource, parsed.tracing.id). The version stays for display.
F3. The Langfuse provider summarises a trace under its root span's name, not the trace name#
- Where:
packages/tracing-langfuse/src/index.ts:106,:92. - Defect:
fields: "core,basic"does not includetraceName. In SDK 5.13.1 that field is in thetrace_contextgroup (@langfuse/core/dist/index.d.ts:10591: "trace_context- tags, release, traceName"). So:92always falls back toroot.name. - Scenario: a trace named "Miadi Episode 120 | branching" through
updateTrace, whose root span is "handle-turn", is described asname: "handle-turn". - Severity: fix before publish.
- Smallest fix:
OBSERVATION_FIELDS = "core,basic,trace_context". This also bringstagsfor F4.
F4. listTraces filters by observation name, and ignores tags#
- Where:
packages/tracing-langfuse/src/index.ts:196,:205,:204. - Defect:
request.namefilters observations by their own name, not by trace name.const wanted = options.tags?.length ? traces : tracesis a dead branch, so tags are silently ignored. Each summary is built from whichever observations the one page returned. - Scenario:
listTraces("ceremony", { tags: ["ep120"] })returns the newest traces whatever their tags.listTraces("ceremony", { name: "Miadi Episode 120 | branching" })returns nothing unless a span carries that name.- The README says the package answers tags. It never does.
- Severity: fix before publish (or remove the options from the contract).
- Smallest fix: pass the structured
filterwith thetraceNameandtagscolumns the SDK documents, or dropnameandtagsfromTracingProvider.listTracesuntil they work.
F5. The manifest reader ignores the miadi-trace: and miadi-prompt: aliases in references:#
- Where:
packages/episodic-memory-schema/src/episode-tracing.ts:120-127,:237-238. - Defect:
parseTracingRefstrips onlymiadi-tracing:andtracing:.artefact-dsl.ts:96accepts both aliases. - Scenario (proven):
readEpisodeTracing({references: ["miadi-prompt:ceremony/foo@2", "miadi-trace:eng/abc123"]})returns[]. The same strings in prose render as tracing chips, but the room's "Traces and prompts" card does not list them. - Severity: fix before publish (the schema package is where the aliases need to be read).
- Smallest fix: rewrite the two aliases to a key in
parseTracingRefthe wayartefact-dsl.ts:96does.
F6. Resolve and open fail open when the audience lookup throws#
- Where:
app/api/chronicle/resolve/route.ts:84-89,app/api/chronicle/open/route.ts:86-92,app/chronicle/lib/artefactRef.ts:61,:98. - Defect: the catch sets
visible = null, andartefactRefreadsnullas "no restriction". In the same route, the chronicle path fails closed (resolve/route.ts:105-107leavesentriesempty), andreferences.tsx:236uses an empty Set. - Scenario:
getVisibleManifestForthrows (for example,buildChronicleCatalogcannot readMIADI_CHRONICLE_ROOT) while the wheel answers. An anonymousGET /api/chronicle/resolve?uri=miadi-notebook:<uuid held by a community episode>then returns the community episode's name, number and the notebook title, and/openredirects. - Severity: fix before publish (app code that ships with the :3335 rebuild).
- Smallest fix:
visible = new Set()in both catches.
F7. The "pure, host-neutral" schema exports gaia's checkout paths#
- Where:
packages/episodic-memory-schema/src/episode-rispec.ts:64-70,:96-104, exported atindex.ts:244. - Defect:
HOST_PATH_REPOSITORIEShard-codes/src/Miadi/,/a/src/Miadi/,/a/src/Miadi-18/,/etc/claude-code/and/srv/miadi/episodes/.parseRiseRefapplies it on every host. Once published, it is public API. - Scenario: on ilex or any host with other paths, a manifest host path is not read. On every host,
/a/src/Miadi-18/xis read asjgwill/Miadi, whatever that path holds there. - Severity: fix before publish (removing it after publish is a breaking change).
- Smallest fix:
parseRiseRef(input, { hostPaths = [] })in the schema. inquiry-weave, the host layer, passes the map.
F8. @miadi/tracing caches a failed project lookup forever#
- Where:
packages/tracing/src/index.ts:165-173. - Defect:
provider.project(domain).catch(() => null)is stored in the map, so one failure is cached for the life of the process. - Scenario: Langfuse answers 503 on the first
uiUrlcall. Every lateruiUrlfor that domain returnsnulluntil the process restarts. - Severity: fix before publish (one line).
- Smallest fix: cache only a resolved non-null project, and delete the map entry on failure or null.
F9. A trace summary reads one page of at most 100 observations#
- Where:
packages/tracing-langfuse/src/index.ts:186,:95,:102. - Defect:
observations: own.lengthis capped at 100. The root and the earliest start come from that page only.TraceSummary.observationsis documented as "how many observations the trace holds" (tracing/src/index.ts:75). - Scenario: a trace with 350 observations reports
observations: 100. Its timestamp and fallback name come from whichever 100 the page returned. - Severity: later (fix the doc before publish).
- Smallest fix: fetch the root with
isRootObservation: truefor name and timestamp, and either page through the cursor or rename the fieldobservations_read.
F10. Visibility is decided by the first episode that created the node#
- Where:
app/chronicle/lib/artefactRef.ts:60-61,:97-98. Nodes are written withepisode_pathatnotebook.ts:268,prototype.ts:266,tracing.ts:294,rispec.ts:205, and never updated. - Defect: an artefact held by several episodes is judged only by its first holder. The tracing node's
addressalso pins the first attach's prompt version (tracing.ts:293). - Scenario: a notebook first attached to a community episode, then to public ep550, renders unresolved for visitors in ep550's own text, and its open door answers 404.
- Severity: later.
- Smallest fix: treat the artefact as visible when any episode with an edge to the node is visible, or when the referring page's episode is.
F11. rise://jgwill/episodes/... and the default branch point at GitHub pages that do not exist#
- Where:
packages/inquiry-weave/src/rise-dsl.ts:97-101,:107,rispec.ts:204,app/chronicle/lib/artefactRef.ts:111-117. - Defect:
jgwill/episodesisssh://mia@gaia:/srv/git/jgwill/episodes.git.gh repo view jgwill/episodesanswers "Could not resolve to a Repository". Separately,defaultBranch = "main"404s for any repository onmaster. - Scenario: a vessel spec outside
miadi-chronicle/<folder>/opens a GitHub 404. Every vessel spec node gets a deadgithub_url.riseInfoOfstill saysexists: true. - Severity: later.
- Smallest fix: use
blob/HEAD/when there is no commit, and omitgithub_urlforjgwill/episodes.
F12. The spec node's commit and GitHub link drift apart#
- Where:
packages/inquiry-weave/src/rispec.ts:181,:204. - Defect: the PATCH sets
metadata.committo the newest episode's commit and leavesgithub_urlat the first commit. - Scenario: ep552 holds the spec at
27cf18f, then ep560 atabc123. The node sayscommit: abc123withgithub_url: .../blob/27cf18f/.... - Severity: later.
- Smallest fix: patch
github_urlin the same call, or keep the commit only in the manifest.
F13. Langfuse prompts in folders cannot be held#
- Where:
packages/episodic-memory-schema/src/episode-tracing.ts:76,:127. - Defect: the id pattern excludes
/. The SDK documents prompt names such asfolder/subfolder/prompt-name(@langfuse/corePrompts.get). - Scenario: holding the prompt
miadi/ep120-forkgetsinvalid_tracingfrom the door, andparseArtefactUri("miadi-prompt:ceremony/miadi/ep120-fork")returns null (proven). - Severity: later.
- Smallest fix: allow
/in prompt ids and encode it in the node id and the reference.
F14. Nothing stops an editing address from being written as the published url#
- Where:
packages/inquiry-weave/src/prototype.ts:60-64,packages/episodic-memory-schema/src/episode-prototype.ts:77-87. - Defect: any https URL is accepted as the published address. The reach rule depends on the person typing each URL into the right field.
- Scenario: a writer pastes
https://v0.app/chat/abcinto "url".episode.yamlthen serves the edit link to every reader. - Severity: later.
- Smallest fix: refuse
url === editor, and refuse hosts known to be editors (v0.app/chat,v0.dev/chat).
F15. A notebook's made_by publishes operator session names that the sessions door keeps private#
- Where:
packages/episodic-memory-schema/src/episode-notebook.ts:26(example and fixtures), againstapp/chronicle/lib/episodeSessions.ts:4-5. - Defect:
made_bycarries a tmux name and a session id, and is served by the publicGET .../notebooksand the room. A5 treats a session's name and pane as operator-only. - Scenario: a visitor reads "miadisabelle/deepdiver (tmux miadi-deepdiver, session 8a83dab6)" in a public episode.
- Severity: later.
- Smallest fix: decide the rule once. If session names are operator-only, keep the tool and commit in
made_byand put the session on the node.
F16. A bare UUID in references: is read as a held notebook#
- Where:
packages/episodic-memory-schema/src/episode-notebook.ts:101,:226-231. - Defect: the
""prefix accepts any bare uuid. - Scenario (proven):
references: ["d64a2fdf-…"], a review id written bare, comes back as a notebook withdeclared: "references". Today 0 of 308 reference strings across 150 manifests are bare uuids. - Severity: later.
- Smallest fix: in the references loop, accept only the notebook URL and
miadi-notebook:.
F17. Two parsers read miadi-prototype: differently#
- Where:
packages/episodic-memory-schema/src/episode-prototype.ts:72againstpackages/inquiry-weave/src/artefact-dsl.ts:91. - Defect: the schema reads
miadi-prototype:foo/barasfoo. The address parser refuses it. - Scenario (proven):
references: [miadi-prototype:foo/bar]puts prototypefooin the room's card, while the same string in prose renders as an unresolved chip. - Severity: later.
- Smallest fix: anchor the schema pattern (
([^/?#\s]+)$), or have the schema reuse the same grammar.
F18. requireTracingRef does not check what its doc says it checks#
- Where:
packages/inquiry-weave/src/tracing.ts:59-64. - Defect: the doc says the reference and the separate fields "must agree". When the reference parses, the fields are ignored without comparison.
- Scenario:
{reference: "miadi-tracing:ceremony/trace/x", domain: "story"}is held inceremonywith no error. - Severity: later.
- Smallest fix: compare the two, or drop the sentence.
F19. A trace with no id takes its name as its id#
- Where:
packages/episodic-memory-schema/src/episode-tracing.ts:198. - Defect:
id ?? nameapplies to every resource kind, not only prompts. - Scenario:
{domain: ceremony, resource: trace, name: ep120-run}is held as the trace idep120-run, which the provider never finds. - Severity: later.
- Smallest fix: fall back to
nameonly whenresource === "prompt".
F20. An older binding line can overwrite a newer session's team, agent, alias and host#
- Where:
packages/episodic-memory-schema/src/episode-sessions.ts:114,:118,:120-121. - Defect: these fields are taken from the incoming line even when it is older than the record. The doc at
:10-11says the latest event decides the team. - Scenario: lines read out of order give the team of the older line. The real file is append-ordered, so the risk is low.
- Severity: later.
- Smallest fix: apply the
current.last_seen > atguard that name and pane already use.
F21. Dead code, unused packages and a claim no code implements#
- Where:
packages/inquiry-weave/src/rise-dsl.ts:103-156app/chronicle/lib/artefactRef.ts:17,:119packages/episodic-memory-schema/src/episode-tracing.ts:41packages/tracing-langfuse/README.md("tags")
- Defect:
riseWorkspacePath,riseLocalPathandKNOWN_RISE_WORKSPACEShave no caller. The only use is the re-export inartefactRef.ts, and nothing imports it.- Nothing in
app/,lib/orpackages/imports@miadi/tracingor@miadi/tracing-langfuse. - "The project id is read from the keys and kept on the node" is not done anywhere.
- The tracing-langfuse README says tags are answered. They are not (F4).
- Scenario: a reader trusts the docs and expects a project id on tracing nodes, or tags in a trace summary. Neither exists.
- Severity: later (the
rise-dslremoval is part of the F1 fix). - Smallest fix: delete or wire each one, and correct the two docs.
F22. The open door's room anchors do not exist#
- Where:
app/chronicle/lib/artefactRef.ts:103. - Defect: it redirects to
#prototypesand#tracing, but no element in the room has those ids. - Scenario: opening a tracing chip lands at the top of the room, not at the card.
- Severity: later.
- Smallest fix: give the two cards those ids.
F23. Empty titled cards on every episode#
- Where:
app/chronicle/components/EpisodeRoom.tsx:372,:378,:401,:404. - Defect:
Cardrenders its title even when its child returns null. - Scenario: every visitor sees an empty "Sessions" box on every episode. Episodes that hold no prototypes, traces or specs also show empty "Prototypes", "Traces and prompts" and "Specifications" boxes.
- Severity: later.
- Smallest fix: render the Sessions card only when the API allowed the read, or let each child own its card.
F24. Artefact chips resolve one at a time#
- Where:
app/chronicle/lib/references.tsx:239-249,:101,:139. - Defect: each
artefactInfoOfandriseInfoOfis awaited in turn, with a 4 s timeout each (artefactRef.ts:39). The gate regexrise:also matches "Enterprise:". - Scenario:
- With the wheel tunnel half-open, a text with 10 artefact references takes about 40 s to render on the server.
- Any text containing "Enterprise:" triggers a manifest load.
- Severity: later.
- Smallest fix:
Promise.all, and gate on\brise:\/\/.
F25. An unrelated dependency bump rides in a #769 commit#
- Where:
pnpm-lock.yamlin 45d5ad25. - Defect: the commit also moves framer-motion and motion-dom 14.0.0 to 14.1.0, which comes from
"framer-motion": "latest"atpackage.json:133. It is not #769 work. - Scenario: a revert of #769 also reverts that bump.
- Severity: later.
- Smallest fix: say so in the next commit, or pin the range.
F26. The Langfuse test's fake ignores the field selection#
- Where:
packages/tracing-langfuse/test/provider.test.mjs:22-34. - Defect: the fake
getManyreturnstraceNamewhateverfieldsasks for. "the provider answers metadata only..." therefore asserts a name the real endpoint would not return. - Scenario: this is how F3 passed the tests.
- Severity: fix with F3.
- Smallest fix: make the fake drop
trace_contextfields unless they are requested, and assert thefieldsvalue.
F27. No test resolves an address through a door-created node#
- Where:
packages/reference-ui/test/reference-ui.test.mjs:254-277.artefactInfoOfandartefactNodeIdhave no test against door output. - Defect: the chip test hands in a made-up
ArtefactInfowithexists: truefor the@2prompt. - Scenario: this is how F2 passed the tests.
- Severity: fix with F2.
- Smallest fix: an inquiry-weave test that attaches a versioned prompt with
attachEpisodeTracingon the fake wheel and then looks upartefactNodeId(parseArtefactUri("miadi-prompt:…@2"))inwheel.store.nodes.
Risks#
R1. The reach rule ends at the wheel#
GET http://ilex:8040/api/nodes/prototype:<slug> has no authentication. Anyone who can reach the wheel (the loopback tunnel on gaia, or ilex.tail3b11eb.ts.net:8040 on the tailnet) reads editor_url and ui_url. That is acceptable while the tailnet is operators only. Write it down where the rule is stated, in the schema's prototype and tracing headers.
Ranked list (most severe first)#
- F1: the browser bundle break in
./referencesand reference-ui (blocks publish) - F2: a versioned prompt never resolves (+F27)
- F6: resolve and open fail open
- F3: wrong trace name from Langfuse (+F26)
- F7: host paths in the pure schema
- F5: aliases not read from
references: - F4:
listTracesname and tags - F8: failed project cached forever
- F9: one-page trace summary
- F10: visibility by first holder
- F14: editing address accepted as
url - F11: dead GitHub targets for
jgwill/episodesandmain - F12: spec node commit and link drift
- F13: foldered prompts refused
- F15:
made_byreach - F16: bare uuid read as a notebook
- F17: prototype parser divergence
- F23: empty cards
- F22: missing anchors
- F24: sequential resolution
- F18: unchecked "must agree"
- F19: trace id taken from name
- F20: out-of-order session merge
- F21: dead code and unimplemented claims
- F25: lockfile drift
Before tonight's publish#
These changes are needed before the five packages go to npm:
- F1: take the fs-dependent workspace map out of
rise-dsl.ts, so@miadi/inquiry-weave/referencesand@miadi/reference-uibundle for a browser again. Prove it with the esbuild line in F1. - F2: build the tracing node id without the version, with the F27 test beside it.
- F3: add
trace_contextto the Langfuse field selection, with the F26 fake fixed. - F5: read the two aliases in the schema's
parseTracingRef. - F7: make
HOST_PATH_REPOSITORIESan option that inquiry-weave passes, not an export of the schema. - F4: either implement or remove
nameandtagsonlistTraces. - F8: stop caching a failed project.
- F6: fail closed in the two app routes before the :3335 rebuild.
Then run one next build, because components/ceremony/turn-text.tsx will be the first client bundle to compile the new references layer. Everything else can follow as patch releases.
🌸: Readers of a public episode keep seeing only what was meant for them, and the people who hold traces and prompts get correct names and working links.