2508 Commits
Author SHA1 Message Date
AkitaOnRailsandClaude Opus 4.8 414fe45e74 docs(lifecycle-ops): document reclaim-ledger-versions
Add the online reclaim command to the safety matrix and a
command-by-command section (dry-run default, content gate, --compact
to reclaim bytes), the operator guide AGENTS.md requires for new
destructive lifecycle ops. Follow-up to #927 (#914).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-27 12:53:53 -03:00
AkitaOnRails 6add9b727b Merge PR #927 into release/2.5
feat(cli): reclaim-ledger-versions drops the pre-#660 ledger residue (#914)

# Conflicts:
#	docs/ARCHITECTURE.md
2026-09-27 12:52:54 -03:00
AkitaOnRailsandClaude Opus 4.8 d7201243b1 chore(changelog): correct #708 authz enforcement to the third slice
The enforcement PR (#924) is slice 3 of #708 (slice 1 = design + choke
point, slice 2 = project_grants); the changelog entry said 'second'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-27 12:52:13 -03:00
AkitaOnRails 20967297ce Merge PR #924 into release/2.5
Per-project authorization, slice 3: enforce restricted end to end (#708)
2026-09-27 12:51:51 -03:00
AkitaOnRails 56a06fd68f Merge PR #898 into release/2.5
fix(omp): use profile paths for hooks, MCP and sessions (#820)
2026-09-27 12:51:43 -03:00
AkitaOnRails 6d389a5121 Merge PR #926 into main
fix(consolidate): unique session titles without starving the input floor (#926)

# Conflicts:
#	CHANGELOG.md
2026-09-27 12:46:59 -03:00
AkitaOnRails 9078dec50f Merge PR #935 into main
fix(consolidate): skip multi-page updates to pinned pages (#934)

# Conflicts:
#	CHANGELOG.md
2026-09-27 12:46:24 -03:00
AkitaOnRails 75ac107c26 Merge PR #940 into main
fix(bootstrap): drop empty chunk rationales from the manifest (#939)

# Conflicts:
#	CHANGELOG.md
#	crates/ai-memory-consolidate/src/bootstrap.rs
2026-09-27 12:45:05 -03:00
AkitaOnRails c0c137ca08 Merge PR #938 into main
fix(bootstrap): leave headroom for the token estimate (#937)

# Conflicts:
#	CHANGELOG.md
2026-09-27 12:44:06 -03:00
AkitaOnRails d2e75897e5 Merge PR #936 into main
fix(bootstrap): cap a lone chunk's output like any chunk (#928)
2026-09-27 12:43:34 -03:00
Éverton Toffanetto fed7d80140 docs(omp): clarify profile selection and install paths 2026-09-27 01:16:35 -03:00
Éverton Toffanetto 1fc6cf7f22 test(run): preserve Pi blank environment coverage 2026-09-27 01:15:09 -03:00
Éverton Toffanetto 86f001c6bf fix(omp): follow OMP's profile rules, PI_CONFIG_DIR and XDG session dir
A named profile (`--profile`, `OMP_PROFILE`, or the legacy `PI_PROFILE`) owns
`~/.omp/profiles/<name>/agent` and ignores `PI_CODING_AGENT_DIR`, as OMP does;
`install-hooks` wrote the extension into `PI_CODING_AGENT_DIR` instead. Profile
names are normalized and refused like OMP does, `PI_CONFIG_DIR` renames the
`~/.omp` root, and on Linux and macOS sessions move to `$XDG_DATA_HOME/omp`
when that directory exists. `install-mcp`, auto-wire, session import,
`backfill`, `doctor` and the `uninstall` sweep follow the same agent dir.

Refs #820
2026-09-27 00:24:41 -03:00
Felipe Neves RicardoandClaude Opus 5.5 cc6fe1728f fix(bootstrap): drop empty chunk rationales from the manifest (#939)
BootstrapBatch.rationale is serde-defaulted, and Anthropic's tool_use
schema does not enforce required fields, so a chunk can return an
empty rationale. The manifest joined every chunk's rationale with
"---" separators, leaving bare separators for the empty ones.

Drop rationales that are empty after trimming before the join, and
say so when no chunk returned one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hbGY9oRywtM3Nz2NcXxUF
2026-09-26 22:27:19 -03:00
Felipe Neves RicardoandClaude Opus 5.5 0d8dc7cc5a fix(bootstrap): leave headroom for the token estimate (#937)
Bootstrap estimates tokens as bytes / 4 and filled --max-input-tokens
and --chunk-input-tokens to the last estimated token. That estimate
undercounts non-English text and source code (about 40% on Portuguese
mixed with code, as #884 measured for consolidation), so a chunk sized
to fit a model's context window could overflow it on input alone.

Fill 80% of each budget by the estimate, the default consolidation
uses for the same undercount, in both the prune and the chunk packing.
No flag or config key is added; a run may plan more chunks than before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hbGY9oRywtM3Nz2NcXxUF
2026-09-26 22:26:59 -03:00
Felipe Neves RicardoandClaude Opus 5.5 7fd00eb766 fix(consolidate): skip multi-page updates to pinned pages (#934)
A multi-page batch writes each update at a path the model chose. When
that path named an existing pinned page, apply_batch replaced its body
and wrote the new version with pinned = 0, because neither the batch
loop nor the wiki write path looks at the page being replaced. Pinned
pages are documented as immutable to automation.

Skip such updates with a warning, next to the existing invariant-slot
skip. _slots/ pages are pinned automatically and keep their own
state/invariant regime, so they are not skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hbGY9oRywtM3Nz2NcXxUF
2026-09-26 22:09:27 -03:00
Felipe Neves RicardoandClaude Opus 5.5 de2719d8c9 fix(bootstrap): cap a lone chunk's output like any chunk (#928)
build_chunk_request picked max_tokens from the chunk count: 16K when a
run had several chunks, 64K when it had one. A repository small enough
for a single chunk under default chunking therefore got the 64K cap
meant for --chunk-input-tokens 0, and a 64K-context model rejects that
request before reading any input.

Key the cap on the chunking mode instead: every call under chunking is
a chunk that fits the chunk budget and gets 16K; only a disabled
chunk budget (one call with the whole pruned bundle) keeps 64K. The
--max-input-tokens help also stops claiming that its 150K default
leaves room for 64K of output in a 200K window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012hbGY9oRywtM3Nz2NcXxUF
2026-09-26 21:53:29 -03:00
Yi-111-a d2b9896876 feat(cli): reclaim-ledger-versions drops the pre-#660 ledger residue
#660 stopped the indexer from superseding an OKF-conformed ledger on every
hook append, but the rows it already wrote stay and nothing removed them:
`compact` deletes nothing, `forget-sweep` only hard-deletes decay
tombstones (only `decay` writes `superseded_at`), and `reindex` loses the
DB-only state. One reported store holds 6,539 versions of 101 live pages,
95% of its rows.

Adds `ai-memory reclaim-ledger-versions` (`POST
/admin/reclaim-ledger-versions`), an online command that deletes only the
superseded versions of paths whose *content* opens with a hook log entry.
Dry run unless `--confirm`; `--drop-latest` also removes each ledger's live
row; `--compact` rebuilds the FTS index and VACUUMs to return the bytes.

The content gate is the load-bearing part and is now one definition:
`ai_memory_core::log_ledger` owns the predicates, `ai_memory_wiki::ledger`
keeps the file-reading half and delegates, and the store decides from a
`pages.body` with no filesystem access. A real page a human named
`log-2026-09.md` keeps its whole version chain. Only `is_latest=0 AND
superseded_at IS NULL` rows are eligible, so decay-owned rows stay with the
sweep that reasons about them.

Derived FTS/entity/vector/link rows go with the page through the existing
ON DELETE CASCADE. The FTS delete trigger is stood down for the bulk delete —
its DDL read back from sqlite_master and re-executed, so it cannot drift
from the schema — and pages_fts is rebuilt wholesale, which is what keeps
this from re-tokenizing tens of gigabytes of ledger body row by row.

Reading the gate needs a bounded prefix, not the whole body: GATE_PREFIX_BYTES
lives with the predicate, shared by the file reader and the store.

Refs #914
2026-09-26 14:16:04 +08:00
Vinícius Andrade 28aa515292 fix(consolidate): unique session titles without starving the input floor
Identical harness runs were landing the same generic session-page title
and tripping the M8 duplicate-title lint. Prompt the model to name THIS
session and not reuse listed titles, suffix a colliding title with a
deterministic `(session <8-char-id>)` on both write paths, and keep the
uniqueness wording compact so the advertised 6000-token input floor
still projects observation bodies.
2026-09-26 00:25:47 -03:00
Maxsuel Einstein 25fabe4a13 feat(cli): repair-backfill-timestamps restores original start/end of backfilled sessions
Sessions imported by `backfill` before it carried per-event timestamps all
have started_at/ended_at on the import day, which flattens every
time-ordered view of that history. This adds an admin repair for sessions
already imported; it is independent of the companion fix to `backfill`
itself (#919).

The repair runs online, through the server's single writer, because it does
not need direct disk access (invariant 9). POST /admin/repair-session-times
validates a caller-supplied batch of (session_id, started_at, ended_at)
candidates in one transaction, scoped to (workspace, project) like
purge-session. It only rewrites a row whose current started_at postdates
the candidate's own transcript end, the bug's signature, so a correctly
hook-captured session or a re-run is a no-op (`not_flattened` /
`unchanged`). Without confirm it is a real dry run: the write runs and
rolls back. Out-of-scope sessions are reported not-found and left
untouched; negative, inverted or future times are refused; an open session
never gets an end time; and a confirmed batch writes one audit_log row with
every repaired session's before/after times.

`ai-memory repair-backfill-timestamps` is the thin client. It reuses
backfill's session discovery and the workstream transcript reader, takes
each session's earliest and latest event time, resolves native ids with the
new `SessionId::from_native` (now shared with the hook router instead of a
private copy), chunks the batch at the server's cap, and prints each
repaired session's old and new times.
2026-09-25 20:51:40 -03:00
luisfnicolauandClaude Opus 5.5 d1e50a9966 docs(security): record #708 slice 3 in the boundary inventory
Row 14 now lists the enforcing code and the adversarial tests, each
proven to fail with the choke point (18 tests) or the unscoped-read
SQL filter (9 tests) neutralized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aqFKAuGVkuBoewmpA3cx9
2026-09-25 19:17:10 -04:00
luisfnicolauandClaude Opus 5.5 9da6f52e37 docs: changelog, users guide and design status for #708 slice 3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aqFKAuGVkuBoewmpA3cx9
2026-09-25 18:51:09 -04:00
luisfnicolauandClaude Opus 5.5 57c98b44d3 feat(cli): grant and access-mode commands; 403 is final for captures (#708)
ai-memory user grant|revoke|grants, ai-memory project access|grants,
[auth] new_projects_restricted, and the native hook client drops an
event the server refused with 403 instead of retrying it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aqFKAuGVkuBoewmpA3cx9
2026-09-25 18:51:08 -04:00
luisfnicolauandClaude Opus 5.5 41ca270b98 feat(auth): enforce per-project access on every surface (#708)
Every database user is stamped as an AuthorizedViewer (never root) and
every surface attaches ScopeResolver::with_project_authz for them: MCP
tools, /api/v1 and the web pages, hook routes and captures. Read-shaped
tools that mutate need write; managed-run, workstream and session-id
entry points authorize the project the id resolves to; message queues
need write to deliver, pop and cancel. Captures into a project the
author may not write are dropped and counted (dropped_unauthorized).
Root-only /admin routes manage access modes and grants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aqFKAuGVkuBoewmpA3cx9
2026-09-25 18:51:08 -04:00
luisfnicolauandClaude Opus 5.5 84cc8f3347 feat(store): per-project creator, grant storage and guarded resolution (#708)
Slice 3 on top of slice 2's choke point:

- V69 projects.created_by; resolve_project_authz derives is_creator
  from it, so one principal per request is right for every project.
- ScopeResolver::resolve_existing_args[_traced] take a ProjectAccess,
  so read-shaped mutations can demand write; resolve_read_args* stay
  Read. resolve_many_existing authorizes every scope, and
  resolve_write_args records the creator.
- authorize_scope_for and *_guarded free functions for routes that
  name a project directly. A refusal names the project and both levels.
- Unscoped reads filtered in SQL before LIMIT with the choke point's
  rule (not restricted, creator, or any grant; global scope shared).
- Grant storage: grant/revoke/list, audit_log rows, access-mode
  setter, [auth] new_projects_restricted, grants follow a project move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014aqFKAuGVkuBoewmpA3cx9
2026-09-25 18:51:07 -04:00
Vinícius Andrade 198f5e0aea fix(restore): stage and verify the tarball before replacing the live data dir
`ai-memory restore --force` deleted the live `wiki/` and `db/` before the
tarball had been opened, validated or extracted, and before the restored
store had been opened. A truncated or corrupt archive, an entry outside
the allowed layout, or a snapshot the current binary could not open (a
backup taken by a newer release, a torn file) therefore left an empty or
half-extracted data dir with nothing to fall back to — at the one moment
the operator has no other copy.

The tarball is now extracted and validated into a staging directory
beside the live data, the staged store is opened there so pending
migrations run and the snapshot is verified, and only then are the live
`wiki/`, `db/` and (when the archive carries one) `config.toml` renamed
aside, the staged copies renamed into place, and the previous data
deleted. Every move is a same-filesystem rename; a failed move reverses
the moves already made, and a failed reversal reports the directory that
still holds the pre-restore data. Scratch directories are removed in
every outcome.

A successful restore behaves as before: `wiki/` and `db/` become what
the archive holds, `config.toml` is replaced only when the archive has
one, `logs/`, `models/` and `raw/` are never touched, `--force` is still
required for a populated data dir, and the sibling-process guard runs
first.

Regression tests drive the new `restore_data_dir` directly: the four
"keeps live data" cases fail under the previous order of operations and
pass here; success-path cases cover the populated, empty and
config-less archives and reopen the swapped-in store.
2026-09-25 18:05:36 -03:00
AkitaOnRailsandClaude Opus 4.8 433a19f3d5 chore(release): 2.4.1 — patch of accumulated fixes
Fixes-only patch on the 2.4.x line; additive items (input_token_safety_margin,
HTTP-exposure status field, status --workspace/--project scoping, jev adapter
docs) are reverted here and live on the release/2.5 feature line to keep the
2.5.0 version reserved for it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
v2.4.1
2026-09-25 18:01:14 -03:00
AkitaOnRails 42467259c3 Revert "Merge PR #911 into main"
This reverts commit b18cb1a3c4, reversing
changes made to 0e91c05b9b.
2026-09-25 17:59:27 -03:00
AkitaOnRailsandClaude Opus 4.8 e45db18a0e Revert jev-reranker-adapter docs (#873) for the 2.4.1 patch line
The additive jev adapter docs move to the release/2.5 (feature) line; the 2.4.1
patch stays fixes-only. Removes the doc, the example shim, and the
llm-providers pointer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 17:59:27 -03:00
AkitaOnRails 2c4fa06747 Revert "Merge PR #904 into main"
This reverts commit dfc7b6dc2b, reversing
changes made to 172f23085a.
2026-09-25 17:56:18 -03:00
AkitaOnRails 0fc60ffc24 Revert "feat(consolidate): add input_token_safety_margin to bound approximate token budget (#884)"
This reverts commit 7ff429935f.
2026-09-25 17:56:18 -03:00
AkitaOnRailsandClaude Opus 4.8 dde5806b23 Merge PR #912 into release/2.5
feat(web): add a root-only /web/pending triage page (#855)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 17:29:50 -03:00
AkitaOnRailsandClaude Opus 4.8 6c5692f0fa docs: add #913 CHANGELOG entry and the #919 occurred_at boundary row
#913 landed without the required CHANGELOG entry (merge gate); #919 added the
client-supplied event-time bound + its adversarial tests but not the
security-boundaries inventory row. Record both.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 16:56:40 -03:00
AkitaOnRails b18cb1a3c4 Merge PR #911 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm

# Conflicts:
#	CHANGELOG.md
2026-09-25 16:55:11 -03:00
AkitaOnRails 0e91c05b9b Merge PR #919 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm

# Conflicts:
#	CHANGELOG.md
2026-09-25 16:55:10 -03:00
AkitaOnRailsandClaude Opus 4.8 ced70b8e72 Merge PR #913 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 16:55:10 -03:00
AkitaOnRailsandClaude Opus 4.8 d5ebec30a9 Merge PR #915 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 16:55:10 -03:00
AkitaOnRailsandClaude Opus 4.8 ac0a2bd381 Merge PR #918 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 16:55:10 -03:00
AkitaOnRailsandClaude Opus 4.8 d227e422be Merge PR #916 into main
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
2026-09-25 16:55:10 -03:00
Maxsuel Einstein 191a8eea11 fix(backfill): store each event's original time instead of the import time
Every session/observation `ai-memory backfill` imported was stamped with
started_at/ended_at/created_at at import time, discarding each transcript
event's own timestamp even though the workstream adapters already captured
it (`NewWorkstreamEvent::occurred_at`) — a session imported today from a
transcript recorded weeks ago showed up as having happened today.

`NewSession` and `NewObservation` gain an optional `occurred_at`
(microseconds); the store falls back to `now()` when it is `None`, so live
hook capture is unaffected. This includes `admit_hook_session_event`'s own
session-row INSERT (the real path a live `/hook` session-start event takes,
separate from `begin_session_row`) — missing that one meant a backfilled
session's `ended_at` (original, past) could sit before its `started_at`
(import time).

The `/hook` body accepts an RFC 3339 `occurred_at`, read from the top level
of the body only (not the nested `payload`/`event`/`properties`/`info`/`path`
search other hook fields use, so a harness payload that happens to carry an
`occurred_at` key elsewhere in its own structure is never mistaken for this
field). It is client-controlled input arriving over the hook endpoint, so
`HookEnvelope::occurred_at_micros` bounds it (must be > 0 and no more than
five minutes ahead of server time) before it is trusted; anything else —
missing, unparsable, or out of bounds — resolves to `None` rather than
erroring, keeping hooks fire-and-forget. It is numeric metadata, not text, so
it never goes through the sanitizer.

Backfill validates each transcript event's own timestamp (an unparsable one
is treated as missing) and threads the resolved time through `map_event` and
into the session-start/session-end items: a missing timestamp inherits the
nearest preceding valid one, an event before the first valid timestamp
inherits that first one, and the session's start/end times are the earliest/
latest valid event time in the transcript rather than assuming it is already
time-sorted.

Because a backfilled session's `ended_at` can land well in the past, it can
sit below the auto-improve review watermark and the cross-session
experience-pass anchor (both keyed on `ended_at`), so a freshly imported
session may not get an automatic review pass until a newer session moves
those forward; an opt-in retention window measured from an observation's own
time can also make an old backfilled observation immediately prunable rather
than only after it ages in place; and the "most recently active project"
restart fallback, which looks at how recent observations are, may not pick a
project that was just backfilled. These are documented consequences, not
regressions introduced here — they follow directly from timestamps now being
honest.
2026-09-25 15:27:03 -03:00
Felipe Neves RicardoandClaude Opus 5.5 66b5a1a321 fix(okf): repair date-only stale_after on existing pages at startup (#917)
Pages written before the previous commit keep the bare date that
conform_frontmatter copied from expires_at, because conformance only
fills absent keys. serve now repairs them before the watcher starts:
the latest index row in place through the writer (same version row,
updated_at and generated.at untouched), then the page file with its
body untouched, in one wiki commit. A stale_after is repaired only when
it equals its date-only expires_at, the exact signature of the old
derivation; anything else was not written by ai-memory and is left
alone. conform_frontmatter applies the same rule, so a restore, a hand
edit or a reindex heals an affected page as well.

This is an idempotent startup pass rather than a registered
WikiMigration: a new migration name makes every older binary refuse
the wiki (NewerWikiFormat), which a patch fix should not force. The
repair adds nothing to wiki_migrations and is a no-op on a clean store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKb3cisaT5r2kiW9mYiWvt
2026-09-25 15:26:19 -03:00
Felipe Neves RicardoandClaude Opus 5.5 eaee835782 fix(okf): give a date-only expires_at an offset-bearing stale_after (#917)
conform_frontmatter copied expires_at into the OKF stale_after key
verbatim. expires_at also accepts a bare YYYY-MM-DD (documented in
docs/usage.md as end of day, UTC), so such a page was written, and
exported by export-okf, with stale_after: 2026-10-01. OKF v0.2 now
requires every timestamp to carry an explicit UTC offset
(knowledge-catalog #323), and its earlier text read a bare date as the
start of that day, a day before ai-memory's TTL hides the page.

The end-of-day rule moves into ai_memory_core::parse_expires_at_instant,
which the wiki's TTL validation and the OKF derivation now share. A
date-only value becomes the instant it names
(2026-10-01T23:59:59.999999Z); an RFC 3339 value is still carried
verbatim, so existing pages keep byte-identical frontmatter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKb3cisaT5r2kiW9mYiWvt
2026-09-25 15:26:19 -03:00
Caminhar 1843ec496a style: rustfmt the new scope tests 2026-09-25 14:41:06 -03:00
Caminhar ae60310abb test(admin,lint): adversarial scope tests for the scoped status and the broken_link finding 2026-09-25 14:25:58 -03:00
Caminhar 1b6ef53bb2 refactor: move the extraction fix out (split into a patch PR) 2026-09-25 14:25:58 -03:00
Caminhar e5262f8d22 fix(wiki): drop directory targets from link extraction
A link target whose final path component is empty (`sessions/`, as a
`relations:` value or as `[notes/](notes/)`) is a directory, not a page. The
relation route appended the extension anyway — `sessions/` became the literal
`sessions/.md` — while the body/wikilink route kept it extension-less. Either
way the row was stored with `to_page_id = NULL`, and no page write could ever
repoint it: `latest_page_id_for_link` matches a target path exactly, and every
page path carries `.md`. One live store held three such rows across two
projects, visible only as `links ... (unresolved: 3)` in `ai-memory status`.

Both routes now share one predicate (`last_segment_names_a_page`) and skip a
directory target, or a stem-less `.md`, with the existing warning.
2026-09-25 14:23:34 -03:00
Samir Hanna VerzaandClaude Opus 5.5 2bcfddd40b test(cli): keep a developer's harness store relocations out of the tests
Nine ai-memory-cli tests failed on a machine whose shell exports a harness
store relocation: CLAUDE_CONFIG_DIR fails five (doctor/backfill, unit and
e2e), CODEX_HOME two in run.rs, PI_CODING_AGENT_DIR two in removal.rs.
Each plants its fixture under a temporary $HOME, and the variable, which
the code honors correctly, sends the lookup elsewhere.

- e2e_support::hermetic() drops the eight relocations
  environment_session_dir_with reads, as it already drops AI_MEMORY_*;
  the one-off CODEX_HOME removal in backfill_failures.rs goes away.
- doctor::scan_local and backfill::collect_local_sessions delegate to
  _with variants that take the relocation lookup; production passes
  relocated_session_dir (the build_launch_plan expression both had
  inline), tests pass |_| None.
- The run.rs tests clear the Codex plan's session_dir, which also keeps
  the passthrough is_none() check from passing for the wrong reason.
- removal.rs removes PI_CODING_AGENT_DIR next to its existing removals.

No behavior change outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K7RSKQrakhnAfKjGaNaVR2
2026-09-25 14:23:00 -03:00
Igor Sant'AnaandClaude Opus 5.5 85edc2a5af fix(hooks): record OMP tool calls through the Pi tool schema
OMP was left out of the closed-tool agents introduced with the safe
tool-context capture (#190), so every OMP pre-tool-use/post-tool-use
observation was stored with the event name as its title and an empty
body: session pages, handoffs and the auto-improve reviewer only saw
the prompts.

The generated Pi extension is the OMP extension with its AGENT
constant renamed (build_pi_extension), so OMP posts the exact
tool/callID/args/output/isError payload Pi does. Route it through the
same metadata schema, closed-summary path and isError outcome; unknown
tool families still keep no output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:37:17 -03:00
Jayson ReisandClaude Opus 5.5 a84d66be60 feat(web): add a root-only /web/pending triage page (#855)
Add a server-rendered page under /web that lists the pending
auto-improvement proposals of all projects. The page has a project
filter and a sort. It shows the rationale, the proposed body, and a
warning for proposals that write under _rules/ or replace a page.

The approve and reject buttons post from the browser to the existing
/admin/pending-writes/{id}/approve|reject handlers with the session
cookie and the CSRF header. Admission, audit, attribution, and the
single writer stay the same. ai-memory-web adds no write route.

The page uses the same Capability::Admin decision as /admin, and
serve passes the trusted-proxy setting to it. A non-root session gets
a 403 page. The HTML auth redirect does not send that session to the
change-password form.

Two store reads feed the page. One counts pending proposals per
project, so the total and the project filter include every project.
The other lists proposals, optionally for one project, with a cap of
500 rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwU4vj4tZLYVLR7vEKeh2W
2026-09-25 18:11:02 +02:00
caminhar-deus 444170607d Merge branch 'main' into fix/directory-link-targets 2026-09-25 11:50:19 -03:00