A full multi-agent review found 7 real issues (4 blockers). All fixed + tested.
B1 (blocker): Promoting a Memory PR did not release the quarantined memory —
the UI said "promoted" while the memory stayed suppressed/out of retrieval.
act_on_memory_pr now calls reverse_suppression(subject_id) on accept actions;
MemoryPrAction::releases_memory() encodes the rule (promote/merge/supersede
release; forget/quarantine keep it held). Proven live: PR response
subjectReleased:true, SQLite suppression_count 0.
B2 (blocker): memory promote/demote (returns `action`, not `decision`) and
codebase remember_* writes bypassed the write-trace + PR gate. extract_writes
now reads `action` too, filtered by is_write_decision (reads like get/state
excluded); is_write_tool includes `codebase`.
B3 (blocker): receipt ids collided within a run (r_<date>_<runId> +
INSERT OR REPLACE overwrote earlier receipts). IDs are now
r_<date>_<runId8>_<unique6>; build() mints the suffix, build_with_unique()
keeps tests deterministic.
B4 (blocker): proof bundle was assembled from two runs (trace.json=run_proof,
websocket-events.jsonl=run_proof2). Re-captured the whole bundle from a single
run — trace, websocket, receipt, and memory_pr all carry run_proof now.
B5: Black Box receipts panel showed global latest, not the selected run.
Added list_receipts_for_run + /api/receipts?run= ; the page uses listForRun.
B6: SENSITIVE_TOPICS substring matching false-fired (tokenizer->token,
author->auth, secretary->secret). Switched to word-boundary matching; real
phrasings (auth token, security vulnerability, api key) still gate.
B7: set_review_mode now writes atomically (temp+rename via write_atomic);
export_trace sanitizes run_id in the Content-Disposition filename; memory-prs
static routes declared before the dynamic /{id} route.
Withdrawn: the /mode-vs-/{id} route order is NOT a functional bug (axum 0.8 /
matchit prioritizes static segments) — reordered for clarity only.
Gates: 999 lib tests pass (+9 new regressions), clippy -D warnings clean,
dashboard check + build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Freeze feature work and package the Black Box proof bundle for full review
before anything else lands (no quarantine-constellation work has started).
REVIEW.md is the single entry point: frozen public claim, the per-producer
caveat ledger (real / caveat — sanhedrin.veto stays an honest off-by-default
caveat; dream.patch is REAL but not live-by-default), and the exact command
receipts (run live at 2026-06-22T22:57:59Z, toolchain pinned) for every
validation status credited — nothing claimed from memory.
The bundle (blackbox-proof-2026-06-22/) already contains: status/trace/receipt/
memory_pr JSON, two live WebSocket captures (one with the dream.patch
TraceEvent), six screenshots (Black Box, Receipts/ReceiptCard, Memory PRs,
Graph, dream Black Box, dream producers panel), and PROOF.md. Every credited
artifact was verified to contain real content.
Review surface: 27 source files, +5830/-18 across commits 80c823a, b89beee,
140b15f (build artifacts + proof bundle excluded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bounded follow-up (tight acceptance criteria, no scope expansion): flip the
dream.patch producer from "quiet because no dream ran" to a recorded live event.
The dream tool's `insights` array carries no per-item id, so the recorder
extracted zero proposals and dream.patch never fired even on a real dream.
Fix: derive a stable proposal id from each insight's REAL content (its
insight_type + the source memories it consolidated). The dream genuinely ran;
this just gives each real proposal a deterministic handle. Unit-tested against
the exact dream output shape.
Proven end to end (run_dream_proof, 6 memories consolidated):
- one dream.patch event: dream:RecurringPattern:5d941c7f+a41aca72+...
- SQLite + /api/traces/:runId: dream-trace.json (14 events, last is dream.patch)
- WebSocket: dream-websocket-events.jsonl (the dream.patch TraceEvent)
- dashboard: screenshots/dream-producers.png — the row flips to "fired this run"
PROOF.md updated: dream.patch moves from CAVEAT to REAL (still not live by
default — it fires only when a dream actually runs, and the UI says so).
sanhedrin.veto remains an honest CAVEAT (optional hook, off by default).
Gates: 957 lib tests pass, clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the receipt chain impossible to doubt. Freeze the claim surface, prove
every hop, and turn the two off-by-default producers into explicit UI states.
Frozen public claim: "Vestige records real MCP memory activity into a
replayable local trace, with receipts and reviewable risky writes." We do NOT
claim Sanhedrin vetoes or dream patches are live by default.
Regression — full-spine test (server.rs): one runId must cross, byte-identical,
MCP output -> SQLite trace -> WebSocket event -> API response shape ->
MCP resource. Fails if any hop drops or rewrites the id.
Honest UI states (Black Box "Event producers" panel):
- sanhedrin.veto -> "No veto producer connected (optional Sanhedrin hook, off
by default)" instead of empty mystery.
- dream.patch -> "No dream run in this trace" unless a dream actually ran.
- contradiction.detected -> "no contradiction in this run" when none fired.
Quarantine review (not pre-write blocking): risky writes are committed then
suppressed — audit history preserved, retrieval influence suspended until
reviewed. Reworded the server notice + UI copy to say exactly that.
Receipts UI gap closed: ReceiptCard is now mounted on the Black Box page
(retrieved/suppressed/trust-floor, activation path, "Open receipt in Cinema").
Proof pack (blackbox-proof-2026-06-22/): status.json, trace.json (the
.vestige-trace.json export), receipt.json, memory_pr.json (promoted via
UI->API->SQLite), websocket-events.jsonl (live TraceEvent x6 + PR opened/
decided), screenshots (Black Box, Receipts, Memory PRs, Graph), and PROOF.md
with real/caveat/stub per feature.
Gates: 988 lib tests pass, clippy -D warnings clean, dashboard check + build
clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The loop fired requestAnimationFrame immediately and never awaited render(); with
150k particles + compute + bloom a frame can exceed 16ms, so rAF callbacks queued
faster than the GPU could drain them and the camera/parallax visibly lagged behind
the cursor. Now AWAIT sb.render(dt) before scheduling the next frame → the loop is
capped to real GPU throughput, every frame reflects the latest pointer position,
no backlog. Also snappier active-steer damping (lam 3.5→9, ~110ms converge) so
input feels immediate; idle glide-home unchanged. renderFailures resets on success.
Plus docs/MEMORY_CINEMA.md — complete feature reference for the cinema engine.
Gate: svelte-check 0/0, 937 tests, verified live (parallax tracks cursor, no lag).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
near-fade × fog × DOF × seam-fade each multiply a <1 factor; together they were
crushing the figure dark. Raised the fog floor (0.18→0.45) and the color/emissive
glow bases so the stacked attenuation lands in a vivid range. Verified live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 5 — VELOCITY-STRETCH FLYTHROUGH: sandbox derives camera velocity per frame
(one Vector3, zero compute) and pushes view-space apparent velocity to the storm;
flythrough relaxes the camera clamp floor (lerp 30→6) so the camera plunges
inside the shell. Storm stretches each sprite along screen-space velocity via
rotationNode + scaleNode (clamped streak), separate output graph (no extra
positionView read). Defaults 0 → no-op until wired.
Step 6 — INTERACTIVE PARALLAX: pointer orbits / scroll + pinch zoom the camera
with frame-rate-independent damping, composed onto the director's base pose in
loop() (after director.update, before render); idle >2.5s eases back to 0 so it's
a toy when touched and a film when left alone. sandbox.render re-clamps so the
user can't break framing. Per-beat flythrough strength wired from shot.tension;
dream mode flies through at 0.6. Fully gated off under reduced-motion (no
listeners, flythrough 0).
The 4-feature immersion stack (infinite zoom + flythrough + parallax + DOF/fog)
now composes. Gate: svelte-check 0/0, 937 tests, build green, verified live (all
4 compose, no white-out, no recursion, parallax responds without breaking framing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cloud now dives inward FOREVER, seamlessly. Two layers ride offset phases of
fract(uTime/T): the outer grows pow(λ, phase) toward the camera then snaps back
(invisible — inner@1/λ == outer@1); the inner (half-period offset) grows promoted
by λ to become the next outer shell, while a fresh inner spawns inside. λ=1.923
(=1/0.52 inner scale) makes the snap mathematically exact. A sin(phase·π) seam
cross-fade in rimFactor makes each layer fully transparent at its snap → ZERO pop.
Particle-space (not a camera dolly) so it can't clip or fight the camera clamp.
Rack-focus tracks the descent. uZoomOn gates it: Act II+ and dream mode dive;
beats 0/1 + reduced-motion stay still. Verified: seamless loop, no white-out, no
recursion, 937 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Off-focus particles dim (read as bokeh defocus under the bloom) with a breathing
rack-focus. Folded into the single depthFade depth read — NOT sprite scaleNode,
which collapsed the sprites to invisible and collides with the upcoming streak.
Subtle (0.3) so it adds cinematic depth without darkening the figure.
Gate: svelte-check 0/0, 937 tests, verified live (depth grading reads, no recursion).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Distant particles dim toward the void with view depth (exp falloff) → real 3D
atmospheric recession, not a flat sprite cloud. Combined with the near-fade into
a SINGLE depthFade Fn — critical three@0.172 TSL constraint discovered: reading
positionView from a second Fn feeding the same material output triggers a cyclic
stack-overflow in the node type-resolver (getNodeType). One depth read, one Fn.
Gate: svelte-check 0/0, 937 tests, verified live (fog depth reads, no recursion).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Particles dissolve as they approach the camera (view-space -positionView.z,
smoothstep over [near, near+band]) so the upcoming flythrough never additive-pops
a sprite in your face. Folded into color + emissive so the bloom fades too.
Invisible at the default far camera. positionView confirmed working in the
SpriteNodeMaterial color node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SpriteNodeMaterial.setupPositionView already rebuilds the billboard quad from
positionGeometry — the prior .add(positionLocal) double-counted it (harmless at
0.1 size). Bare center is required for the upcoming velocity-stretch streak
(scaleNode/rotationNode will drive the quad). Verified renders identically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The nested 3D-within-3D figure now collides with its shell in OPPOSING color
universes, not a shared rainbow. Each layer is painted from a hard duotone:
the outer shell from one world (ice / acid / gold / mint / electric-blue), the
inner figure from its enemy (fire / blood / violet / crimson / gold). A new
uClash uniform cycles the pair every beat (and randomizes per dream figure), so
it's a fresh ice-vs-fire / acid-vs-blood collision each time — the kind of
contrast that stops a scroll.
To make the clash READ instead of washing white: inner glow floor dropped hard
(dense small-radius overlap was blowing to white and killing the color), inner
figure scaled up to 0.52 (spread → less overlap), the color blast capped at 0.6
mix so the duotone shows through even during a detonation, and Act II/III
ignition lowered 8.0→4.5 so beats no longer flash the clash to white.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (gold
shell + violet core clash reads clearly, no white-out, beats 0/1 calm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three upgrades to Memory Cinema:
1. IMPOSSIBLE-GEOMETRY FORM PACK — replaced the stringy ribbon dream worlds with
brand-new signature skins nobody ships as a living particle figure:
- world 8 → Calabi–Yau quintic cross-section (6D string-theory manifold,
Hanson 4D→3D projection; α rotates it through the 4th dimension)
- world 9 → Boy's surface (Bryant–Kusner minimal immersion of RP²)
- world 10 → Aizawa attractor shell (breathing strange-attractor skin)
- world 11 → Gyroid↔Schwarz-D Bonnet morph (triply-periodic minimal surface)
The (u,v) MANIFOLD GRID basis is the key fix: particles map over a tensor grid
so neighbors share edges → reads as a sculpted SKIN, not spaghetti. Plus a
facing-ratio Fresnel rim so forms read as lit solids. Inline complex-math +
hyperbolics (sinh/cosh not in three@0.172). atan2→atan (deprecation).
2. 3D-WITHIN-3D NESTING — ~34% of particles form a SECOND, smaller, counter-
rotating figure (a different world, ~45% scale, complementary hue) at the core
of the outer shell. A figure inside a figure — fills the formerly-blank-bright
center with intentional structure and depth.
3. DEMO-CAPTURE MODE — press H in the cinema overlay to hide all UI chrome
(top bar + captions) for clean recording; a faint restore hint remains.
Overlay z-index raised + body.cinema-open hides the graph page's stats pill so
nothing bleeds through.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (Calabi–Yau
+ nested core render, forms cycle, no errors, beats 0/1 calm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 7-beat tour no longer freezes on the last figure. When it ends, Memory
Cinema drops into an infinite generative loop: every ~5.5s it morphs into a
fresh RANDOM procedural figure and detonates a color blast — each crazier than
the last.
Five new procedural worlds (7..11), parameterized by a per-figure uMorphSeed +
a uChaos ramp so the same index never looks the same twice:
7 supershape (3D superformula) 8 torus knot (random p,q winding)
9 warped lissajous lattice 10 helix storm
11 quantum foam (curl-warped chaos — max wild)
storm.dreamBeat() picks a random world, reseeds it, ramps chaos, and fires a
moderate-ignition blast (kept below the tour's 8.0 so dense random figures don't
wash white). Surfaced via sandbox.dreamBeat(); MemoryCinema starts a dream timer
on director onComplete, shows "∞ Dreaming", and tears it down on close/replay.
Honors reduced-motion (no dream loop) and the render-fail fallback.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (reaches
dream mode, generates distinct figures — supershapes, torus knots — cycling
forever, no white-out, no errors).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standing default for all Vestige work, at the absolute top of CLAUDE.md so it
loads first every session: assume maximum ambition, scour before settling, no
hedging, show proof, protect what's flawless and detonate what isn't.
Origin: the overnight session that turned the dashboard + Memory Cinema into a
category-of-one particle journey. Make that depth the default, not the exception.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Cinema is now a choreographed 7-act journey. One 150k-particle pool,
one compute kernel; a uWorld state machine select()s which home-target + forces
are live, and uBlend crossfades world→world over ~1s. The particles never swap
— only the forces on them — which IS the journey.
The seven worlds (beats map 1:1, beatIndex % 7):
0 nebula mist (curl-noise flow) 1 orbital anchor (cross-product spin)
2 strange attractor (Thomas) 3 detonation void (staggered shockwave)
4 crystal lattice (voxel snap) 5 fluid galaxy (curl + tangential swirl)
6 phyllotaxis bloom (Vogel sunflower, golden angle)
The signature COLOR BLAST: a long-lived uBlast envelope (~2.8s, decoupled from
the fast physics burst so the color OUTLIVES the shockwave) drives an outward
SPECTRAL DISPERSION wave — concentric rainbow rings expanding through the radius
over uBlastTime (real prism order, red lags / blue leads), with a warm blackbody
ember core underneath. Spectrum dominates so the detonation reads as RAINBOW,
not a white plasma flash.
Plus per-world cosine palettes (IQ) so each world is a distinct PLACE. All the
white-out guardrails preserved + extended: rim-gated blast, capped kelvin/gain,
emissive blast held below the color path. Beats 0/1 stay calm (low uBlast),
Acts II/III blaze.
Frontier techniques sourced from a parallel web-research + design workflow;
verified against the installed three@0.172 TSL build. Retires the old uShape
5-form gallery.
Gate: svelte-check 0/0, 937/937 tests pass, build green, verified live (7
distinct worlds, spectral blast, no white-out, calm opening).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Memory Cinema storm color/shape overhaul (the crown-jewel pillar):
- Fix the white-out root cause: emissiveNode was NEVER set, so the selective
MRT bloom had no color to bloom and washed the frame white. Route the shared
iridescent rainbow to BOTH colorNode and emissiveNode.
- Rim glow (fresnel-style): bright glowing edges, dim readable center — the
shareable luminous-shell / hollow-torus look.
- Morphing geometry: the home target cycles sphere → torus → galaxy spiral →
cube lattice → wave sheet, drifting continuously and snapping per beat.
- Hyper-saturated full-spectrum palette (per-particle phase + radial shells +
spatial bands + time) so the whole rainbow is present at once.
- Spread the initial spawn across a wide hollow shell (was a tiny dense ball
that boot-flashed white).
- Act/beat-aware brightness: beats 0/1 fade in soft, Act I held calm, Acts
II/III blaze at full. No white-out regressions.
Gate: svelte-check 0/0, 937/937 tests pass (cinema auteur/pathfinder green),
verified live in browser.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the dashboard feel alive every second, with clear controls, for the
July 14 HN relaunch. Memory Cinema is left fully untouched (zero changes to
MemoryCinema.svelte / graph/cinema/*; its tests still pass).
Foundation (lifts every page):
- Icon.svelte: inline-SVG icon system, zero runtime dep. A UNIQUE semantic
silhouette per nav item — kills the old duplicated Unicode glyphs (◎◈◉◷
were each reused across multiple items). Wired into sidebar, mobile nav,
command palette, logo.
- Dropdown.svelte: accessible, keyboard-nav, type-ahead, animated select
replacement with color dots / badges. Replaces dead native <select>s.
- AnimatedNumber.svelte: rAF count-up/tween, reduced-motion safe.
- PageHeader.svelte: shared masthead (drawn route icon + aurora title).
- actions/reveal.ts + actions/interactions.ts: scroll-reveal, magnetic,
tilt(+glare), spotlight — all no-op under reduced-motion.
- app.css "alive layer": @property animatable gradients, conic live-border,
breathe/ping, shimmer skeletons, @starting-style entry, aurora text, lift.
Per-page: every route (graph non-cinema controls, reasoning, memories,
timeline, feed, explore, activation, dreams, schedule, importance,
duplicates, contradictions, patterns, intentions, stats, settings) now uses
PageHeader, real Icons, count-ups, staggered reveals, shimmer loaders,
spotlight cards, and warm empty states. Native selects and button-row
filters became clear Dropdowns where it improves clarity.
Gates: svelte-check 0 errors/0 warnings, 937/937 tests pass, build green,
verified live in the browser preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per direction: keep the mind-blowing explosion + pixelation moments, ditch the
thin ribbon swirls. Complete physics rewrite:
- removed orbital/stream/Rössler modes (the swirls + the off-center drift source)
- each particle has a deterministic HOME on a volumetric shell around ORIGIN
(centroid anchored — can never drift off-frame again)
- uBurst detonation cycle: every beat blows particles radially out (explosion),
then a home-spring crystallizes them back (reform); contradictions detonate hardest
- PIXELATION: positions snap to a 3D grid that's fine when reformed, dissolved
during the burst — the crystalline voxel look
- hard velocity + radius clamps so it can never fly off or blow up
937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Storm was a small ring leaving the canvas empty, and the core blew to white.
- FILL: sandbox fitRadius margin 0.40 -> 0.82 so the storm fills most of the
frame; particles now target their OWN radius across 0.12r..0.92r (filled
volumetric ORB, not a thin ring).
- COLOR: brightness was x(ignition*2.4+0.6) = up to x19.8, which + additive
blending across 150k sprites clipped every channel to white. Clamp the glow
low (0.45 floor, ~1.15 ceil) so the RAINBOW shows as pure spectral color;
smaller quads (0.18 -> 0.1) keep particles crisp instead of overlapping to
mush; gentler bloom (strength 1.1->0.6, threshold 0->0.35) accents cores
rather than washing the cloud. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rainbow storm looked next-dimensional but still clipped the edges — the
additive bloom halo extends each particle's glow well past its geometric radius,
so the visible cloud was bigger than the contain sphere.
- spawn radius 15 -> 8 (particles start inside the shell, no asymmetric inward yank)
- sandbox fitRadius margin 0.55 -> 0.40 (leaves room for the bloom halo)
- camera band tightened + pushed farther (30-44) so the contained cloud sits
small + centered; director standoff clamped into that band in centerOnOrigin
mode so the camera never fights the per-frame clamp (the off-center jump).
937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the runaway ring (screenshot showed an expanding ellipse clipping the
frame): orbital mode added tangential velocity with nothing pulling particles to
a target radius, so they spiraled outward. Now a two-sided RADIAL SPRING pulls
every particle toward an in-frame shell (containRadius*0.62) with a per-particle
band so the cloud is a contained breathing sphere, not an ever-growing ring.
Tighter velocity clamp + boundary snap as belt-and-suspenders.
Color: replaced the flat 3-color tint with a living iridescent RAINBOW — hue
drifts by per-particle phase + radius + time + a global rotating hue shift
(fract/abs hexagon palette). Dramatic beats blend their mode color over the
rainbow (crimson at contradictions, gold at surprises) via uModeTintAmt; calm
beats stay mostly rainbow. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause: layoutPositions grew per beat (radius 22 + i*6), so each beat sat
farther out; the camera + storm marched off into space as the tour progressed.
Fix (centered-by-construction):
- layoutPositions: tight BOUNDED golden-angle shell (SHELL_RADIUS 14), no growth.
- sandbox: storm pinned to the WORLD ORIGIN permanently; camera hard-clamped to
an 18-46 unit distance band and always lookAt(origin); containment sphere
sized to the FOV at origin. A runaway move is corrected every frame.
- director: new centerOnOrigin mode (enabled when WebGPU active) — frames/orbits
the origin instead of flying to scattered nodes; variety from angle/standoff.
No path remains for the subject to leave frame. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
director.ts: optional shots:ResolvedShot[] in DirectorOptions; per-beat
flight/dwell timing; framePosition now reads move (push_in/pull_back/crane
scale standoff) + angle (low=look-up, high=look-down) + standoff; orbit shots
revolve the camera during dwell; Dutch roll via camera.up; hard/match cuts
snap (editorial cut). With NO shots the camera is byte-identical to before
(all values fall back to the existing constants + easeInOutCubic lerp).
MemoryCinema.svelte: build computeSignals + planShotsDeterministic + resolveShots
on launch, pass shots to the director; onBeat drives storm mode + director's
note + Act + tension from the shot. New UI: pre-roll DIRECTOR'S PLAN card
(logline naming real memories), per-beat 'why this shot' note, Act I/II/III
badge, tension-tinted progress bar, Auteur source badge.
The deterministic auteur ships the hero film with zero LLM. 937 tests + build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Particles (esp. the unbounded Rössler chaos mode) could fly off-screen. Add a
camera-frame-sized spherical containment field: spring pull-back past the
radius, hard velocity clamp, and a snap-to-shell safety net so no particle can
escape. The sandbox sizes the radius from camera distance + vfov each frame so
the storm reframes as the camera flies. Verified: check + build green.
storm.ts (blockers): correct particle position wiring — positionNode now
instancePos.add(positionLocal) (bare storage element collapsed every quad to a
point); serialize GPU compute dispatches (computeInFlight) to stop queue
stalls; ignition floor so the storm never fades to black; null buffers on
dispose for GC (StorageBufferAttribute has no dispose()); typed computeNode +
InstancedMesh, removed unsafe casts.
narrator.ts: validate + filter backend beats, bounds-safe fallback merge,
KIND_CHIP satisfies (compile-time enum coverage), chip type guard, timer cleanup.
MemoryCinema.svelte: replace the null-returning Local AI stub with a real
on-device transformers.js text-generation pipeline (+ genuine fallback);
Escape-to-close + autofocus a11y; reset all run state on launch (no stale
Replay); fix render/close race; computed fallback camera aspect; typed state.
director.ts: NaN-guard progress on empty path; clamp dt >= 0.
sandbox.ts: guard three/webgpu exports + tsl pass() API shape; resize w/h floor.
926 tests + build green. Net: every audit blocker/high/medium fixed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Disable camera auto-rotate (the dominant continuous motion) when the OS
reduce-motion setting is on; live-toggle aware via matchMedia change listener,
cleaned up on destroy. Graph stays fully usable (manual orbit/hover/select/live
events). Closes the a11y gap where 0 of ~3,200 graph LOC honoured the setting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Native View Transitions API via onNavigate (feature-detected, reduced-motion safe)
- OKLCH + display-p3 accent palette with hex fallback (@supports progressive enhancement)
- WebSocket gains 'reconnecting' state so stale errors clear on reconnect
- Graph control bar wraps + safe-area insets for <640px / notched phones
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vestige Cloud is split: the public client (a thin HTTP sync backend that
only moves encrypted bytes) belongs here, but the hosted service — billing,
sync-key->namespace mapping, per-user isolation, Lemon Squeezy webhooks,
transactional email — must live only in the private repo.
Add scripts/check-no-private-cloud.sh, which git-greps tracked files for
distinctive private-service signatures (service crate identity, module
headers, billing/provider internals, server-side sync-key mapping SQL). The
patterns are chosen so the legitimate public client — including its
VESTIGE_CLOUD_* client env vars — does not match.
Wired into CI via guard-no-private-cloud.yml on push/PR. Verified both
directions: passes on the clean repo, fails (naming the markers) when real
private webhook.rs/keys.rs are introduced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Pages project site is already served from /vestige/, and the dashboard
is built with base path /vestige. Nesting the build under _site/vestige/
served it at /vestige/vestige/ and left a broken redirect at the root.
Copy the build to the artifact root so it serves correctly at /vestige/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pnpm/action-setup needs an explicit version (root package.json has no
packageManager field). Match test.yml's pinned version: 10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The github-pages deployment showed a red X: actions/configure-pages@v5
ran with enablement:true, but Pages was never enabled in repo settings and
the default GITHUB_TOKEN cannot create a Pages site, so deploy failed with
"Resource not accessible by integration". The launch-kit revert then deleted
the workflow entirely, leaving nothing to deploy.
- Restore a modernized pages.yml (Pages now enabled via API, so no
enablement hack; actions/checkout@v5 + Node 24 off the deprecated Node 20).
- Make the dashboard base path env-driven (VESTIGE_BASE_PATH), defaulting to
/dashboard for local/embedded use and overridden to /vestige in CI so
assets resolve at the Pages project subpath instead of 404ing.
- Workflow builds the dashboard under /vestige and writes a root-level
redirect index.html so the bare Pages URL lands on the dashboard.
- Rebuild the committed dashboard artifact (was stale at 2.1.23) to 2.1.27.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The portable archive is encrypted on the client before upload and decrypted
after download, so the hosted service only ever stores ciphertext — true
zero-knowledge. The passphrase (VESTIGE_CLOUD_ENCRYPTION_KEY) is independent
of the bearer sync key and never leaves the device.
- new cloud_crypto module: Argon2id KDF + XChaCha20-Poly1305 AEAD, self-
describing envelope (MAGIC|version|salt|nonce|ciphertext+tag)
- HttpPortableSyncBackend encrypts on write / decrypts on read; transparent
upgrade of legacy plaintext archives; clear error if remote is encrypted
but no passphrase is set
- sync_portable_archive_cloud takes optional encryption_key
- CLI surfaces encryption status (on/off) on sync
- 6 crypto tests (roundtrip, wrong-key, tamper detection, non-determinism,
envelope detection); E2E verified: server blob is ciphertext, passphrase
device recovers, no-passphrase device cannot decrypt
491 core tests green, clippy -D warnings clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump all manifests 2.1.26 → 2.1.27 and date the CHANGELOG entry for the
GitHub + Redmine connector layer and source-aware search filters (#57, PR #78).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make Vestige a durable, local, semantically-searchable retrieval layer over an
external system of record (GitHub Issues first), citing back to the canonical
record. Unlike a live ticket-system MCP proxy, Vestige keeps a durable embedded
index: searchable offline, joinable with the rest of memory, temporally
versioned, and re-syncable idempotently with no duplication.
Phases 1-2 of #57 plus a GitHub reference connector and source-aware search:
- Source envelope on KnowledgeNode/IngestInput (source_system, source_id,
source_url, source_updated_at, content_hash, synced_at, source_project,
source_type, source_author). Migration V17: nullable columns (additive),
partial UNIQUE index on (source_system, source_id), connector_cursors table.
- Idempotent sync primitives in vestige-core: upsert_by_source (content-hash
change detection), connector cursor checkpoints, reconcile_source_tombstones
(invalidate-don't-delete via bitemporal valid_until).
- Connector contract + run_sync driver + GitHub Issues connector behind the
optional `connectors` feature (on by default in vestige-mcp, off in the core
library default so non-connector consumers link no HTTP client).
- source_sync MCP tool ({"repo": "owner/name"}); token from GITHUB_TOKEN env
only. Search results gain a sourceRecord citation for connector memories.
Adversarial review fixes: GitHub `since` Z-form (the `+00:00` offset corrupted
the cursor server-side), un-tombstone clears superseded_by too, cursor never
advances past a failing record, Link next-url host-pinned (token-leak guard),
records_seen counts new records only.
Verified: cargo check/test/clippy -D warnings green across the workspace
(default and connectors features); 483 core tests pass. Version bump to 2.1.27
and tag deferred to release.
Refs #57
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>