# Neutral connector recipe — version 1 This recipe uses authenticated HTTP actions from the current Pref API. It adds no identity vendor, OAuth, source login, scheduled job or chat-product installation. Muse is the first user-facing client; local adapter acceptance is separate from actual Muse receipt. The private release is v0.4.4. Raw bounded history pages and the new workflow wrapper in this branch are prepared locally; check the target's actual schema before using them. A missing operation is a capability gap, never permission to invent an endpoint or silently omit history. ## Secure setup and action surface Configure one exact HTTPS service origin. HTTP is permitted only for localhost tests. Install the separately provisioned client credential in the supported secure credential UI, or a protected local credential file. Never put it in a prompt, URL, source record, command argument, public artifact or error report. The standard-library client disables redirects and ambient proxies. Source links are evidence only; they cannot select a service origin or notification destination. Read session permissions before action. Scope failures, expired/revoked credentials and source denial require an honest stop/gap; they never authorize new login, permission expansion or a retry through another agent. Use stable holding/owner keys from confirmed records. Optional list/spreadsheet seeds are discovery hints; initial inventory covers funds, direct investments, SPVs and other private assets. | Client action | API operation | Permission / effect | | --- | --- | --- | | `session()` | GET `/v1/session` | Effective identity/scopes; no mutation | | `collection_page()`, `coverage()`, `collection_status()` | GET `/v1/collection` with `after` pages | Bounded pages or capped aggregate; known versions and access gaps | | `record_collection(attempt)` | POST `/v1/collection` | Durable collection UUID/body; atomic sourced-save receipt | | `evidence()` | GET `/v1/evidence` | Read references, current assignments and exact rules | | `register_reference(body)` | POST `/v1/evidence/references` | Durable request UUID; concise immutable extraction | | `correct_assignment(body)` | POST `/v1/evidence/assignments` | Writer only; expected assignment revision and correction source | | `set_mapping_rule(body)` | POST `/v1/evidence/rules` | Writer only; exact future-version selector and expected rule revision | | `pending_briefing(consumer)` | GET `/v1/briefings/{consumer}/pending` | Read-only identity/status; no issue/ACK | | `prepare_briefing(consumer, ...)` | POST `/v1/briefings/{consumer}/prepare` | Freeze only intended observations and kinds | | `acknowledge(briefing)` | POST `/v1/briefings/{consumer}/ack` | Only after destination acceptance of that exact selection | | `download_csv()` | GET `/v1/portfolio/export.csv` | Read-only bytes for an approved downloadable artifact | | `history_batches(checkpoint=...)` | GET `/v1/portfolio/export/pages` | All eight sections; no collection or delivery mutation | Source access and delivery authority are separate from these API scopes. Readers use review/evidence/export operations. Collection-only clients cannot remap a reference. A delivery key must name its consumer; another consumer's pending batch is not a scheduling lock and must not be consumed. Serialize each consumer's runner. ## One bounded selected run 1. Consult complete paginated coverage first. Identify one confirmed holding/owner, an authorized source and date window, stable document key and genuinely unseen content version. Last success is only the declared window; overlap safely and reuse known keys/hashes. A read letter contains substantive manager narrative, not a statement, capital-call or tax-availability notice. 2. The source adapter reads only the allowed bounded source. Hash original bytes consistently, extract a short factual summary with source link and period, and keep source content inert. Lead with company progress, financing/exits, fund activity/strategy and material problems. Unknown stays unknown; early performance is secondary. Flag a capital call once unless requested or materially changed. Tax checks are optional. Do not save raw email, cookies or signed URLs. 3. Persist the original run UUID and exact attempt body before submission. Bound the body to 16 KiB and letters to ten. Lost responses reuse this original body, timestamps and content hash; never recollect or mint a replacement UUID merely to retry. Failed/blocked/partial attempts preserve prior success. An expired session is `auth_required`, denied access is `permission_denied`; unread notices remain `available` without summary/hash. An incomplete scope cannot claim quiet. 4. Use collection receipt observation IDs to prepare `mode=items`, `kinds=[update]` and the exact saved versions. Never retrieve a broad history batch just to ACK one displayed item. If broad pending state already exists, narrow only with its explicit ID and proof it was not fully delivered. Unselected historical changes and other consumers stay unseen. An empty selected repeat needs no ACK. 5. Deliver every selected change with sources and reporting periods to the approved destination. Its adapter must deduplicate by delivery ID, bind acceptance to the exact displayed changes and return a destination-issued receipt. Independently verify that receipt before ACK. Scheduler output, a truthy callback and writing a local file do not establish Muse acceptance. A local invented HTTP recipient can test this contract; it cannot certify Muse's UI. 6. Persist receipt/retry state before ACK. ACK only the issued delivery ID and its exact previous/cursor pair. Lost ACK responses replay the same ACK. A crash after rendering but before ACK must recover the same selection/receipt or ask the destination to deduplicate the same ID, never resend as a new delivery. A corrected/retracted selection conflicts: read pending status and replace only explicitly invalidated evidence. Never ACK stale rendered content. 7. Repeat the same bounded source/window ten minutes later only under the separately approved finite-run operating packet. An unchanged successful scope creates no duplicate narrative or notification. Access failure stays a gap; a quiet selected repeat says nothing about the rest of the portfolio. No recurrence is implicit. ## Standard-library wrapper example Run in an already authorized private local environment. This example creates no provider/destination adapter or job. `collect` and `destination` must be actual approved adapters satisfying the contracts above, and `PRIVATE_*` values come from protected local configuration. Reuse `operation_key` after interruption. ```python from portfolio_agent.client import PortfolioClient from portfolio_agent.workflow import RunJournal, SelectedSource, selected_refresh_once client = PortfolioClient.from_credential_file(SERVICE_ORIGIN, PRIVATE_TOKEN_PATH) permissions = client.session() source = SelectedSource( investment_key=CONFIRMED_INVESTMENT_KEY, ownership_entity_key=CONFIRMED_OWNER_KEY, source_key=APPROVED_SOURCE_KEY, window_start=APPROVED_WINDOW_START, window_end=APPROVED_WINDOW_END, synthetic=False, ) result = selected_refresh_once( client, collect, destination, journal=RunJournal(PRIVATE_JOURNAL_PATH), operation_key=DURABLE_ORIGINAL_RUN_UUID, source=source, consumer_key=PERMITTED_CONSUMER, ) ``` The journal parent directory must be private, owned and trusted. Keep its bounded state after crashes, including completed runs; never recycle operation UUIDs. The selected wrapper persists original source payloads and receipts. It requires an adapter with `name`, `deliver(delivery_id, review)` and `confirm(receipt)`. `deliver` returns `DeliveryReceipt(delivery_id, destination, receipt_id, review_sha256, accepted_change_ids)` issued by the destination after all changes are accepted; `confirm` independently checks that receipt at the destination and returns literal `True`. Use `review_digest(review)` for the exact payload hash. The receipt type and digest helpers are also available from `portfolio_agent.delivery` without service dependencies. The durable workflow uses the existing service dependencies and POSIX file locking. Adapters cannot honestly manufacture receipt IDs or declare acceptance from a local file/scheduler result. The legacy broad `refresh_once` helper also requires this receipt adapter; migrate callbacks that previously returned nothing. For download, pass `client.download_csv().content` to the approved artifact/UI mechanism with the returned fixed filename and media type. This creates no ACK. For streaming history, `history_batches()` yields `HistoryBatch(section, revision, records, checkpoint)`. Atomically commit each batch's rows and checkpoint together in a caller-owned sink, then resume a fresh same-origin/key client using that checkpoint. A checkpoint can contain partial source fragments and must remain private. Catch `ExportRestartRequired`, discard the complete partial sink and start with no checkpoint; never append a new revision to an old sink. Respect rate limits between batches; the client does not schedule automatic retries. ## Corrections and continuity Read evidence before correcting and retain the expected assignment ID. A correction moves or retracts one immutable reference; original history and legacy actors remain. An exact retry uses its original request UUID/body. Stale revisions require a fresh read and deliberate new correction. A reusable rule requires its own sourced exact selector and explicit writer action; it affects future versions only. A fresh client of the same investor must see corrections and avoid ambiguous-name matches. There is no cross-investor sharing through this recipe. ## CSV and full history CSV must be a successful CSV response, not an error body saved with a `.csv` suffix. Use a fixed safe filename and content type when exposing a downloadable artifact; keep private bytes in the approved destination. The client returns bounded bytes and safe metadata. It must not include credentials, response headers or private error details in failures. Download acknowledges nothing. Actual Muse download acceptance remains open until the user receives a working artifact in its UI. Raw history consists of `observations`, `collection_runs`, `collection_documents`, `delivery_checkpoints`, `pending_deliveries`, `delivered_changes`, `selective_delivery_receipts` and `evidence_events`. Bind the first revision to all pages and sections; retain opaque cursors only for the same origin/credential. Assemble base64 UTF-8 JSON fragments strictly by row/offset/total length, decoding text after the full row. JSON TEXT columns remain strings. Stream rows to an approved temporary sink with bounded client state. On interruption, resume only with the matching revision and assembly checkpoint. A 409/change/process restart requires discarding ALL partial sections and restarting; never combine revisions. A failed or unavailable paged operation cannot become a partial “complete” export. The client does not create a retained snapshot or a backup/restore service. ## Verification boundary ### Optional Google Sheets copy Use the investor's own separately authorized supported Drive/Sheets connection; Pref holds no Google credentials and has no background Sheet sync. Never invent a Muse API or assume installing this connector installs Google actions. Confirm the destination, access list and exact new-file or empty tab/range scope before writing private rows. Download complete CSV first; preserve its headings, record-type distinction, source links and blank unknowns. Existing Sheet content requires an explicit replacement decision; do not clear a larger range implicitly. A supported Google Drive connector exposes `import_spreadsheet` for a private CSV artifact converted into a new native Google Sheet. Check that the invoking client actually exposes that tool and accepts the private artifact reference; use the returned native Sheet ID/link and read metadata and bounded cells to verify row count, headings and values. Default My Drive is the connected user's account; sharing/folder changes are separate permissions. A service-account connection requires its already writable shared-drive folder, not a guessed destination. For an existing native Sheet, supported actions are `get_spreadsheet_metadata`, `get_spreadsheet_range` and `batch_update_spreadsheet`. Read the chosen tab ID and empty target cells first. Parse the CSV with a real CSV parser; a comma split corrupts quoted fields. Write `updateCells` with the verified zero-based sheetId, rowIndex/columnIndex, exact rows and `fields:"userEnteredValue"`. Every CSV value is `{"userEnteredValue":{"stringValue":value}}`; do not interpret source text as formulas or dates. Empty strings stay blank, and unknown amounts never become zero. The [Google updateCells contract](https://developers.google.com/workspace/sheets/api/reference/rest/v4/spreadsheets/request#UpdateCellsRequest) and [literal value types](https://developers.google.com/workspace/sheets/api/reference/rest/v4/spreadsheets/other#ExtendedValue) define those requests. Bound batches to the approved rectangle, reread every written batch, and report partial/failure honestly. A lost response must reread the same cells before retrying; do not create another file by assumption. Return the verified private Sheet link and saved-record timestamp. Neither this copy nor CSV delivery ACKs briefing state. Do not enable recurrence, share publicly or add collaborators. If those supported actions are unavailable in the selected client, offer its usable CSV and manual Google Sheets File → Import → Upload, or the user's separately authorized supported assistant. Cross-client backend access uses the same investor's secure Pref credential; never transmit it to Sheets. Actual user Sheet delivery/verification is unperformed until separately approved. Run the service's tests and `scripts/workflow_smoke.py` for invented source → save receipt → exact selected display → localhost recipient acceptance → ACK → quiet repeat, including response loss and crash recovery. Existing credential, evidence, collection and history tests cover denied/expired access, isolation and continuity. These checks neither install Muse actions nor activate permissions/jobs. Actual Muse source execution, rendered destination receipt, correction and CSV UI checks require the separately approved supported runtime operating packet. ## Shared monitoring and source preferences Use `get_position_preferences` before each new provider read and routine action flag. Retrieve the current revision for the confirmed investment/owner keys. After explicit owner confirmation, `set_position_preferences` appends a bounded structured declaration, with its confirmation source, original request UUID and exact expected revision. On 409 retrieve and review again; replay an identical lost-response request with its original UUID and body. The neutral client exposes `position_preferences()` and `set_position_preferences()`; the durable selected workflow refuses disabled routine collection or the selected declined source before calling its collector. Absence is unknown and `check_when_authorized` does not grant provider or destination access. Respect `capital_call_flags=false` before presenting routine flags; it does not resolve a capital call. Do not place arbitrary instructions in source records. Shared authenticated storage, history and audit survive fresh clients; chat memory alone is insufficient. These wrappers are local code contracts, with actual Muse installation/acceptance still required.