CedarCopilot wants to merge 3 commits into staging from fix/document-address-resolution
Live on prod since Mon, 1 of 4 behaviors working
Since the deploy, 49 prod OTEL spans matching contact/{personKey}/profile ran with 0 error spans. Libra found no comparable traffic on this surface in the 44h before the deploy, so there is no before-and-after contrast. Confidence is medium because there was no pre-deploy.
Only 11 prod OTEL spans matching read-document reached Libra since the deploy, below the 20-span floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Only 11 prod OTEL spans matching read-document reached Libra since the deploy, below the 20-span floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Only 8 prod OTEL spans matching writeDocument reached Libra since the deploy, below the 20-span floor for calling a change verified. Nothing is failing; there is not yet enough traffic to confirm it.
Libra has verdicts on 1 of 4 tracked behaviors on prod; 3 are still being checked. Libra checks hourly for 3 days after each deploy.
Two defects producing one error string , Unrecognized path prefix in "<x>" , at ~100 failures a day across ~25 users:
| Day | Failed calls | Users |
|---|---|---|
| 2026-08-24 | 98 | 25 |
| 2026-08-21 | 102 | 27 |
| 2026-08-18 | 72 | 22 |
| 2026-08-17 | 78 | 23 |
Document ids are first-class in Cedar: <ref id="…"/> is the playbook's own reference format, read-document returns an id in its output, and list-documents returns one per row. But every document tool accepted only a path.
So an agent holding an id had no way to turn it into content. It called read-document({ path: "<uuid>" }) , the only inference available to it , and failed. This was never the model guessing; no call it could have made would have worked.
Resolving the ids agents actually asked for over 7 days: 40 distinct email-style.md, plus organisation/knowledge-base, subagents/strategist.md, resources/templates.md, and a customer's positioning-customs-broker.md. For email-style.md the drafting skill's own skillDependencies injection covered it, so it self-healed. Everything else was advertised to the agent, attempted, and silently never read , capability loss, not a visible error.
read-document now accepts a bare id. Rather than adding a by-id read branch, the id is translated to a path and the existing path route runs unchanged , so content shortcuts (<uuid>#sections, a shape observed failing in production), the crm-updater view, local overrides, and every per-scope access rule still apply. A by-id branch would have had to re-implement user/ ownership, thread/ privacy, and the org-scoped playbook allowance, and would have drifted from them. The lookup is org-scoped, so a foreign id reports not-found rather than leaking a path.
contact/ scope was never taught to the resolvercontact/{personKey}/profile shipped with contactProfilePath, isContactDocPath, unit tests, a smoke script, and a meeting-prep instruction telling agents to read then rewrite these documents. But parsePath had no contact/ branch, so every read and write threw. Constructing a path and resolving one are different things, and only the first was ever exercised.
Production holds exactly one contact/ document , contact/smoke_contact_example_test, 0 bytes, written by the smoke script in July. The scope has never worked.
Resolved org-scoped (userId: null), like conversation/ and thread/: a contact profile describes an external person, not the Cedar user, and its stated purpose is reuse across every deal that person touches. Verified consistent with both downstream classifiers, which already treated it that way by fallthrough , scopeFromPath (files.ts:76) returns {type:'org'} and queryUserIdForDocumentPath returns null for non-user//thread/ paths.
An earlier revision of this PR fixed (1) by changing the playbook renderer to advertise the path instead of the id. That was narrower and worse: it fixed one producer of ids while leaving the tool's own output and list-documents still handing agents an address the tool rejects. Fixing the tool covers the whole class, and needs no prompt change , so no prompt-cache invalidation across users, and no churn in the renderer tests or the golden-output comparison.
mastra/tools/document/__tests__/readDocumentTool.by-id.test.ts , an id returns content; the resolved path (not the id) is what reaches the reader, so scope rules still gate it; <uuid>#sections works; a foreign id is not-found with no path leaked; normal paths and #shortcuts are untouched.services/documents/__tests__/contact-paths.test.ts , contact paths resolve, are org-scoped, and an unknown prefix still throws while naming contact/ as valid.Verified: types clean · deps:check clean (1532 modules) · 6509 tests passing across src/services, `s
Libra has not measured any production surfaces for this change yet.