Skip to content

CONTRACT: fix(ui-server): complete shares whose retained file is linked from the note's folder - #3376

Merged
schlessera merged 3 commits into
mainfrom
fix/3353-retained-file-share-path
Nov 3, 2026
Merged

schlessera merged 3 commits into
mainfrom
fix/3353-retained-file-share-path

Conversation

@schlessera

Copy link
Copy Markdown
Owner

Closes #3353

What changed

complete_share now accepts a retained file's Markdown link when the link resolves to the retained path from the filed note's own folder. That covers document-relative links (../../assets/documents/x.pdf from a/b/note.md), links starting with / (from the brain root) and root-relative links from a root-level note, including percent-encoded or <…>-wrapped paths with spaces. Inline links, images and reference definitions count. Links inside code spans or fenced blocks, and wiki-links, do not. A refusal now names the expected target and gives an example link that works from that note. When several receipts cover one path, the receipt that matches the current content proves it. If none matches, the refusal names the stale receipts. A filing turn that ends without completing no longer spends the share's retry attempts. A share that earlier turns left failed can be reclaimed by its filing session, and that reclaim retires its "Queued work exhausted its retries" Action, which would otherwise hold back staging cleanup. The tool description, the MCP contract doc, the share skill and the async-collaboration decision now state the accepted link forms.

Why this shape

  • Wiki-links don't count. The brain resolves wiki-links only to Markdown documents (createWikiLinkResolver, packages/core/src/lib/indexer/links.ts:46-103), so [[x.pdf]] is not a link the brain follows.
  • Attempts. Of the issue's two options, interactive claims stop counting attempts (countAttempt: false on the store's claim transition). The attempt budget limits autonomous retries, and the autonomous runtime and triage already skip bound interactive shares (runtime.ts:76, triage.ts:206). Each bound turn is the user retrying, so counting it only turned refusals into a permanent dead end and raised a misleading dead-letter Action. Autonomous claims still count (the existing dispatcher-first assertion still expects attempts: 1). Shares already exhausted by older servers are recovered by their bound session rather than left stuck.
  • Superseded receipts. Rather than ordering receipts by age, each path is proven by whichever passed receipt matches its current bytes. That needs no new ordering column and still refuses a path that no passed receipt matches.

Existing test fixtures linked [Original](assets/ithaca.pdf) from notes/. That link is broken when rendered, so the bug was baked into the tests. They now use ../assets/ithaca.pdf. Their attempts expectations for interactive turns move from 1 or 2 to 0.

Proof

Tested head 91bcca2d1, base origin/main @ 3984040f1.

bun run check:pr --base origin/main   # exit 0: lint/leakage, env docs, changesets, affected fast invariants (1215 pass)
NODE_OPTIONS=--max-old-space-size=8192 bun run typecheck   # exit 0
bun run test packages/ui-server/tests/interactive-share.test.ts packages/ui-server/tests/share-links.test.ts   # 19 + 2 pass

Failing first (new tests against origin/main's src/, with the tests kept):

  • brain-root-absolute and URL-encoded links… → Retained binary must be linked by the filed document
  • a retained PDF linked document-relative… → LINK_RESOLVING_ELSEWHERE_IS_REFUSED_NAMING_THE_TARGET (the old message names no target)
  • three filing turns that end without completing… → Turn refused before its backend started: Share is not ready for recovery
  • a share that earlier turns exhausted to failed… → Inbox attempt limit reached (this fails in its own setup, because main has no uncounted claim. It is not an independent receipt.)

Mutations (each restored afterwards):

  1. Swap the resolver back to the substring check d.content.includes(\](${disposition.path})`). The document-relative completion then fails with Retained binary must be linked by the filed document: expected a Markdown link that resolves to assets/documents/ithaca.pdf from voyages/ithaca/harbor.md…`. The root-absolute case and the fourth-turn case fail the same way.
  2. Drop countAttempt: false from start(). The fourth turn then fails with Inbox attempt limit reached, and the legacy-failed recovery test fails the same way.

Wider runs on this host. bun run test packages/ui-server/tests packages/ui-sdk/tests: 5482 pass, 357 fail. On untouched origin/main the same 72 files give 358 failures. Every failure but one also fails on main: they are native worker-sandbox tests, and this host refuses unprivileged user namespaces (unshare: write failed /proc/self/uid_map). The one branch-only failure, http-mount-pool.test.ts ("mount disposal joins real owned Unix HTTP connections…"), passed 3 of 3 on rerun. It's a flake under load and doesn't touch inbox code. Hosted CI has the full runtime.

Provider history. The change touches a turn path (the complete_share description). The keyless guards packages/ui-backend-{claude,pi}/tests/provider-history.test.ts fail on this host and identically on untouched origin/main, because the Claude Code subprocess exits 1 in this sandbox. Hosted CI must give the passing receipt. The description is static per build, so within a session the tool list stays byte-identical.

Checklist

  • Focused local preflight/behavioral receipts and complete selected hosted proof passed; actual head/base/job checkout evidence is in Proof. (local receipts above; hosted proof is pending confirmation)
  • Acceptance criteria from the issue are all met, or the gap is named below.
  • The PR and completed/advanced issues have verified release milestones under docs/process/github.md. (0.45.0 set on both; read back before merge)
  • A changeset is included, or this changes nothing a consumer can see.
  • No personal data anywhere in the diff — the leakage gate covers the whole tree.
  • The integration contract index or relevant component is updated in this same commit, or nothing in the machine surface moved. (docs/integration-contract/mcp.md, CONTRACT: commit. The description wording changes and accepting more link forms is additive. The input schema is unchanged.)
  • Docs under docs/ reflect the change, or nothing documented moved.
  • Provider history stays append-only (docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed. (guard can't run on this host, and fails identically on main; needs the hosted receipt)

Anything left open

The provider-history guard and native-runtime receipts have to come from hosted CI. Otherwise nothing.

🤖 Generated with Claude Code

schlessera and others added 2 commits November 2, 2026 21:11
…ed from the note's folder

complete_share matched the literal text `](assets/...)`, so a working
document-relative or brain-root-absolute link from a nested note was
refused, and every refused turn spent one of the share's three attempts
until no later turn could complete it.

- Resolve each Markdown link destination (inline, image, reference
  definition; percent-encoded or <...>) from the filed document's folder
  and compare it to the retained path. Code spans, fenced blocks and
  wiki-links do not count. The refusal names the expected target and an
  example link.
- When several receipts cover one path, the one matching its current
  content proves it; otherwise the refusal names the stale receipts.
- Interactive share claims spend no attempts and ignore retry backoff.
  A bound session can reclaim a share earlier turns left `failed`, and
  its exhausted-retries Action is retired.
- The complete_share description, the MCP contract doc, the share skill
  and the async-collaboration decision state the accepted link forms.

Closes #3353

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@schlessera schlessera added this to the 0.45.0 milestone Nov 2, 2026
@schlessera schlessera added type: fix Something is broken and must behave the way it is documented. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. contract Touches the integration contract: CONTRACT: commit, contract doc updated in the same commit. area: skills labels Nov 2, 2026
@schlessera

Copy link
Copy Markdown
Owner Author

Collision report at head 91bcca2d1 (bun scripts/collisions.ts --json --pr 3376): no path or unknown collisions; closure (shared test-graph) overlap only, with open PRs #3054, #3056, #3059, #3069, #3116, #3292, #3293, #3295, #3309, #3316, #3317, #3320, #3325, #3335, #3336, #3341, #3342, #3343, #3345, #3352, #3360, #3362, #3365–#3374.

@schlessera
schlessera marked this pull request as ready for review November 3, 2026 00:03
@schlessera
schlessera merged commit c147ba4 into main Nov 3, 2026
42 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: skills area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. contract Touches the integration contract: CONTRACT: commit, contract doc updated in the same commit. type: fix Something is broken and must behave the way it is documented.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui-server: complete_share refuses a retained file linked by a document-relative path, so shared PDFs never complete

1 participant