Commit a886449
release: v0.2.0 — Oneiro (#30)
* feat(dialectic): Stage 3 — action dispatcher (CLA-100) (#17)
* feat(dialectic): Stage 3 — action dispatcher (reframe / flag / keep) (CLA-100)
Stage 3 reads `action` + `action_payload` from each Stage 2 audit row
and executes:
keep — no memory mutation; mark row dispatched
reframe — re-embed (Workers AI bge-base-en-v1.5), upsert Vectorize,
update D1 content/summary, preserve original in
memory_reframes (reversible via SQL)
flag — append to dialectic_flags for human review
## New files
- `migrations/0006_dialectic_dispatch.sql` — dispatch_at + status + error
columns on dialectic_decisions, run-level actions_dispatched counter,
memory_reframes (reversibility audit), dialectic_flags (human review).
- `src/dialectic_validation.rs` — native-testable validation gate
(`validate_synthesis_payload`). Catches Synthesizer shape/action
mismatches before any mutation. 10 unit tests cover the matrix.
- `src/worker_dialectic_dispatch.rs` — the dispatcher itself. Wasm-only
because it touches Env / D1 / Vectorize / Workers AI. Pulls validation
in from the universal module.
## Wiring
- `worker_dialectic_audit::record_decision` now returns the
`decision_id` so the dispatcher can update the same row.
- `worker_dialectic::run()` reads `DispatchMode::from_env` once per
run, then dispatches each non-well-calibrated decision after the
audit row is recorded.
- `RunSummary.actions_dispatched` tracks dispatch attempts.
- `record_run_finish` writes the new counter to dialectic_runs.
## Kill switch
`MEMORIA_DIALECTIC_DISPATCH` env var:
on — real dispatch (will be the default once burned in)
dry_run — validate, mark status=dry_run, no mutation
off — skip dispatch entirely (revert to Stage 2 dark-launch)
Unrecognised values fall through to `On` — typos must not silently
disable the dispatcher.
## Safety properties
- Validation gate ensures malformed payloads never mutate state.
- Reframe order: embed → Vectorize upsert → D1 update → audit.
Embed/Vectorize failures occur before any D1 mutation.
- Reframe preserves the original in memory_reframes. Every reframe
is reversible via a SQL UPDATE.
- Idempotency: dispatch_decision is only called by the caller for
rows where dispatched_at is NULL. mark_dispatched always sets
dispatched_at, so no row is re-dispatched.
## Test plan
- [x] `cargo check --target wasm32-unknown-unknown --lib` clean
- [x] `cargo test` — 75 (incl. 10 new validation tests) + 49 = 124 passed
- [x] `worker-build --release` — 28.2kb optimised bundle
## What's NOT included (future tickets)
- "Catch up on unmarked rows from prior runs" pass. Hard errors
during dispatch leave rows unmarked; today they require manual
replay. Worth a small follow-up once we have data on how often.
- Deeper prompt-injection hardening on memory content (raised in
CLA-99 PR review; minimal wrap already in place).
- `flagged` MCP tool to surface dialectic_flags during conversations.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(dialectic): PR #17 review — fail-closed dispatch + atomic reframe (CLA-100)
Addresses both blockers from GPT review on PR #17. Reframe atomicity
in particular is a real safety fix: the previous code could lose the
only copy of the original content if the audit insert failed after a
successful UPDATE. That violated the "every reframe is reversible"
claim. No longer possible.
## 1. DispatchMode::from_env defaults to DryRun, not On
Previous behaviour: missing or unrecognised `MEMORIA_DIALECTIC_DISPATCH`
fell through to `On`. A fresh deploy without the secret would immediately
perform live reframes/flags.
New behaviour: only an explicit `on` (or `live`) enables real dispatch.
Missing → DryRun with a console_log notice. Unrecognised → DryRun with a
console_error notice. Fail-closed for a mutating system.
Reasoning: a typo silently disabling dispatch is recoverable (operator
notices nothing changed, fixes the secret). A typo silently enabling
mutation is not (operator notices memories changed unexpectedly, has to
roll back via memory_reframes). Asymmetric blast radius → asymmetric
defaults.
## 2. Reframe uses db.batch() for atomic UPDATE + audit INSERT
Previous order:
1. fetch original
2. embed
3. Vectorize upsert
4. D1 UPDATE memories
5. D1 INSERT memory_reframes
If step 5 failed after step 4 succeeded, the memory was already mutated
and the original was lost. The code logged this as "success with
warning" — but that violated the claimed safety property.
New order:
1. fetch original
2. embed
3. Vectorize upsert
4. **Atomic** db.batch([UPDATE memories, INSERT memory_reframes])
D1 batches are transactional — either both statements commit or neither
does. The destructive UPDATE can no longer land without the audit row
carrying the original. "Every reframe is reversible" is now true at the
storage layer, not just by convention.
The Vectorize/D1 inconsistency window (step 3 succeeds, step 4 fails)
remains as the only acceptable inconsistency state, recoverable via
next dialectic pass — same as before.
## 3. mark_dispatched gains WHERE dispatched_at IS NULL guard
Makes the primitive idempotent at the SQL level rather than relying on
the caller's filter. Future replay/catch-up paths can call it without
holding their own filter.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
No new test cases needed: blocker 1 is configuration, blocker 2 is a
storage-layer guarantee verified by D1's batch semantics, and the
mark_dispatched nit is a constraint addition that the validation
tests don't exercise.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(setup): set MEMORIA_DIALECTIC_DISPATCH=on for fresh deploys (CLA-100) (#18)
The worker's fail-safe default is dry_run, which protects operators
mid-deploy who haven't set the secret yet — a typo or omission can't
silently enable destructive reframes.
But that same default would silently disable dispatch on every fresh
consumer deployment via setup.sh. Non-technical users won't read
audit rows, won't flip secrets manually, won't know dispatch isn't
running. They'd sit in audit-theater forever — Stage 2 telemetry
accumulates, Stage 3 never acts, memories silently miscalibrate.
Setup.sh now pushes "on" explicitly. Two failure modes, two
appropriate defaults:
- Code default (DryRun) → catches operator error during incremental
deploys
- Setup-script value (on) → ensures consumers get a working
dialectic out of the box
Also documents the secret in wrangler.toml.example so the override
path is discoverable.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(dialectic): reframe cooldown to prevent runaway re-litigation (CLA-101) (#19)
Stage 1 candidate selection now excludes memories reframed within the
last REFRAME_COOLDOWN_DAYS (default 7). Without this gate, a
freshly-reframed memory's updated last_accessed keeps it in the
recent-semantics pool, and Stage 1 may re-flag the reframed version
on a different axis (e.g., the now-flatter language reads as
understated). The memory could then drift through several iterations
and oscillate between inflated/understated verdicts.
First Stage 3 dry-run pass (2026-05-18) surfaced this in concrete
form: the same memory (baece146) was picked up by two independent
runs and produced reframe proposals each time. Both pointed in the
same direction (consistency signal) but a live cutover without
cooldown would have started the loop.
## What this does
- New helper `recent_semantics_not_recently_reframed(db, limit, cooldown_days)`
in worker_store.rs. LEFT JOIN on memory_reframes (max reframed_at
per memory_id), excludes rows inside the cooldown window. Uses
SQLite's datetime() on both sides so the RFC 3339 reframed_at
values compare structurally against datetime('now', '-N days')
rather than lexicographically.
- New `REFRAME_COOLDOWN_DAYS` constant (7) in worker_dialectic.rs.
One-line caller swap in run().
- The old `recent_semantics` (no cooldown) had no other callers and
was removed cleanly rather than left as orphan code. The verbose
new function name documents what the call does, making future
intent unambiguous.
## What this enables
The cooldown lets a reframed memory accumulate real recall traffic
and demonstrate calibration in use before the dialectic litigates
it again. Genuine problems still surface — just not within a 24-hour
loop.
This is the last release-blocker before flipping
MEMORIA_DIALECTIC_DISPATCH=on safely. Without it, the first live
night would start the loop on whichever memory Stage 1 flags first.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(dialectic): cooldown gates on dialectic_decisions, not memory_reframes (CLA-101) (#20)
Original CLA-101 cooldown gated on memory_reframes.reframed_at, but
that table is only written by Stage 3's reframe_memory() when dispatch
is LIVE. In dry_run mode (the failsafe default during burn-in), the
dispatcher short-circuits before memory_reframes is touched. Result:
the cooldown was silent on the exact code path it was supposed to
protect — the same memories kept getting re-judged every night during
the observation window.
Observed in production: two dialectic runs ~2 hours apart on
2026-05-18, both with dispatch in dry_run mode, both picked the same
three semantic memories (baece146 + 94a53721 + ff8dc5b0) and ran full
Stage 1 evaluations. The post-deploy cooldown had no effect.
## Diagnosis
The cooldown's job is to prevent re-litigation of memories the
dialectic has already taken a look at. That event happens at
decision-time (Stage 2 records a row to dialectic_decisions),
regardless of whether the proposed action is then applied. The audit
table is the canonical record of "we've evaluated this memory" — not
memory_reframes, which only fires on the mutation half.
## Fix
- Rename `recent_semantics_not_recently_reframed` →
`recent_semantics_not_recently_judged`. The function now LEFT JOINs
dialectic_decisions (max created_at per memory_id) and excludes
memories with a decision inside the cooldown window.
- Rename `REFRAME_COOLDOWN_DAYS` → `DIALECTIC_COOLDOWN_DAYS`.
- Doc comments updated to reflect what the gate actually protects.
## Broadened scope (intentional)
The new gate excludes memories with ANY recent decision, not just
action-worthy ones. Two reasons:
1. Well_calibrated memories were also being re-judged every night.
The same three memories appeared in both runs of the observed
audit data. Wasted Haiku calls; not the point of nightly dialectic
passes.
2. The dialectic should spread its attention across the full semantic
pool over the cooldown window — not hammer the N most-recent
memories every night. Gating on any decision means the candidate
pool drains naturally as memories get judged, then refills as the
cooldown expires for older decisions. Rolling sweep instead of
fixed focus.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
## Post-deploy verification
After this lands and deploys: tonight's dialectic run should pick
DIFFERENT semantic memories from the three baece146/94a53721/ff8dc5b0
seen in the 2026-05-18 audit data. All three have decision rows
within the last 7 days, so all three should be excluded.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(version): update-available prompt in recall output (CLA-102) (#21)
* feat(version): update-available prompt in recall output (CLA-102)
Non-technical users won't read changelogs or watch GitHub releases.
Without an in-band prompt, every future Memoria release — security
patches, dialectic refinements, new tools — depends on operators
noticing manually. The deployed worker becomes a frozen artifact of
whatever version got installed.
The leverage: the Claude instance the user is talking to is itself
the most reliable update prompt. The recall handler now checks for
an update and appends a short notice to its output when one's
available. Claude reads the notice and naturally surfaces it
conversationally — "looks like there's a Memoria update, want me
to walk you through it?".
## Files
- `VERSION.json` (new, repo root) — `{latest_version, release_notes_url}`.
Bumped as part of every release PR alongside Cargo.toml.
- `src/worker_version.rs` (new) — check_for_update(env) returns
Option<UpdateAvailable>. KV-cached (6h TTL); fetches GitHub raw on
miss. Every error path returns Ok(None) — recall never fails
because the version check failed.
- `src/worker_mcp.rs` — recall handler appends a "── Memoria update
available ──" section to its output when check_for_update returns
Some. Section omitted entirely when versions match or check failed.
- `src/lib.rs` — declares the new worker_version module.
- `wrangler.toml.example` — documents the new VERSION_CACHE KV
binding.
- `scripts/setup.sh` — creates MEMORIA_VERSION_CACHE KV namespace
alongside MEMORIA_TOKENS, patches wrangler.toml with its id.
## Compile-time version
`CURRENT_VERSION = env!("CARGO_PKG_VERSION")` — baked into the wasm
bundle at build time. Cargo.toml's version and VERSION.json's
latest_version must stay in sync; the release PR is the canonical
place to bump both together.
## Failure modes (all return Ok(None) — recall continues normally)
- KV binding missing
- GitHub raw unreachable
- VERSION.json malformed
- Versions match
- Worker can't even reach the network
Console_error logs every soft failure so operators can investigate
without affecting user-facing behaviour.
## Existing deployment note
This adds a KV binding that doesn't exist on Justin's current
production deployment. Before deploying, run:
wrangler kv namespace create MEMORIA_VERSION_CACHE
And add the resulting id to your local wrangler.toml under a new
[[kv_namespaces]] entry with binding = "VERSION_CACHE". Setup script
handles this automatically for fresh installs.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
## Field shape extensibility
UpdateAvailable currently carries {current, latest, url}. The shape
extends cleanly to {breaking_changes, security_critical,
deprecated_after, min_version_for_compat} as future fields. All
additive; existing consumers ignore unknown keys.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(version): use major.minor.patch comparison, not string equality (CLA-102)
PR #21 review caught a real footgun: the original
`if remote.latest_version == CURRENT_VERSION` flagged any mismatch as
"update available". That includes dev/release-branch builds where the
worker is COMPILED at `0.2.0-dev` but VERSION.json on master still
reads `0.1.0` — the prompt would tell the user to "update" to an
older version.
Replaces the equality check with `is_remote_newer(current, latest)`:
- Parses optional `v` prefix and major.minor.patch
- Ignores any pre-release suffix (`-dev`, `-rc1`, etc.)
- Returns true only when latest > current
- Falls back to plain inequality for malformed versions so we still
surface something unusual rather than silently saying everything's
fine
The "Memoria"-hardcoded prompt copy + URL paths flagged in the review
are intentionally held for CLA-97's rename sweep — landing the rename
as one batch is cleaner than partial renames across feature PRs.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: rename memoria → Oneiro (CLA-97) (#22)
* chore: rename memoria → Oneiro (CLA-97)
Mechanical rename sweep across the codebase. Three cases:
memoria → oneiro (lowercase: package, db/index/bucket names, urls)
Memoria → Oneiro (titlecase: prompts, docs, log strings)
MEMORIA → ONEIRO (uppercase: env vars, secrets, KV namespace names)
Plus structural changes:
- memoria-skill/ → oneiro-skill/
- evals/memoria_evals.md → evals/oneiro_evals.md
- scripts/com.memoria.backup.plist → scripts/com.oneiro.backup.plist
- Cargo package: memoria → oneiro
- Worker name: memoria → oneiro (new CF Worker URL post-deploy)
- All MEMORIA_* secret/env-var names → ONEIRO_*
- wrangler.toml: removed from tracking + added to .gitignore so
account-specific resource IDs don't live in the repo (handled by
setup.sh on fresh install)
## Why hard rename (option A)
Option B (code-only rename, infrastructure stays memoria-named) was
the safer path but never exercises the consumer deployment flow.
Option A dogfoods setup.sh end-to-end before public release — bugs
in the install path surface on us, not on a stranger reading the
README and hitting a wall.
## Migration path (separate PR)
Justin's existing memoria-* CF resources continue to function — his
local wrangler.toml is now untracked and unchanged, so wrangler deploy
still targets the existing worker. Migration to fresh oneiro-* resources
is handled by scripts/migrate-from-memoria.sh (CLA-97 follow-up).
## What's NOT in this PR
- Migration script (separate PR — follows this one immediately)
- GitHub repo rename (handled in GitHub UI after merge; raw URLs in
the code already point at /oneiro/ paths, which GitHub auto-redirects
from old /memoria/ until the rename completes)
- Worker URL change in client configs (Justin's manual re-pointing
after migration; that's the "configure once, propagates everywhere"
demonstration the announcement leans on)
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
(oneiro lib generated 19 warnings — pre-existing dead-code)
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
- [x] sed pass left 0 memoria references in tracked source
42 files changed, 334 insertions, 330 deletions, 7 file/dir renames,
1 file removed from tracking (wrangler.toml).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(rebrand): refresh architecture copy for CF-canonical runtime (CLA-97)
PR #22 review flagged stale architecture content in README, CLAUDE.md,
and oneiro-skill/references/architecture.md. The text still described
the pre-migration setup (dialectic on HomeLab, three local processes,
M1 Pro server, launchd, Ollama, Tailscale Funnel), even though the
worker code itself had moved entirely to Cloudflare via CLA-95/99/100.
A reader landing on the README pre-release would think they needed
local infrastructure to run the dialectic. They don't — Stage 1-3
all live in the worker now.
## What changed
### README.md
- **Circadian Rhythm section**: both processes now described as
Cloudflare cron triggers. The misleading "HomeLab (Claude Code
agent teams via launchd)" cell is gone.
- **The Dialectic section**: rewritten around the actual Stage 1/2/3
shape that shipped — neutral assessor, Advocate-vs-Challenger
dialogue, Synthesizer arbitration with keep/reframe/flag. Removed
the "Why still local" justification (no longer applies). Added
the safety properties that actually exist: atomic reframe via D1
batch, validation gate, cooldown.
- **Why-this-matters paragraph** replaces the four-deadlock-states
paragraph's tail — explicitly names the escalation-to-mythology
failure mode the system exists to prevent. This is the line the
ClaudeExplorers announcement will lean on.
- **Architecture diagram**: dialectic cron is now Cloudflare, not
HomeLab. Updated to mention Stage 1/2/3 + memory_reframes +
dialectic_flags.
- **Status section**: "the dialectic runs on the operator's HomeLab"
removed. "No external infrastructure required after setup.sh
completes" added.
- **Roadmap**: removed "Dialectic on Cloudflare" (done). Added
`flagged` MCP tool as the natural next step.
### CLAUDE.md
- **Circadian Rhythm**: replaces the three-local-processes table
with the two-CF-cron table. Notes that the dialectic *replaced*
the local subconscious pass — keeps the function, changes the
mechanism.
- **Build & Test / Deploy**: cargo + worker-build + wrangler.
Removed the local-binary stdio/HTTPS instructions (the native
binary is preserved for tests only).
- **Bidirectional Sync** section removed entirely (was local-to-local).
- **Project Structure**: lists the worker_*.rs files that actually
matter, plus migrate-from-memoria.sh under scripts/.
- **Tech Stack**: Cloudflare-first. rmcp 1.4 not 1.2. Removed
rusqlite, Ollama, Tailscale Funnel, launchd. Added Anthropic
OAuth credit pool with the Haiku 4.5 gating note.
- **Infrastructure**: "Cloudflare Workers does all the heavy lifting.
No always-on server required." plus the OAuth + update-prompt
details.
- **Roadmap**: completed items reflect what's shipped (Stage 1-3
dialectic, dispatcher, cooldown, version check, migration helper,
OAuth pentest hardening). Next items are the actually-queued
follow-ups (R2-optional, flagged tool, multi-tenant, tiered
models). Future items unchanged (entity graph, embodiment, iRacing).
- **Ollama → Workers AI** reference under Memory Dynamics.
### oneiro-skill/references/architecture.md
- Mirrored to match CLAUDE.md; title changed to "Oneiro —
Architecture Reference" since this is a user-facing reference
rather than a Claude-Code-instructions file.
## Build verification
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed (no code changes; sanity check)
- [x] worker-build --release — 28.3kb bundle unchanged
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat(migration): one-time memoria → oneiro data migration script (CLA-97) (#23)
* feat(migration): scripts/migrate-from-memoria.sh — one-time data move (CLA-97)
Migrates an existing memoria-* deployment to fresh oneiro-* resources
after CLA-97's rename. Idempotent and read-only on the source — the
old memoria-* resources stay intact until the operator manually
decommissions them, so verification happens before any destructive
step.
## Stages
1. Preflight — verify source + dest resources exist, dest schema applied
2. D1 — `wrangler d1 export` source, strip CREATE statements,
rewrite INSERT → INSERT OR IGNORE so re-runs are safe,
`wrangler d1 execute --file=` against dest. Tombstones,
audit tables, dialectic_*, memory_reframes, rem_*,
cluster_decisions, co_activations all come along.
3. Vectorize — gather memory IDs from dest D1, batch-fetch from
memoria-vectors via `wrangler vectorize get-vectors`,
format as NDJSON, `wrangler vectorize insert` into
oneiro-vectors. Insert is upsert by id, so re-runs
are safe. Batch size tunable via --vectorize-batch.
4. R2 — list memoria-images, copy each object key to
oneiro-images via get/put through a tmp file. R2 PUT
overwrites by key, so re-runs are safe.
5. Verify — row counts in source vs dest, tombstone counts. Operator
spot-checks before decommissioning source.
## Flags
--source-db NAME / --source-vectors NAME / --source-images NAME
Override source resource names (defaults are
memoria-db / memoria-vectors / memoria-images).
--vectorize-batch N Vectors per get/insert call (default 100).
--skip-d1 / --skip-vectors / --skip-r2
Partial re-runs after a stage failure.
--dry-run Print what would happen without writes.
Destination names read from current wrangler.toml — setup.sh must have
populated it. The script refuses to run if source and dest D1 names
match (sanity check against migrating onto itself).
## Decommissioning
Out of scope. Script prints the commands at the end so the operator
can run them after verifying the migrated copy works:
wrangler delete --name memoria
wrangler d1 delete memoria-db
wrangler vectorize delete memoria-vectors
wrangler r2 bucket delete memoria-images
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): PR #23 review — fail-early on jq, live-writes warning, cleanup
Three review fixes from GPT review on PR #23.
## 1. jq preflight check (blocker fix)
Previously, the script could complete the D1 import and only discover
the missing jq dependency partway through Vectorize. Bad partial state —
the D1 was migrated but vectors weren't, leaving the dest worker in a
half-built state.
Moved the jq check to preflight (before any writes). Now the script
either has jq up-front or refuses to start. The defensive jq fallbacks
inside the Vectorize stage have been simplified since jq is guaranteed
by preflight.
## 2. Live-writes warning
Added a preflight notice that the old worker stays live during the
migration. Any writes between D1 export and connector cutover won't be
in the migrated copy unless the script is re-run. Operator can either
pause the old worker first or plan a re-run after cutover to sweep
stragglers.
## 3. Dead placeholder loop removed
Vectorize stage had a no-op scaffolding loop left over from an earlier
draft:
while IFS= read -r -d '' batch_ids || [ -n "$batch_ids" ]; do
: # placeholder for parallelism if needed later
done < <(:)
Harmless but confusing — removed.
## Tests
- [x] bash -n scripts/migrate-from-memoria.sh — syntax clean
- [x] --help renders correctly
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(setup): two bugs surfaced during the dogfood run (#24)
First-run dogfooding caught two CLI/parsing bugs in setup.sh. Both
land here so the next setup.sh attempt completes cleanly.
## 1. wrangler dropped --yes from `d1 migrations apply`
Step 7 of 8 failed with:
✘ [ERROR] Unknown argument: yes
Newer wrangler versions removed the flag in favour of TTY-based
confirmation auto-skip. Replaced with deterministic `printf 'y\n'`
piping so the script works across wrangler versions and terminal
types. Output capture for failure handling preserved.
## 2. Fallback awk regex was broken (latent bug)
When `wrangler d1 create` fails because the resource already exists
(common on re-run), the script falls back to extracting the existing
ID from wrangler.toml via awk. The pattern was:
gsub(/.*"|".*/, "")
…which is broken: `.*"` matches greedily to the LAST quote on the
line, so on `database_id = "abc-123-uuid"` it matches the entire
line and replaces it with empty string. The fallback returned `""`
and the script aborted with "Couldn't determine D1 database_id".
This bug existed in three places (D1 fallback, TOKENS KV fallback,
VERSION_CACHE KV fallback). It was never exercised by a first run
(which extracts from wrangler's stdout, not from the file), so it
sat latent until the dogfood re-run actually went through that path.
Replaced with `split($0, a, "\""); print a[2]` — splits the line on
quote characters, picks the value between the first pair. Verified
locally: `echo 'database_id = "abc-123-uuid"' | awk '...'` returns
`abc-123-uuid` cleanly.
## What this PR is
Exactly the bug class the option-A dogfood was meant to catch. The
benefit of doing the rename + migration ourselves before public
release is that this surfaces to us first instead of to a stranger
reading the README on day one. Two bugs, five lines apiece, never
seen externally.
## Resume path
After this merges, re-run `./scripts/setup.sh`. The first 6 steps
are idempotent (create-or-detect-existing patterns now work
correctly). Step 7 completes with the y-pipe. Step 8 deploys.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(oauth): include Claude.ai web in default allowlist + show URI in error (#25)
Two related bugs surfaced during Justin's first connection from
Claude.ai web to the freshly-deployed oneiro worker.
## 1. Default allowlist missed Claude.ai web's callback
DEFAULT_ALLOWED_REDIRECT_URIS contained only `claude://oauth-callback`
(Claude Desktop's scheme). Claude.ai web uses
`https://claude.ai/api/mcp/auth_callback`. Every fresh deploy hit
"redirect_uri not registered" on the first web-based connection
attempt, even though the setup script explicitly tells users to use
Claude.ai → Settings → Connectors → Add Custom Connector.
Updated the default to include both Anthropic-controlled endpoints:
desktop scheme + web callback. Custom callbacks (localhost dev,
alternate clients) still override via the
ONEIRO_OAUTH_REDIRECT_URIS secret.
## 2. Error response didn't actually contain the offending URI
The setup script's troubleshooting hint reads:
If you see "invalid_request: redirect_uri not registered"
Copy the URI from the 400 response body, then:
wrangler secret put ONEIRO_OAUTH_REDIRECT_URIS
…but the response body was just the literal string
"invalid_request: redirect_uri not registered" with no URI to copy.
The instruction was misleading at best.
Updated three error sites to include the offending URI in the body:
GET /authorize — render_consent_page in lib.rs
POST /authorize — handle_authorize_post in worker_oauth.rs
/token (exchange path) — token_error in worker_oauth.rs
Body now reads:
invalid_request: redirect_uri not registered: <actual-uri>
So the setup script's instruction does what it says.
## Why this matters
After (1), Claude.ai web works out of the box on a fresh install.
After (2), any user who DOES need to add a custom callback (a
non-default Anthropic client, localhost dev) can copy/paste the URI
from the error response instead of digging through logs or guessing
at form-encoded query params.
Both bugs surfaced through dogfooding option-A (Justin connecting
his fresh oneiro deploy from Claude.ai web). Neither would have been
caught by the test suite — they live in the integration boundary
where wrangler + worker + Claude UI meet.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): two follow-ups from real-run failures (#26)
* fix(migration): drop OR IGNORE (collides with dump's ON CONFLICT) + better debug
First real migration run hit:
✘ [ERROR] near "ON": syntax error at offset 5: SQLITE_ERROR
## Root cause
`wrangler d1 export` emits upsert-style statements:
INSERT INTO memories VALUES (...) ON CONFLICT(id) DO UPDATE SET ...;
The migration script's `sed` was prepending `OR IGNORE` to make re-runs
idempotent, producing this illegal hybrid:
INSERT OR IGNORE INTO memories VALUES (...) ON CONFLICT(id) DO UPDATE SET ...;
SQLite doesn't accept both an `OR IGNORE` conflict-resolution clause AND
an `ON CONFLICT` clause on the same INSERT — it rejects at offset 5
(the start of `OR IGNORE`'s appended clause).
The dump's existing `ON CONFLICT` clauses already provide idempotency,
so the right fix is to leave INSERT statements alone.
## Fix
1. Removed the `INSERT INTO → INSERT OR IGNORE INTO` sed substitution.
Statements pass through as-is. Only schema (`CREATE TABLE` / `CREATE
INDEX`) is still stripped.
2. Counter changed from `grep -c "^INSERT OR IGNORE"` to `grep -c "^INSERT"`.
## Debuggability bumps surfaced by the same failure
- Trap now preserves `TMPDIR` on failure. Previously the script
reported "Check ${DATA_FILE} for details" then deleted the file via
trap on exit. Now the tmpdir survives unless the script reaches
`MIGRATION_OK=true` at the end.
- Wrangler output capture changed from `2>&1 | tail -3` to full-file
capture + `tail -30` on failure. The earlier pipe ate everything
except a "Logs were written to ..." footer; the actual error lived
in the wrangler log file. Now operators see the real error inline.
- Error path prints the preserved file paths (dump, transformed SQL,
wrangler log) so the operator can grep them directly.
## Tests
- [x] bash -n scripts/migrate-from-memoria.sh — syntax clean
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): exclude D1 internal tables from import
PR #26 fixed the OR-IGNORE vs ON-CONFLICT collision, but next run hit:
✘ [ERROR] UNIQUE constraint failed: d1_migrations.id
The dump from `wrangler d1 export` includes Cloudflare's internal
`d1_migrations` table — it tracks which migrations have been applied
to that specific D1. The destination database already has its own
`d1_migrations` rows from when setup.sh ran step 7 (apply migrations).
The dump's INSERTs don't have ON CONFLICT clauses on these internal
tables, so they fail on the PK collision and roll back the whole
import.
Application tables (memories, dialectic_*, etc.) are fine — the dump
gives those upsert-style INSERTs that survive re-runs.
## Fix
Three new sed filters strip the internal tables from the import:
- `INSERT INTO d1_migrations` — CF's migration tracker
- `INSERT INTO sqlite_sequence` — AUTOINCREMENT counters (we don't
use any, but defensive)
- `INSERT INTO "_cf*"` — any other CF-prefixed internal tables that
might appear in future wrangler versions
These belong to CF's per-database bookkeeping, not the operator's
data. The destination manages its own copies of all three.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): handle quoted table names in dump filter
The d1_migrations filter from the previous commit didn't actually fire.
wrangler d1 export quotes table names:
INSERT INTO "d1_migrations" ("id","name","applied_at") VALUES(1,...);
…but the regex was anchored on `INSERT INTO d1_migrations` (unquoted).
The pattern matched zero lines, the transformed file was byte-identical
to the previous run, wrangler's content-hash cache served the same
upload, and we hit the same PK collision.
Confirmed by:
$ grep -in "d1_migrations" data-only.sql | head -5
2:INSERT INTO "d1_migrations" ("id","name",...)
3:INSERT INTO "d1_migrations" ("id","name",...)
...
Fix: add `"?` to each internal-table filter so both quoted and
unquoted forms match. Same pattern applied to sqlite_sequence in case
a future wrangler version drops the quotes.
Verified manually:
$ echo 'INSERT INTO "d1_migrations" (...)' | sed -E '/^INSERT INTO "?d1_migrations"?/d'
(empty — line deleted)
$ echo 'INSERT INTO "memories" (...)' | sed -E '/^INSERT INTO "?d1_migrations"?/d'
INSERT INTO "memories" (...)
(preserved — not internal)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): strip PRAGMA / BEGIN / COMMIT — D1 execute rejects them
After the d1_migrations filter started working, the next error was the
"near ON syntax error at offset 5" — same shape as the original
OR-IGNORE error, but on different content. The actual cause was the
dump's first line:
PRAGMA defer_foreign_keys=TRUE;
D1's `wrangler d1 execute --file` path doesn't accept PRAGMA
statements. The parser fails and reports a misleading "near ON"
error pointing into the NEXT statement's token stream (everything
shifts when the PRAGMA isn't consumed cleanly). Confirmed by
inspecting `head -15 data-only.sql` — first line was the PRAGMA,
nothing else nearby contained the "ON" token at offset 5.
wrangler manages its own transaction atomicity for `execute --file`,
so stripping these is safe:
- `^PRAGMA ` — defer_foreign_keys + any other PRAGMA
- `^BEGIN(...)?;?$` — explicit transaction begin (if present)
- `^COMMIT;?$` — explicit transaction commit
The application INSERT statements pass through unchanged.
Verified manually: piping a four-line snippet through the new sed
keeps only the INSERT, drops the PRAGMA/BEGIN/COMMIT.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): use wrangler --no-schema instead of regex-stripping schema
Per Justin: "Why are we creating tables anyway? setup.sh already created
them." The right answer. setup.sh runs migrations 0001–0006 on the dest
before this script runs; we never want the source's schema in the
import. Three rounds of sed regexes trying to strip CREATE TABLE +
CREATE INDEX (single AND multi-line) + PRAGMA + BEGIN + COMMIT were
patching symptoms instead of questioning the premise.
wrangler d1 export supports `--no-schema` which makes it emit only
data INSERTs. Adding that flag eliminates an entire class of bugs:
- Multi-line CREATE INDEX continuations (" ON foo(bar);" orphans
that produced "near ON at offset 5" errors)
- PRAGMA defer_foreign_keys statements that D1 execute rejected
- BEGIN/COMMIT wrapping that we were defensively stripping anyway
- Single-line CREATE INDEX edge cases that the range filter handled
awkwardly
The remaining sed is now three lines — filter the D1 internal tables
(d1_migrations, sqlite_sequence, _cf*) that wrangler still emits as
data. Those belong to CF's per-database bookkeeping; dest manages its
own.
Net diff: −40/+24, much simpler, and correct by construction rather
than by accumulated regex defenses.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): surface Vectorize + R2 errors instead of swallowing them
D1 stage works end-to-end (500 memories migrated in the first clean
run). Vectorize and R2 both reported failure but the script had
`2>/dev/null` swallowing stderr from the three wrangler calls
involved:
- `wrangler vectorize get-vectors ... --ids=...`
- `wrangler vectorize insert ... --file=...`
- `wrangler r2 object list ...`
Result: silent "0 vectors copied" and "skipping R2 stage" outcomes
with no clue why. Diagnostic dead-end.
Replaced each `2>/dev/null` with per-call stderr capture to a file in
TMPDIR, then `head -10` on failure so the actual wrangler error shows
inline. Same pattern as the D1 import fix from earlier in this PR.
Now when the next migration run hits these stages, we'll see whether:
- `--ids=` should be `--id=` (singular, repeated)
- `--remote` is rejected by `r2 object list`
- Or something else entirely
…instead of guessing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): Vectorize warning-tolerance + R2 driven by D1 query
D1 migration is solid (500 memories landed cleanly). Last two stages
needed real fixes after the dogfood run exposed them:
## 1. Vectorize: tolerate "missing vector" warnings
`wrangler vectorize get-vectors --ids=...` emits a WARNING (yellow ▲)
and a non-zero exit code when the batch contains any IDs that don't
have vectors — even if the rest of the batch returned valid data on
stdout. Old behaviour treated that as fatal, killing the whole batch.
Justin's deployment has 498 vectors across 500 D1 memories (two early
smoke-test memories created before Vectorize was wired up). With 2
missing spread across 5 batches of 100, every batch hit a missing
vector and got skipped. Net result: 0 vectors migrated.
Fix: capture stdout regardless of exit code, attempt jq parse with
`.[]?` (empty-input-tolerant), only escalate to true failure when the
JSON itself doesn't parse. The 498 valid vectors land; the 2 missing
ones get quietly dropped. Same correctness, no false alarms.
## 2. R2: D1-driven instead of `wrangler r2 object list`
`wrangler r2 object list` doesn't exist (known wrangler gap). The
old code fought through every JSON-parse fallback I could think of
and still got a clean "command not found" → silent skip.
Real answer: D1 is the source of truth for which images exist.
Every image-attached memory has `image_hash` + `image_mime` columns;
the R2 key is `{hash}.{ext}` per worker_store::store_image_to_r2.
Querying D1 also has the nice property of only migrating *referenced*
images — orphans from earlier failed writes get left behind, which
is the correct semantics for a clean cutover.
New flow:
1. `wrangler d1 execute --json --command="SELECT DISTINCT image_hash,
image_mime FROM memories WHERE image_hash IS NOT NULL"`
2. jq into "hash mime" lines
3. For each: derive key (mime→ext mapping matches the Rust function),
`wrangler r2 object get` + `put` (those CLI commands DO exist)
Same pattern Justin called out for the D1 stage — stop fighting the
dump format, use what we already own.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): add OR IGNORE — dump has no upsert clauses (re-verified)
Idempotency check fail. PR #26's re-run failed with:
✘ [ERROR] UNIQUE constraint failed: memories.id
…which proves what my comments were claiming was wrong: wrangler d1
export --no-schema does NOT emit `ON CONFLICT(id) DO UPDATE SET ...`
clauses. Plain `INSERT INTO "table" (...) VALUES (...);` statements
only. First-run worked because dest was empty; re-run hits PK
collisions.
My original "OR IGNORE clashes with ON CONFLICT" diagnosis from way
back was incorrect. The real culprit of the early "near ON at offset
5" errors was the orphan multi-line CREATE INDEX continuations
(` ON foo(bar);` lines surviving after their CREATE INDEX
counterparts got stripped). --no-schema eliminated that whole class
of problem at the source.
With --no-schema in place, OR IGNORE is the actually-correct way to
make the import idempotent. Re-adds the substitution that PR #26
incorrectly removed, with corrected reasoning in the comment.
Empirical behaviour now:
- First run on empty dest: 499 rows INSERT cleanly
- Re-run on populated dest: 499 rows hit PK, get IGNOREd, no-op
- Updates to source between migrations: dest stays at the older
version. Acceptable for one-time cutover; source is frozen by
the time the operator runs the migration script anyway.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): pass Vectorize IDs as separate args, not CSV
Real Vectorize error finally surfaced (after the stderr-capture fix):
✘ [ERROR] id too long; max is 64 bytes, got 3559 bytes [code: 40008]
wrangler's `--ids` flag is array-typed (yargs convention) — it expects
multiple values as separate arguments, NOT as one comma-separated
string. Old code:
--ids="<comma-separated-100-uuids>"
…sent the whole 3559-byte CSV as a single id to the API, which
choked at the 64-byte limit. Hence every batch failed.
Fix: build a bash array from the batch file, expand with "${IDS[@]}"
so each UUID becomes a separate argv element:
while IFS= read -r id; do
[ -n "$id" ] && IDS_ARR+=("$id")
done < batch.txt
wrangler vectorize get-vectors INDEX --ids "${IDS_ARR[@]}"
Used a read-loop instead of `mapfile` so this works on macOS's
default bash 3.2 (mapfile is bash 4+).
R2 stage from the previous commit was already working (10/22 copied
by the time vectorize failed), so this should be the last fix for
the migration script. After this lands, full re-run should complete
all four stages cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): cap Vectorize batch at 20 (API limit)
After the array-args fix, the next error: "max id count is 20". CF's
vectorize get-vectors API caps each request at 20 IDs regardless of
how they're passed. The 100-per-batch default was 5× over the ceiling.
Reduced VECTORIZE_BATCH from 100 to 20. 500 memories ÷ 20 = 25
batches, each completing in well under a second. Operator can still
override via --vectorize-batch flag if a future API version raises
the ceiling.
This should be it for the migration script — D1 + R2 already
verified working in the previous run, and Vectorize was the only
remaining stage failing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): file-based jq for Vectorize + surface jq's actual error
Justin reports: stdout starts with `[` and ends with `]}]`. That IS
valid JSON. So either:
- Shell variable expansion is mangling ~80KB of JSON in transit
(large strings + embedded special chars + bash quoting interactions),
OR
- jq is failing on something INSIDE the array that the previous
`2>/dev/null` swallowed silently.
Two changes to disambiguate:
1. Write wrangler stdout directly to a file. jq reads the file. No
round-trip through `VEC_JSON=$(...)` variable expansion. Removes
one whole class of "shell ate my data" bugs.
2. Capture jq's stderr instead of /dev/null'ing it. On parse failure,
show jq's actual complaint plus the first 300 bytes of wrangler's
stdout so we can see what jq saw.
Same diagnostic philosophy that worked for the earlier stages: stop
guessing, capture real evidence.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(migration): strip wrangler banner before piping vectorize JSON to jq
Justin saved a clean capture (one_vector_id.txt) and inspection
revealed:
⛅️ wrangler 4.90.1 (update available 4.92.0)
─────────────────────────────────────────────
📋 Fetching vectors...
[
{"id":"...","namespace":null,"metadata":null,"values":[...]}
]
Three banner lines on stdout BEFORE the JSON payload. jq parses from
byte 0, hits the emoji, fails. (Empirically: the banner is on stdout,
not stderr — `2>/dev/null` doesn't help.)
Fix: `sed -n '/^\[/,$p'` to keep everything from the first `[` line
onwards, then pipe to jq. Verified locally against the captured file:
$ sed -n '/^\[/,$p' one_vector_id.txt | jq -c '.[]? | {id, values, metadata}'
{"id":"27cbccb9-...","values":[0.001631..., ...],"metadata":null}
Two debugging notes for the future:
- VEC_OUT (raw wrangler stdout) is preserved separately from
VEC_JSON (banner-stripped). On parse failure both are available.
- The change in file naming (.raw vs .json) makes it obvious which
file to inspect.
This should be the last Vectorize stage fix. All four migration
stages will run cleanly on the next re-attempt.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* docs(readme): move Quick Start to the top, prereqs above the command (#27)
Three QoL fixes from Justin's first read of the public-facing README:
## 1. Quick Start moves from line 122 to line 7
Most impatient readers know they want to deploy; making them scroll
past five sections of philosophy first is friction. New section order:
# Oneiro ← title + 1-paragraph what
## Quick Start ← was at line 122, now line 7
## Why This Exists ← philosophy now follows the action
## How It Works
## Guiding Principles
## Architecture
## MCP Tools
...
Curious readers continue down. Quick deployers stop at the section
that gets them running.
## 2. Prerequisites moves above the install command
Within Quick Start, the old order was:
git clone + ./scripts/setup.sh ← the action
### What you'll need first ← the prereqs, AFTER the action
Reordered to prereqs first, then deploy. New subsection order:
### Prerequisites ← installs needed before running
### Deploy ← the actual command
### What the script asks
### After the script finishes
### Verifying Oneiro is running
### Manual deploy (no script)
## 3. Rust toolchain phrasing clarified
Old line:
- [Rust toolchain](https://rustup.rs/) with the
`wasm32-unknown-unknown` target — the script will add the
target for you if rustup is installed
Confusing: the link is to rustup, but the prereq is described as "the
Rust toolchain", and "if rustup is installed" reads like the script
expects rustup to already be there (which it does).
New line names rustup explicitly as what to install and reframes the
target step as setup.sh handling complexity for you:
- [`rustup`](https://rustup.rs/) — provides the Rust toolchain.
The setup script adds the `wasm32-unknown-unknown` target
(needed to compile the worker to wasm) on first run, so you
don't need to manage it yourself
Same surface, clearer mental model: rustup is the dependency, the
toolchain comes from it, the wasm32 target is the script's problem.
No content removed; everything still in the README, just reordered.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(skill): refresh oneiro-skill.zip with current SKILL.md content (#28)
The CLA-97 rename PR updated SKILL.md and references/architecture.md
inside oneiro-skill/ but didn't regenerate the .zip artifact alongside.
Operators downloading the repo got the renamed source files but a
zip still reflecting pre-rename content.
This refresh aligns the shipped zip with the source tree so users can
do the intended "drop into Claude Desktop/Web" flow without manually
re-zipping or hitting outdated content.
14882 → 15216 bytes (binary diff). Source files unchanged.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore(release): v0.2.0 prep — LICENSE + version bump + skill install step (#29)
Pre-release packaging in one PR:
## LICENSE
README has claimed MIT since the first commit but the actual LICENSE
file was missing — GitHub's auto-detected license badge would have
shown "no license" for any stranger landing on the repo, which has
real legal-clarity consequences for would-be users. Added MIT with
2026 © Justin Davis at the repo root.
## Version bumps — 0.1.0 → 0.2.0
- `Cargo.toml` package version → 0.2.0 (Cargo.lock regenerated)
- `VERSION.json` `latest_version` → 0.2.0
- `VERSION.json` `release_notes_url` now points at the specific
`releases/tag/v0.2.0` URL the upcoming tag will produce
0.2.0 not 1.0.0 — conservative. The architecture has matured enough
to declare a release boundary, but 1.0 carries compat-commitment
weight that's earned through real-world use by multiple operators,
not declared. Save 1.0 for when the project has demonstrated stability
across more deployments and the next round of follow-ups (CLA-98 R2
detection, `flagged` MCP tool, multi-tenant) has landed.
## README — skill install step
Added a "Install the skill (recommended)" section to the post-deploy
flow, between connector setup and verification. The skill is
strongly-recommended-but-not-required: tools still work without it,
but instances diverge on memory hygiene heuristics. Drag-and-drop
cost is near zero; documentation cost was real.
## Tests
- [x] cargo check --target wasm32-unknown-unknown --lib — clean
- [x] cargo test — 75 + 49 = 124 passed
- [x] worker-build --release — 28.3kb optimised bundle
- [x] Cargo.lock reflects new version (`name = "oneiro"`, `version = "0.2.0"`)
## What's next after this lands
1. Release PR: dev → master (the cumulative v0.2.0 cutover)
2. Tag `v0.2.0` on master
3. `gh release create v0.2.0` with notes derived from the release PR
4. Switch GitHub default branch back to master so strangers cloning
the repo land on stable rather than dev
5. GitHub repo description + topics for discoverability
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent f3df920 commit a886449
52 files changed
Lines changed: 2213 additions & 827 deletions
File tree
- evals
- memoria-skill
- references
- migrations
- oneiro-skill
- references
- scripts
- scripts
- src
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
| 5 | + | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
| 51 | + | |
| 52 | + | |
54 | 53 | | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
| 54 | + | |
62 | 55 | | |
63 | 56 | | |
64 | 57 | | |
65 | 58 | | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
83 | 63 | | |
84 | 64 | | |
85 | | - | |
86 | | - | |
87 | | - | |
88 | | - | |
| 65 | + | |
89 | 66 | | |
90 | | - | |
91 | | - | |
92 | | - | |
| 67 | + | |
93 | 68 | | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | 69 | | |
98 | | - | |
99 | | - | |
| 70 | + | |
| 71 | + | |
100 | 72 | | |
101 | 73 | | |
102 | | - | |
103 | | - | |
104 | | - | |
| 74 | + | |
105 | 75 | | |
106 | 76 | | |
107 | 77 | | |
108 | 78 | | |
109 | 79 | | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
115 | 95 | | |
116 | 96 | | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
127 | 110 | | |
128 | 111 | | |
129 | 112 | | |
130 | 113 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
139 | 124 | | |
140 | 125 | | |
141 | 126 | | |
142 | | - | |
143 | | - | |
144 | | - | |
145 | | - | |
146 | | - | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
147 | 133 | | |
148 | 134 | | |
149 | 135 | | |
150 | 136 | | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
170 | 151 | | |
171 | 152 | | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | | - | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
178 | 157 | | |
179 | 158 | | |
180 | | - | |
| 159 | + | |
181 | 160 | | |
182 | 161 | | |
183 | 162 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments