v0.8.21: additional self hosting documentation, workspace fork improvements, tiptap and tailwind dep upgrades - #7446
v0.8.21: additional self hosting documentation, workspace fork improvements, tiptap and tailwind dep upgrades#7446waleedlatif1 wants to merge 31 commits into
Conversation
* fix(pdf): keep PDF.js external in production * chore(pdf): align config test filename
* improvement(usage): show up to 50 rows per tab * improvement(usage): allow expanded views up to 100 rows
…t rename (#7414) Chat title generation is fired at turn start and resolves asynchronously, but its write was unconditional (`WHERE id = chatId`), re-checking nothing. The guard that decides whether to generate reads a snapshot taken at request start, so a title generated from that snapshot could land long after the user renamed the chat and overwrite the rename — a classic check-then-write race. A user who renames a chat shortly after their first message could silently lose the rename. Make the write conditional on the title still being unset, so the generated title fills an empty slot and can never overwrite an explicit one, and skip the follow-up notification when the write lost the race (so we never announce a title the row no longer holds). Both auto-title call sites are fixed: the interactive turn path (lib/copilot/request/lifecycle/start.ts) and the inbox task path (lib/mothership/inbox/executor.ts), which had the identical unconditional write.
…ag (#7385) * feat(search): add Sim Search mode, connectors tab, and cited sources - Build/Search mode switcher in the chat composer; Search mode swaps the suggested actions for Connect Sim Search rows that open the OAuth modal - Search tab beside Integrations and Skills: a personally scoped catalog of knowledge-base connectors with connect-in-place rows and a connected-credential page without sharing or members - <source> special tag contract: inline citation chips at the citation point plus a scrolling, right-faded sources strip under the reply - chip `shape` variant so a fully round chip is chip chrome, not a class override - keep the connector catalog out of the workflow editor's module graph and record the new routes in the boundary baseline * fix(search): harden source parsing, availability, and provider matching - validate <source> urls by parsing them and requiring a host, so a malformed value never renders a dead citation link - gate Search-mode suggestions on deployment OAuth availability, sharing the predicate with the Search catalog - match connected credentials across a service's additional provider ids (Salesforce sandbox) and count them in connector telemetry - collect footer sources from the rendered text segments, covering a block-less message's fallback text and excluding subagent lanes - use a distinctive citation-link sentinel and an absolute import for SuggestedActions - teach the email tokens transcription test the composed chip geometry * feat(knowledge): add document ACL schema and members-mode tables Adds document.acl (sorted access tokens, fast default '{ws}') with a partial GIN index and a token-shape CHECK, source_modified_at, the members-mode columns and lease on knowledge_connector, a real FK on credential_id (left NOT VALID until script migration 0011 has remapped legacy account ids), and three tables: knowledge_connector_member, knowledge_document_observation, knowledge_connector_member_sync_log. The document contract stops passing unknown keys through so the new column and storageKey never reach clients. * feat(knowledge): add access scope, token, and predicate libraries Pure building blocks for document-level access: the token vocabulary and its single sorting/validation site, the subject token derived from a managed credential row, the read-side predicate (scalar binds, literal ARRAY), the per-principal scope resolver with a lazy memoising provider, the branded system scope, and the members-mode sync limits. Nothing consumes them yet. * feat(knowledge): enforce document access scope on every read path Every document loader, chunk list, tag usage, search leg, metadata lookup, and knowledge-base file serve now takes a required access scope and applies the shared ACL predicate. Context resolvers attach a lazy, memoised scope provider derived from the acting principal and load documents through it, so a document the caller may not read is absent from the first read. Raw v1 routes and legacy helpers resolve the same scope; the outbox handler is the one branded system-scope caller. Vector legs run inside a transaction that enables pgvector's relaxed iterative scan (with a 42704 fallback) so a narrow ACL still fills topK. Forked workspaces copy documents as '{ws}'. Behaviour is identical today: every row carries the default '{ws}' token. * refactor(knowledge): extract connector sync primitives from the sync engine Splits the content sync engine into four modules without changing behaviour: sync-lock (lease predicates, heartbeat, and a SyncRunLease the stages run under), sync-persistence (storage and document-row writes), sync-primitives (the pure decision functions plus the six orchestration stages: listing pass, owned-corpus load, classification, hydrate/persist/dispatch, deletion reconciliation, and the stuck-document sweep), and sync-engine (lock acquisition, terminal state, and executeSync composed from the stages with its original closures). The stages take the token, hydration, and lease as inputs so a second engine can run the same guards under a different lease. Tests keep every assertion; only import paths moved with the symbols. * feat(knowledge): plumb credential groups into knowledge connectors Registers Google Drive as a Credential Group provider (provider list, adapter registry, and the Google managed-OAuth policy), marks the Drive and Confluence connectors as permission-scoped listings with the config fields that would cap them, and extends the Credential Group policy with a knowledge_connector principal and a credential_group:OptionId condition so a group can grant one connector the credentials collected under one option. The policy codec gains one KnowledgeConnectorCredentialAccess statement per option, decoded by SID; workflow-access edits now carry connector grants through untouched. Knowledge-side helpers grant and revoke the binding under the policy's revision CAS, mint a member token only when the policy names the connector for the credential's option (audited per access), enumerate an option's credentials in every state for membership reconciliation, and validate a members-mode binding before anything is touched. Nothing calls them yet. * feat(knowledge): add the members-mode sync engine Adds the engine that crawls a knowledge connector once per Credential Group member with that member's own token and derives each document's ACL from who observed it. A run takes the member lease (mutually exclusive with the content engine's), finishes any pending access rewrite, mirrors the option's credentials onto member rows (active or suspended, tokens rewritten, departed members dropped), then claims due members one at a time until its budget ends: each member's listing is admitted into a first-writer-wins union, every listed document is hydrated once through its observers' tokens, observations are recorded, removals follow only a full, complete, non-suspect listing, the affected ACLs are rematerialised from the observation graph, and the document lifecycle tombstones the unobserved, resurrects the re-observed, and purges the long-unobserved under the lease. The run re-dispatches itself while members remain due. Around the engine: a member queue mirroring the content dispatch (token, lease, lost-hand-off release), a Trigger.dev task on its own queue, a cron scheduler that reclaims stale leases and lost dispatches, closes orphaned run logs, sweeps members whose crawls stopped, and dispatches due connectors, and the Docker and Helm cron entries. The content engine and its dispatch refuse members-mode connectors, workspace-mode syncs restore the workspace ACL on completion, document writes carry the ACL each mode needs, the hard-delete guard can verify the member lease, and the Drive and Confluence connectors report a scope the member cannot reach as an empty listing rather than an error. Nothing enters members mode yet. * feat(knowledge): let connectors enter members mode Wires the members-mode engine to the connector surface. A connector can be created in members mode (admin only) bound to a Credential Group option, or switched between modes through a new admin-only access operation and route: the switch takes the connector's content lease, rewrites the documents' ACLs to the new mode's default in bounded batches (finishing under the member run when the request budget runs out), grants or revokes the option's credentials in the group's policy, flips the mode, and queues the first run of the new engine. Members-mode source edits refuse listing caps and queue a member run, a manual sync routes to the member queue, keep-documents deletion is refused, and deletion revokes the grant. Contracts carry the access mode, the binding, the member-sync state, the member summary, and member run logs; the member scheduler and dispatch honour a paused connector. * feat(knowledge): per-member access controls in connector settings Add an Access section to the add and edit connector modals so a workspace admin can sync a permission-scoped connector once per credential-group member. The connector card shows the per-member badge, member sync status, and a member run history in place of the content sync history. * feat(knowledge): note member-scoped retrieval on the knowledge block Knowledge bases report whether a live connector syncs per member, and the knowledge block's selector explains that such documents depend on who triggers the run. * feat(knowledge): per-member change feeds for members-mode connectors Connectors may open a change feed over a member's view of the source. Google Drive reads changes.list, where a lost share arrives as a removal, so a member's access is withdrawn without waiting for a full listing; Confluence filters its CQL listing by lastModified for incremental content refreshes. Each member stores where their feed resumes, and a member with an open feed is relisted fully only weekly as a check. * feat(knowledge): hybrid retrieval by default with a source-recency boost Search fuses the full-text and semantic legs unless a caller asks for vector-only, at every surface: internal, v1, v2, the knowledge block, and Sim's own knowledge tools. Connector documents now record when the source last changed them, and a recently modified document edges past a stale one of similar relevance. * feat(knowledge): gate permission-aware knowledge behind a flag and harden the stack Add the knowledge-member-access feature flag (workspace allowlist and platform admins via AppConfig; KNOWLEDGE_MEMBER_ACCESS off-AppConfig). It gates members mode at creation and switching, the member engine, and the hybrid-by-default retrieval with its recency boost; the connector modals read it from the workspace host context. The knowledge block's retrieval mode gains an Automatic option so the server's default applies. An admin with no credential group for the connector's provider can create one from the Access field; the group is named after the connector and members are invited from Settings. Review fixes: manual-only connectors no longer re-dispatch member runs forever; ACL materialisation is idempotent so a run that died mid-way is repaired; incomplete listings retry at once only when they can resume; suspended members keep their observations; a scope the member cannot reach closes their change feed; the workspace ACL restore runs inside the completion lock and only for workspace-mode connectors; the mode switch grants before rewriting and flips in one transaction, clearing listing caps; minting checks group, option, and enrollment status; connector document lists, bulk select-all operations, and tag usage stats apply the access scope; the connector card reads the member engine's status; the edit modal applies an access switch on its own instead of racing the settings save. * test(knowledge): cover retrieval default resolution behind the flag * fix(knowledge): judge members mode by the workspace alone and fail closed when the flag is off Members mode is gated by the workspace at creation, switching, in the member engine, and in the workspace host context, so no gate can open a mode another will refuse; the flag's admin clause now affects only the retrieval default, and search passes the signed-in person rather than a billing owner or key creator. Member tokens count only where the feature is on, so turning it off hides member-scoped documents on the next read. New and reactivated members are due at once so a drain re-dispatches until everyone has been listed. Leaving members mode flips first under the lease with the rewrite marked pending, then rewrites; a failed move between options of one group restores the previous option. The edit modal closes after a switch and cannot re-choose per-member where the flag is off; connector document exclusion honours the caller's scope. * feat(knowledge): provision per-member access in the background Choosing per-member access no longer asks the admin for anything: the server reuses the workspace's one credential group for the connector's provider or creates one, invites every workspace member to connect, and each member run invites people who joined since. A member's only step is to connect: the knowledge base page shows a banner for every per-member connector they have not connected, and a new enroll route mints their enrollment link on demand so the invitation email is never needed. The connector list reports the viewer's membership for that banner. The permission-scoped listing CI test now compares OAuth provider ids, which is what the runtime checks. The knowledge page module baseline is re-recorded for the provisioning graph. * fix(knowledge): make per-member access self-serve end to end Choosing per-member access provisions a group named after the connector and the first member run invites everyone in the workspace, under the run's lease; the request itself sends nothing, so nobody gets two links. A member connecting queues a member run for every connector on that option, and the knowledge base page shows what each viewer must do — connect, reconnect, verify their email, or nothing — opening enrollment in a new tab and refreshing until they are connected. Every gate reads one availability check (flag and Credential Groups). When it is off, readers get no member token, the engine waits instead of suspending anyone, and the field cannot re-choose per-member access. Member tokens go only to current workspace members with a live group and option. A connector that just entered members mode never tombstones its documents before a member has listed, the stale sweep leaves paused and manual connectors alone, a disabled member sync is re-enabled by re-applying its binding, leaving members mode forces a full content sync, and by-id bulk operations honour the caller's scope. Deleting a per-member connector always takes its documents. * feat(knowledge): list a workspace's per-member connectors for the viewer One read returns every per-member connector in the workspace with where the viewer stands on each, so a surface outside the knowledge base can ask them to connect. The member sync status enum now has one home in lib/knowledge/types. * feat(search): show per-member knowledge connectors on the Search tab The Search tab lists the knowledge bases whose connectors sync per member under "Shared with you", with where the viewer stands on each and the same one-click connect the knowledge base page offers. One hook opens enrollment in a new tab and refreshes the surface until the viewer is connected; the knowledge base banner uses it too. * feat(knowledge): enable per-member access on every permission-scoped connector Google Slides, Docs, Forms, Calendar, Gmail, Notion, Jira, Jira Service Management, Box, Dropbox, monday, ClickUp, Asana, Salesforce, Linear, DocuSign, and Zoom list only what the caller's own account can read, so each now declares its listing caps and can sync per member. A shared listing-scope error lets a connector report a folder, space, board, or calendar the member cannot reach as a complete listing of nothing; Notion and Asana report it from their typed errors, Dropbox from its 409, and the Atlassian connectors also from an unreachable site. Google Docs and Forms join the credential-group providers so their accounts can be collected per person. * refactor(knowledge): tighten the per-member access surface - The credential-group policy canonicaliser carries knowledge-connector statements through instead of rejecting a third statement - Google Docs and Box report an unreachable scope so a member's access is withdrawn rather than retried forever; Notion leaves members mode because its page access is granted to the shared integration bot - Provisioning reuses a Credential Group only when other members-mode connectors already sync through it, never a group curated for something else; the fast dispatch on connect accepts the same statuses as the sweep - Enrollment opens its tab inside the click so popup blockers cannot swallow it, and the membership poll stops after ten minutes - Session-only routes use session auth; members mode stores the cap-stripped config; one liveness rule for members and managed bindings - Drop dead code: the access barrel, system.ts and rank.ts folded into their siblings, unused ACL helpers, the unread change_cursor_at column, stale documentation, and the engine/provisioning/queue import cycle * fix(knowledge): repair CI and apply cleanup passes - Bump the chart version for the member-sync cron and secret - Pass an access scope to getDocuments in the list-convention test and resolve a knowledge scope only for knowledge-base file reads - Drop memoisation nothing observes, read the enrollment error from the mutation, hoist the empty connector list, use the default Cancel variant, let Badge own its gap, and keep the sidebar lit on the Search tab * fix(knowledge): apply the audit swarm's findings - Mutation responses failed contract validation because a viewer's membership was required; a mutation now answers with null - Bulk enable/disable wrote to documents outside the caller's scope - The staleness sweep tombstoned deferred and backing-off connectors; it now only touches connectors that completed a run in the window - Members pace on the plain interval so the connector's jittered run finds them due; a member who alone exhausts the run budget backs off; an account that connects mid-run keeps the connector re-dispatching - Gmail and Google Calendar kept a 500-item cap once the cap field was cleared; caps are now written as 0, which every connector reads as unlimited, on the update path as well - Jira, ClickUp, Asana, Linear, and Dropbox classify an unreachable scope on the source's own error rather than a bare 404; an unmatched Atlassian domain is a configuration error, not this person's access - A Credential Group or option serving a members-mode connector cannot be removed under it; the OAuth completion dispatch cannot fail the callback; the content queue refuses a connector that syncs per member - The iterative vector scan runs only for a personal token set; the ACL check validates each element; the flag is read before the enrollment join; connector info is loaded only where it is shown - Search tab: the empty state counts member rows, rows stack, one membership vocabulary shared with the knowledge base banner; an admin can rebind a per-member connector to any matching group * feat(knowledge): search results a person can open, from Chat - The composer's Search mode searches every knowledge base as the signed-in person and lists what they may read as result cards: source icon, title linking back to the document, knowledge base, updated date, and the matching passage with the query terms in bold; Summarize hands a document to the agent in Build mode - The agent's knowledge tool returns each result's title, link, connector, and modified time and is told to cite with source tags carrying a snippet; a reply whose sources carry snippets ends with the same cards - A session route for the search, bound to the shared search use case, so the browser reads through the same access predicate as everything else - Search quality: hybrid legs over-fetch before fusion, the vector leg's iterative scan fills a limit past the default candidate pool, and the recency weight moves a fresh document a few places rather than the list - A manual member sync makes every active member due, so Sync members now lists everyone instead of nobody * fix(knowledge): land member observations and ACLs only under the lease A run that stalled past the lease TTL and resumed after a replacement took over could commit stale observations, membership rows, and document ACLs over the replacement's. Every such write now runs in a transaction that first proves the run still holds the connector's member lease, holding the connector row's lock so the scheduler cannot reclaim it mid-transaction; a run that lost it ends as superseded. * fix(knowledge): disable a member sync only under the lease A run whose binding was removed suspended members and rewrote ACLs before proving it still held the lease; a run reclaimed meanwhile could suspend the replacement's members and then fail. Suspension, the ACLs it changes, and the disable now land in one lease-guarded transaction, and a reclaimed run ends as superseded. * fix(knowledge): record a member failure only under the lease A listing that ran past the lease TTL and then failed wrote its backoff over the replacement run's counters and schedule. The failure ladder now lands under the same lease guard as every other member write. * fix(knowledge): tombstone and resurrect only under the lease The document lifecycle's tombstone and resurrection writes ran outside the lease guard, so a run reclaimed after its ACL transaction could hide a document the replacement restored or expose one it removed. They now run in the same lease-guarded transaction as every other member write, and a run whose lease is lost while it disables itself ends as superseded rather than rejecting. * fix(knowledge): prove the lease before a batch's document writes Hydrating a batch can outlast the lease. processDocOps now takes the strict lease probe before persisting anything from a batch, so a run replaced during hydration cannot land stale content or queue processing over the replacement's; it ends as superseded. Both engines share the guard. * feat(knowledge): one click connects a Sim Search source and indexes it for the person Connecting a source on the Search tab, or from the composer's Search-mode suggestions, no longer creates a bare credential that indexes nothing. It finds or creates the workspace's Sim Search knowledge base and a per-member connector for the source, then enrolls the person; the OAuth completion queues their member run, so indexing starts on its own and the row counts their documents up as they land. Sources that need a site or space are set up from a knowledge base instead. The credential-only detail page goes. * feat(knowledge): the composer's Search mode shows every source and reads like a search - Under the input, Search mode lists every Sim Search source as a chip with the person's own state: connected with a document count, indexing, reconnect, or one click to connect; sources that need a site link to Knowledge. The sampled four-row list goes, and with it the rows that offered sources the server had to refuse - Results carry a header (how many documents, searched as you, and which source is still indexing), hover actions to copy the link or summarize, an Answer with Sim action for a prose answer, and source and recency filters once a list is long and mixed enough to need them - An existing chat opens in Build; a new chat keeps the last mode. Results never join a transcript * fix(knowledge): process connector-owned documents as the system Members-mode documents are inserted hidden until the member sync materializes who observed them, so the processor's own source-file read, authorized as the actor with workspace scope, denied every document a per-member connector created. Connector-owned rows now read their source as the system; uploads keep the actor's authorization. * feat(search): connect every personal source in place The Sources strip lists only what a person can connect themselves and asks for a source's required setup (a site, a space) in a small modal on the first connect; sources an admin must set up as workspace connectors no longer appear as dead chips. The indexing loader spins, the query stays in the composer after a search, and the knowledge-base page drops the per-member sync banner. * feat(search): keep the results familiar to a search page A result's second line names its source app rather than the knowledge base, emptying the search box returns to the sources, and the arrow keys walk the result links. * feat(knowledge): sync Google Chat, Meet, Sheets, Bitbucket, and Airtable per member Google Chat lists only the spaces the caller belongs to, Google Meet only the conferences they organized, and Google Sheets, Bitbucket, and Airtable one configured spreadsheet, repository, or table that a member either can read in full or cannot reach at all, so each now declares its listing caps and can sync per member. Sheets, Bitbucket, and Airtable report a 403 or 404 on their configured scope as a complete listing of nothing for that member; Bitbucket reads a zero cap as unlimited instead of refusing it. Google Chat, Meet, and Sheets join the Google managed OAuth providers, and Bitbucket gains a managed policy that takes its subject from account_id and its address from the confirmed primary email, requesting the email scope that needs. HubSpot stays a workspace connector: HubSpot documents that an OAuth token reflects the app's scopes, not the authorizing user's record permissions. Google Vault needs the Vault admin privilege and Trello authorizes through its own OAuth 1.0a flow rather than a managed OAuth 2.0 connector. * feat(knowledge): sync Microsoft connectors per member Microsoft Teams, Outlook, OneDrive, SharePoint, and Microsoft Excel list only what the caller's own account can read, so each now declares its listing caps and can sync per member. Outlook reads a 0 conversation cap as unlimited instead of the default. A team, channel, folder, site, or workbook Graph answers 403 or 404 for reads as a complete listing of nothing for that member, as does a channel or folder the member's own listing does not show. A Microsoft managed OAuth policy lets a Credential Group collect each person's account: the id_token is verified against the identity platform's keys and bound to the access token through the OIDC userinfo subject, the person is recorded by oid and tid, and the email counts as proven only through the claims Entra vouches for. * fix(knowledge): close the connector access-switch and members-mode update races - Leaving members mode dispatched the first workspace sync against a fresh clock read while the row held the flip's nextSyncAt, so the queue refused it as stale; the dispatch now asserts the instant the flip wrote. - A workspace credential change kept the incremental watermark, so the new credential's corpus was never fully listed; it now drops lastSyncAt, makes the sync due, and refuses under a CAS while any sync owns the row. - Both switch directions released the lease before revoking the previous group's grant, and a revoke drops the connector from every option of the group; the lease now outlives the revoke. - The members-mode flip and the members-mode create lock the Credential Group row and re-check the option under it, the same lock the group's option removal and delete hold while they look for bound connectors. - Members-mode updates wrote interval changes and resumes to nextSyncAt, which the member scheduler never reads; they now land on nextMemberSyncAt under the matching CAS, a running member run refuses every edit, a queued one refuses config edits, and a pause releases the queued entry. * fix(knowledge): close member-sync races found in review - sweep stale member observations under the connector share lock and the member row lock, re-checking mode and staleness so a run's fresh observations and a mode switch's ACLs are never overwritten - gate the member queue CAS and lease on connector status and on the schedule the dispatch was made for; keep dispatching after one failure - prove the run lease inside every connector document write transaction - drop the per-member page cap on listing passes; a capped listing could never reach the documents behind page 200 - issue member invitations with reject-on-revoked so a concurrent revocation is never reactivated - re-apply the caller's access at document delete and upsert-replace - keep sourceModifiedAt/connectorType on provenance-bearing searches - read Google Calendar's updatedTime as the source modified time * fix(knowledge): resolve Sim Search review findings - Sim Search connect: the first connect of a source requires a workspace admin and refuses everyone else with the way forward; per-member availability is checked before anything is created; creation runs under a per-workspace advisory lock with a re-check; the route forwards the source's setup fields - Connector access switch: workspace mode requires credentialId in the contract and the credential must belong to the connector's provider - Base64 hydration reads knowledge-base files as the run's principal - Search surfaces gate on the workspace's per-member access flag; search results skip legacy personal bases, surface list errors, and report indexing across every per-member connector - Enrollment hook: explicit copy for failed/disabled member sync, awaited connections are dropped once connected, each path clears the other's error; Search mode keeps its query in an existing chat and keeps its context chips diffable; citation labels are Markdown-escaped; an access switch also refetches per-document caches * fix(connectors): withdraw member access on unreachable scopes and page unlimited listings Gmail treated the members-mode cap of 0 as "capped after the first page", so a member's sync never listed past 100 threads and could never withdraw access. Box turned a subfolder a member cannot reach into a capped listing, which suppressed removals forever; the members-mode crawl now marks its sync context, and Box takes the skipped folder as absent from that member's complete listing while a shared credential still caps. Google Calendar with several calendars withdrew everything when one calendar answered 404; per member it now skips only that calendar. JSM classifies 403 (a service desk the caller may not view) alongside 404, and the Atlassian connectors also treat a token that reaches only other sites as not on the site. Monday reports every configured board coming back absent as the scope being unavailable. Salesforce classifies 400 INVALID_TYPE and 403 INSUFFICIENT_ACCESS as the object being unreadable, and filters on LastModifiedDate with supportsIncrementalSync so member passes stop enumerating the whole org. The duplicated Graph listing error helper moves into the shared connector utils. * feat(search): make a search a shareable link The composer's Search-mode query lives in the URL as q, so a search can be bookmarked or sent; opening such a link restores the query and Search mode. * test(knowledge): pin member-access availability in the v1 search route test * refactor(search): apply the cleanup passes to the Sim Search surface Drop three memos over cheap derivations, make the search setter's callers honest about their dependencies, ignore a whitespace-only query from a link, give a dropdown setup field its hint, and drop the query from the URL when the composer leaves Search. * refactor(search): apply the simplify passes to the Sim Search surface One availability gate for every members-mode refusal, the shared role gate behind the admin-first message, setup fields precomputed on the catalog, the connect flow owned by the enrollment hook and shared by the strip and the Search page, the latest attempt derived instead of cross-resets, a trailing adornment slot on the chip in place of a loader wrapper, the processor's source access as one object with the connector-row rule in one place, and the first connect's independent lookups run together after its synchronous checks. * fix(connectors): keep per-member listings complete across partial scopes and blank caps Teams, SharePoint, and OneDrive skip one of several channels or a descendant folder a member cannot reach instead of failing the whole listing, which the members-mode crawl would read as the member reaching nothing. Gmail and Outlook keep their default cap for a null, empty, or whitespace field rather than lifting it, through a shared parseDefaultedUnlimitedSafeInteger. The Confluence incremental CQL clause is fixed on the first page so a cursor is never paired with a query that crossed a minute boundary. * fix(knowledge): address review threads on search surface, lease-guarded dispatch, and selected-output principal - Guard the processing-queue write and dispatch with the sync lease so a reclaimed run neither marks nor enqueues processing - Pass the executing principal into the final-frame selected-output context - Read JSM statusDate as the source modification time - Keep a first-connected Sim Search source non-actionable until its membership row appears - Render the Build/Search switcher only for the Home composer - Keep every Search-mode submit out of the agent; attachment-only submits do nothing - Skip the member-connector fetch and 'still indexing' note when per-member access is off, and scope it to the searched bases - Queue the workspace member-connector list on a members-mode sync trigger - Log a refused clipboard write on the source card instead of leaving it unhandled * fix(connectors): validate Gmail's thread cap with the sync parser and reject invalid Date metadata * fix(knowledge): prove the lease inside every ACL rewrite batch and harden source-modified-time parsing * fix(knowledge): claim a member only under a proved lease and read email conversation dates * feat(search): show the matching passage and the author on each result A result's snippet is the window around the first query term, skipping the header block an email carries, so the row shows why the document matched; its meta line names the source, the person behind the document from its author-like tag, and the date. The agent's citations carry the author too. * refactor(search): style the results and sources as one surface with the composer Result rows take the chat surface's row rhythm with hairlines between them, fade-clipped titles and meta lines, a proper icon button for Copy link, a ghost Summarize matching Answer with Sim, actions revealed on keyboard focus, and a linkless document rendered in the same row with its author, date, and bolded passage. The source and date filters live in the URL beside the query, cleared with it. The sources strip keeps connected chips at full weight, and the member-connector query is gated with an enabled option and cancelled before an optimistic queue write. * fix(search): safe result links, script-aware term matching, and honest header stripping A result links only to an http(s) URL; term matching judges word edges by the surrounding characters instead of ASCII \b and strips quotes from a phrase, and the same matcher bolds the passage; a chunk that is nothing but fields keeps its content; the agent leaves unknown optional citation fields out and keeps the tool's published result count; route errors log the wrapped cause and Postgres code. * fix(search): no cached member rows with access off, no double enrollment, code-point-safe matching Surfaces consume member-connector rows only while the feature is on; the Search page treats an awaited enrollment, including a first connect, as non-actionable; a refused members-mode sync rolls back only the member lists; result actions show on pointers without hover; route error causes go through the redacting describer; the citation template is valid JSON; term edges and snippet windows respect code points. * fix(knowledge): create a Sim Search source without holding a transaction across the nested use cases The first connect held an advisory-lock transaction while the nested base and connector use cases queried the pool, which the transaction tripwire now refuses, so every first connect of a new source failed. Concurrent first connects are coalesced per workspace base and per source within the process and re-check before creating instead. * fix(search): admin-gated first connect on the surfaces, live indexing state, honest filters and loading Route errors redact bound values from the outer message too; a source nobody connected is offered only to an admin and others see why; a first connect refreshes the base's connector list; member connectors poll while a source is indexing for the viewer; a filter from a shared link applies even when the controls would not appear; results kept from the previous query show as loading. * improvement(routes): log the deepest cause's stack outside production * feat(home): add Ask mode, an agent turn grounded in the searched sources * fix(home): keep the composer on the live search query, not a mount-time snapshot * feat(home): make Ask turns answer from the searched sources with the knowledge tool alone * improvement(chat): name the query in the knowledge search row and list a reply's sources densely * fix(home): carry the Ask request mode through every send lane and refuse every non-server tool on an Ask turn * improvement(chat): put a reply's sources behind a counted button in the action row, and keep Ask follow-ups to questions * feat(chat): let the agent use the person's own Credential Group credentials, and keep integrations on Ask turns for questions knowledge cannot answer * fix(knowledge): treat a reclaimed lease during a member purge as superseded; tidy the chat reply sources after cleanup * improvement(home): fold Ask into Search behind an Answer toggle * fix(credentials): prove a Chat managed-credential use only from a real tool call, and list or mint only live group bindings * improvement(home): name both choices in Sources mode with a Search / Assistant toggle * improvement(home): make Assistant a peer of Build and Search in the mode switcher * improvement(home): keep the composer mode in the URL and drop the mode store * fix(home): let Search be chosen inside a chat now that the mode lives in the URL * fix(chat): scope the managed-credential listing to the group's workspace and carry only chat params on the handoff * fix(credentials): refuse a managed credential whose group or option is disabled on every mint, workflow runs included * fix(home): clear the composer when a result is handed to the Assistant, follow the search query in a chat, restore a queued message's mode, and seed the base list * fix(knowledge): budget the pending ACL rewrite per run, and converge a racing first connect on the oldest base and connector * fix(home): ground an Assistant send from the query cache instead of growing the page graph with a prefetch --------- Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
…of at the end of the run (#7418) * fix(knowledge): grant member access as each batch is indexed instead of at the end of the run * perf(connectors): hydrate deferred mail and chat documents in batches instead of one at a time * fix(knowledge): grant access after dispatch, include skipped rows, and keep the grant best-effort * fix(knowledge): cast the stale-sweep cutoff so the member-sync scheduler stops failing * fix(connectors): cap assembled mail and chat text at one shared bound and advertise it as the hydration estimate * fix(google-chat): report the number of messages the capped transcript actually indexed
…alidate (#7422) * fix(credential-groups): raise OAuth code cap so Atlassian callbacks validate The managed credential-group callback capped the OAuth `code` query param at 2048 characters. That bound was sized when the flow only supported Slack, whose authorization codes are short, and was never revisited as more providers were added. Providers that return a signed JWT as the authorization code exceed it, so the callback rejected the exchange with a validation error before it ran. Share a single MAX_OAUTH_CODE_LENGTH (8192) from the contract primitives and use it on both callback paths, so the two contracts cannot drift apart again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013e5sXbYST2R4qNzM996GFg * fix(credential-groups): correct the OAuth code bound's doc comment The constant's comment claimed every callback contract shares the bound, but the Shopify and MCP callback schemas leave `code` unbounded. Describe what the bound is and why it is sized as it is, without asserting an adoption scope that would go stale as contracts are migrated onto it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013e5sXbYST2R4qNzM996GFg --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t memory filesystems (#7393) * feat(files): folder operations for the File block, and folders as scope Adds folder operations to file_v5 and the agent tool surface, and makes a folder a scope on the file operations that already existed rather than a second set of operations beside them. New operations: List, Create Folder, Move Folder, Delete Folder, Restore Folder, and Move File. List answers "what is in here" — subfolders and files together, direct children by default, the whole subtree under Recursive, subject to Max Depth and Search. Entries are a discriminated union on kind, and the listing is capped with a truncated flag rather than unbounded now that it includes files. Read, Get Content, Compress and Append gain an optional Folder above their file picker. It narrows what the picker offers; on the three read operations it also stands for that folder's files when none are picked, resolved when the workflow runs so a file added later is included. Append only narrows the picker, but the folder does travel when the advanced entry supplies a name rather than an id, because a name is only unique inside a folder. Write gains a folder destination, placed above File Name because it names where before it names what. Two things are worth a reviewer's attention. Path handling. Two spellings circulate: the stored display path, which backslash-escapes a slash inside a folder name, and the canonical percent-encoded path the tools take. A folder genuinely named "Q3/Q4" is one level in both and two if either is split on "/". folderPathSegments picks the parser by the leading slash the canonical form always carries, and resolveFolderIdsForPaths, isFileInFolderScope and selectDirectoryEntries are pure and tested against exactly that case. Where a folder is known, the code resolves by id and never builds a path-shaped reference at all. Delete Folder's recursive flag is a guard, not a scope. Without it, deleting a non-empty folder fails, and it is user-only so a model asked to clean up a folder cannot set it on a guess. This is deliberately the inverse of the read family's Include Subfolders, which is a scope and defaults on. Five file operations widen from copilot-only delegation to admit the executor, each justified by a tool in this change: files.list and files.folders.list for file_list, and files.folders.update / delete / restore for their tools. Principal kinds are unchanged and copilot keeps every operation it had. * Update apps/sim/tools/file/compress.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/sim/lib/workspace-files/application/workspace-file-folders.test.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/sim/lib/internal/file/operations.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * feat(files): confine content search to a folder scope * feat(files): edit and insert file content in place * feat(files): ranged content reads and v2 surfaces for search and edit * Update apps/docs/openapi-v2-files-audit.json Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/docs/content/docs/integrations/file.mdx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/sim/lib/workspace-files/application/edit-workspace-file-content.test.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update apps/docs/content/docs/cli/files.mdx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * refactor(files): share one folder-scope module with the table branch * test(files): cover both non-text buffer shapes, and regenerate the spec * fix(cli): stop claiming a line limit caps a --filter match * fix(files): connect the scoped-search, edit and ranged-read fields end to end * test(files): give the session principal fixture its required sessionId * fix(files): keep folder creation working under a slash-named parent * chore(files): sort the folder-scope import * fix(realtime): count room subscribers so one unmount cannot cut off another * test(realtime): type the room fixture instead of casting it * fix(files): close the round-2 review findings * fix(files): scope the picker from either half of the folder pair * docs(files): say that a folder constrains a canonical id too * fix(files): keep the root scope on a named append, edit or insert * fix(files): keep multi-folder search serializable and reclaim a stranded lock * fix(files): normalize a slash-omitted folder path instead of discarding it * docs(files): say what the search coverage flag actually covers * Update apps/sim/lib/api/contracts/v2/openapi/files-audit.ts Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * refactor(files): make the folder scope one advanced field, always recursive * chore(docs): regenerate the spec after the conflict-example wording change * fix(files): let an exact file id win over a lookalike name * fix(files): refuse an out-of-scope file id instead of matching a lookalike * feat(files): bring back Include Subfolders, in the advanced fields * Update apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * feat(files): complete folder scoping across surfaces * test(files): align generated API and folder scope coverage * fix(files): close folder scope review gaps * feat(files): add anchor-based file edits * fix(files): harden scoped edit operations * fix(files): preserve scoped listing semantics * fix(files): bound expanded edit output * fix(files): keep replacement content literal --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* docs(library): update ai-agent-ideas * fix(library): remove redundant heading links --------- Co-authored-by: Sim Pi Agent <pi@sim.ai> Co-authored-by: Waleed Latif <walif6@gmail.com>
…rson's own Slack user token (#7421) * feat(knowledge): crawl Slack per member on Sim Search through each person's own Slack user token * fix(connectors): keep the newest messages when a chat transcript reaches the size limit * fix(slack): fold the channel header into the content hash so a rename or topic edit re-indexes
* feat(providers): make the agent tool-call ceiling configurable MAX_TOOL_ITERATIONS is now read from an env var of the same name and defaults to 20, so self-hosted deployments can raise the agent tool loop without a custom build. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lm2sCA6iswCvNT33eNRrix * chore(helm): bump chart version for the new env-var default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lm2sCA6iswCvNT33eNRrix --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) The agent-stream tool chrome rendered a collapsible "Tools" list above the terminal output panel, while the same calls remained inline in the block's toolCalls output field. Drop the header so tool calls read inline again. Claude-Session: https://claude.ai/code/session_01CimY1cwvWpQs3RY5MXtdE3 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(monday): forward users through app installation * test(monday): exercise OAuth link route --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local>
…s, refetch references on return (#7426) * fix(secrets): ignore Note blocks in fork mapping and secret references, refetch references on return * chore(docs): say only note-only secret references skip fork mapping * fix(forks): report a sync's resource copy on its push or pull row instead of a separate Fork entry * fix(tables): mint fork-copied table ids with the same tbl_ shape as created ones * fix(forks): label a failed fill honestly, count skills and documents, and surface deploy warnings on the sync row
* feat(cli): tell the user when their sim is out of date `sim tools execute` shipped in 2.1.5. Someone on 2.1.2 looking for it saw a help listing without it and concluded the CLI could not do it - a missing subcommand is indistinguishable from a feature that was never built, and nothing in the CLI could tell them otherwise. It had no update check, no version negotiation, and no way to learn what "current" is. Once a day, at an interactive terminal, the root `preAction` hook asks `registry.npmjs.org` for the dist-tags of the channel it was installed from and prints one line on stderr when a newer version exists. The request carries the CLI version and nothing else - no key, no workspace, no command - and `SIM_NO_UPDATE_CHECK=1` turns it off. Everything about it fails silently, and it says nothing when stderr is not a terminal, in CI, under `npx`, from a checkout, or to a prerelease install. The last two are not politeness: the repo manifest trails npm permanently by design because the publish workflow bumps the version in-job under `permissions: contents: read` and never commits it back, so without the checkout guard every engineer here would be told daily to upgrade to a version their own tree already contains; and `staging` publishes on every push, so advising a prerelease user would be stale within the hour. Comparison is scoped to one channel, which is what makes "upgrade" to an older stable version structurally impossible rather than merely guarded against. The comparator implements semver precedence including the numeric prerelease rule - `preview.9` precedes `preview.44`, which a string comparison gets backwards. The `preAction` hook is deliberate over a teardown in the entrypoint: commander answers `--help` and `--version` during parsing, so the two latency-sensitive invocations are excluded by construction, and some commands call `process.exit` directly where a `finally` would never run. Timeout is a hard 1s rather than `SIM_TIMEOUT_SECONDS`, which defaults to an hour and governs work the user actually asked for. The check is stamped whether or not it succeeds, so a blackholed registry costs one second a day instead of one per command. * fix(cli): close the update-notifier findings from pre-landing review Mutation testing found three tests that could not fail: deleting the `preAction` hook, switching the default writer to stdout, and flipping `comparePrerelease`'s empty-list arm all left the suite green. The stdout one was vacuous because the test helper always injected a writer, so the single safety property this feature claims - never touch stdout - was unprotected. The hook now has a positive test. It asserts registration rather than a resulting request, because the check suppresses itself when running from a checkout, and inside the suite `import.meta.url` IS a checkout: the behavioural path is unreachable there by construction. It is covered directly in check.test.ts and walked against the real registry from a staged global install. Security review: the response body is now read under a 64KB budget instead of buffering whatever a mirror sends, the request refuses to follow redirects, and the registry's answer is parsed before it is persisted, so nothing unvalidated reaches the disk. The reduced User-Agent was a comment; it is now an assertion, so a future "DRY up the user agent" refactor cannot silently start handing npm the user's node version, platform and arch. A configured mirror's own path and query are preserved. `new URL(relative, base)` discards both, so a token-authenticated Artifactory or Nexus base was being rewritten into a request the mirror answers with a 404. Also: one normalisation for every module-path decision (separators AND case, so a Windows or case-insensitive checkout is not read as a global install by one guard and a checkout by the other), the package name is named once rather than spelled in two unrelated places, and `delete process.env.SIM_CONFIG_DIR` in teardown - assigning `undefined` stores the literal string and leaves later tests pointed at a relative `./undefined` directory. Tests: 843 -> 861. Ten mutations applied to verify the new assertions actually fail when the thing they guard is broken; all ten killed. Declined, with reasons: the ~10s lingering-socket exit delay could not be reproduced through the CLI (measured 1.11-1.38s across three runs on node v23.11.0, including a command that only sets exitCode), so no node:https rewrite. `announced` plus `resetUpdateCheck` stays - it is the same shape as the existing resetEnvironmentNotices and resetRenameWarnings seams. The channel type stays rather than collapsing to a boolean, because it is what a decision to notify prerelease users would extend; its docs now say what the code does instead of describing a comparison it never performs. * fix(cli): correct the update-notifier privacy claim and prerelease parsing Review round 1: five findings, all valid. The privacy statement was too absolute. The request carries no Sim API key, but `npm_config_registry` can point at a private mirror, and a token embedded in that URL is sent with the request - it has to be, or the mirror rejects it. Both docs now say which credentials are involved and where they go: your registry's, to the host you configured, never Sim's. `parseVersion` accepted zero-padded prerelease identifiers. Semver forbids them, and accepting `2.1.3-preview.09` was worse than cosmetic: `09` failed the numeric test and fell through to being an alphanumeric identifier, and alphanumerics outrank every number, so `preview.010` sorted ABOVE `preview.2`. The file's own doc comment already claimed leading zeroes were rejected "the way the specification rejects them" - true of the release triple, not of the prerelease. Now true of both. The `--version`/`--help` test did not hold the guarantee it advertised. It watched for a request and a cache file, but neither ever appears from inside a checkout no matter what runs, because the check suppresses itself there - so it would have passed even if the hook fired, which is the exact regression it claims to prevent. It now swaps a sentinel into commander's registered preAction hooks and asserts the sentinel does not fire while parsing those two, then asserts it DOES fire for a real action command, so the negative assertion means something. No module mocking, which this package bans. The troubleshooting page hardcoded `npm install -g`, which installs a second copy under a different package manager rather than replacing the executable on PATH. It now shows all three, and says the notice already prints the one matching your install - which the notifier has always done. Tests: 861 -> 863. Both new guards mutation-checked: dropping the leading-zero rejection and deleting the hook each fail the suite. * fix(cli): make the update-notifier docs match what the code actually does Review round 2. Three findings, all valid. The previous commit's message claimed it had replaced `process.env.SIM_CONFIG_DIR = undefined` with `delete` in the test teardowns. It had not: it added a comment explaining why the assignment is wrong and left the assignment in place, so the teardown still stored the literal string "undefined". Both files now actually delete it. The same pattern exists in profile.test.ts and configure.test.ts, which predate this branch and are left alone. Two documentation claims were stronger than the implementation. "At most once a day" is only true with a writable `~/.sim`. The pace lives in a timestamp file, so a read-only home in a container - or a `~/.sim` left root-owned by an earlier sudo install - means the pace cannot be remembered and the check runs per command. That was already noted in a code comment; it is now in the docs where users read it, along with the fact that it stays bounded by the same one-second timeout. "The tag it was installed from" described behaviour that does not exist. The check only ever queries `latest`, because prerelease installs return before any request. Both docs now say that plainly instead of implying the CLI can ask about the staging or dev channel. * docs(cli): name the update cache path for relocated config dirs Review round 3. The cache is derived from `configDir()`, so it moves with `SIM_CONFIG_DIR` like the config and credentials files do - but the docs named only the `~/.sim` default, sending anyone with a relocated config dir to a file that is not there. * fix(cli): harden and simplify update checks * test(cli): isolate update checks from CI markers * fix(cli): tighten update check eligibility --------- Co-authored-by: Waleed Latif <walif6@gmail.com>
* chore(tailwind): upgrade to v4 and swap clsx/tailwind-merge for cn Migrates apps/sim from Tailwind v3.4 to v4.3 (CSS-first config) and replaces clsx + tailwind-merge with the cn package in @sim/emcn and apps/docs. The upgrade is intended to be visually inert. Parity was verified by building the stylesheet both ways and diffing computed styles in a real browser: - Preflight: 112/112 bare elements identical, after restoring seven v3 behaviours v4 dropped (button cursor, form-control background, placeholder colour, table/option padding, search-field normalisation, dialog centring) - Utilities: every class the app uses renders pixel-identically - Border widths: 10/10 combinations identical - Class strings: 699 changed strings run through both merge engines, 0 diffs Pinned against v4 defaults that would otherwise shift rendering: --font-sans (v4 changed its own default stack), --text-xs--line-height, and the 24 stock palette colours the app uses (v4 re-authored the palette in oklch, which is visibly more saturated on P3 displays). borderWidth.DEFAULT has no v4 theme key, so a PostCSS pass rewrites Tailwind's own border-width output through --border-width. Re-declaring the utilities in a trailing @layer instead would make `border` beat `border-2` and `border-t-0`. rounded-sm and rounded-md are deliberately NOT renamed: this app overrides that radius scale, so the standard v3->v4 rename would change their values. cn is compiled ahead of time (cn build --full) so the config compiler stays out of the browser bundle; check:cn-tables fails CI if the tables drift. * chore(tailwind): anchor the cn-tables scratch dir ignore Was an unanchored `.cn-check/`, which would ignore a directory of that name anywhere in the tree; the check script only ever creates it under packages/emcn. * fix(tailwind): close the remaining v3/v4 rendering differences Found by rebuilding the v3 stylesheet from this same tree and diffing computed styles for all 4,674 class strings the app actually uses — combinations, not just individual utilities, which is where these only show up. - leading-*/text-* precedence: v4 routes a text-* utility's line-height through --tw-leading, so a leading-* class now wins at every breakpoint. v3 let a responsive text-lg reassert its own 28px leading; restate that explicitly. - overflow-wrap: v4 emits the arbitrary property before `break-words`, flipping the winner from `anywhere` to `break-word`. Drop the redundant utility. - z-9999999 and duration-[30ms] never took effect under v3 (outside the generated scale; rejected as ambiguous). v4 honours both, which would change stacking order and a transition from 150ms to 30ms. Removed. - note-block focus ring: outline-<n> set width only in v3, so this drew nothing; v4 also sets outline-style. Removed to keep v3 rendering. Every remaining computed-style difference is now verified non-visual: colour notation (oklch/oklab, pixel-identical), gradient interpolation, mask-composite keyword aliasing, and outline-none vs outline-hidden. * fix(tailwind): restore v3 hover, blur and breakpoint behaviour An independent parity audit across light, dark and touch contexts found four user-visible differences the earlier desktop-only checks could not see. - hover: v4 wraps `hover:`/`group-hover:`/`peer-hover:` in `@media (hover: hover)`, so 64 hover-family classes were dead on touch devices — row highlights, reveal-on-hover icons, nav menus. This codebase already has `hover-hover` for the cases that want that gating, so plain `hover:` was meant to apply everywhere. Restored with `@custom-variant hover`. - blur-0 no longer exists in v4. Two sites used `group-hover:blur-0` to clear a base `blur-[2px]`, leaving the icon permanently blurred. Renamed to `blur-none`. - space-y-* dropped from specificity 0-3-0 to `:where()` 0-0-0 and moved to the preceding sibling, so a child's own `mt-*` now wins. Two layouts grew 4-8px; removed the child margins v3 was overriding. - Breakpoints: v4 emits rem, which resolves against the browser's initial font size rather than `html { font-size }`, so a reader on a larger default text size got the mobile layout at desktop widths. Pinned back to v3's px. Hardening from the same pass: - `::before`/`::after`/`::backdrop` border-color — v4 leaves them `currentColor` and the app's `*` rule did not cover pseudo-elements. - apps/docs was missing the `cn-tables.ts` source exclusion, so its bundle carried phantom `float-left`, `clear-end` and every `mix-blend-*`. - Hairline plugin: narrowed to border-width longhands (it could rewrite an `outline-width` sharing a rule), stopped `\b1px\b` splicing into `0.1px`, and skipped node_modules stylesheets. Added 18 fixture tests. - 23 golden-case tests for `cn`, pinning the font-size class group. - The tables drift check now derives its flags from `cn:build` so the two cannot diverge, uses the local bin instead of `bun x`, and writes to tmpdir. * refactor(emcn): finish the cn migration by dropping clsx apps/sim still imported clsx directly in 8 files, so the repo carried two class-name paths — one that resolves Tailwind conflicts and one that does not. Converts them all to `cn` and removes the dependency. clsx joins; cn merges. The two differ only where a call emits conflicting classes, so every call site was checked rather than assumed. Of 19 sites with 2+ class tokens, 6 could conflict: - 4 already resolve the same way, because cn keeps the class CSS source order was picking anyway (cursor-grab/pointer, opacity-0/100, text-secondary/tertiary) - 2 are mutually exclusive ternaries in output-panel; only one branch ships The seventh is a latent bug rather than a merge difference: workflow-item's overlay sets `pointer-events-none` in its base and `pointer-events-auto` when the context menu is open, but Tailwind emits `pointer-events-none` last, so it always won — that button has never been clickable while the menu is open, only visible. Left rendering as-is with a comment; enabling it is a real fix but not a rendering-neutral one. Verified all four runtime branches of that call still yield `pointer-events-none`, and the built stylesheet is byte-identical. * refactor(emcn): use tailwind-merge v3 instead of the cn package The cn swap did not earn its keep. Benchmarked on this repo's own 4,673 class strings, with both libraries' caches warm — which is what a re-rendering UI is almost always in — cn is 4.2x faster per call (4.7ns vs 19.5ns). On a heavy 5,000-call render that is a 0.074ms saving: below anything perceivable, and far below React's own reconciliation. The 13.7x cold-path win only applies to strings neither cache has seen. Against that it cost 2.1 KB gzip over tailwind-merge v3, paid on every cold load on the critical path to first paint. cn trades gzip-compressibility for parse speed: its tables are pre-encoded data that gzip cannot squeeze, where tailwind-merge's config is repetitive JS that it can. Reverting also drops a dependency published eight days ago onto a recycled npm name, and lets the 7-day supply-chain gate go back on for the whole tree. Keeps everything the migration was actually worth: Tailwind v4, and a single class-name path now that apps/sim's direct clsx imports are gone. The 23 golden cases pass unchanged against tailwind-merge, so the merge semantics are identical; the built stylesheet is byte-for-byte the same.
* feat(copilot): add create_table_view and edit_table_view Direct main-agent tools for saved table views. create_table_view takes a table id (optional name, config, isDefault) and returns the view id; edit_table_view takes a view id plus a config patch and resolves the owning table from the view. Both results name the table and view, so the resource panel opens the table pinned to that view, and an already-open table switches to it once its views list carries the id (view-pin store). viewId now rides the resource stream descriptor and chat-resource persistence so the pin survives reopening the chat. * fix(copilot): address review findings on table view tools - edit_table_view resolves the view's table under a workspace-only context (no table scope exists yet for the delegated principal), then re-enters the table-scoped read and update with that id - updateTableView takes the per-table views lock when promoting, so it serializes with default-on-create instead of racing the unique index - the View N fallback is chosen inside the locked create - unknown column names are classified as validation errors in the shared translation, so the model sees which column it got wrong - pending view pins are reset when a chat is torn down or switched - add and reorder share one chat-resource item schema; reorder merges incoming entries with stored ones so pins and paths survive - mergeChatResource keeps every field the newcomer defines - the pin merge runs for every pinned upsert, not gated on wasAdded * refactor(copilot): drop the direct view tools, pin views through table_views Views stay with the table subagent's multiplexed table_views; the orchestrator delegates as before. Its create/update/set-default results now name the table and view they wrote, and resource extraction turns that into the pinned table resource, so the panel opens (or switches) the table on that view. Unknown column names are classified as validation errors, and create_view's isDefault lands in the same locked transaction as the insert. The stream/persistence plumbing for viewId, the pin store, and the lock on default promotion are unchanged. * chore(copilot): sync table view update semantics * fix(copilot): sync table view sort item schema * fix(copilot): persist table view pin updates * fix(tables): reconcile agent view pins * fix(copilot): type resource update directives * fix(tables): serialize default view demotions * fix(copilot): preserve view pin clear requests * fix(copilot): serialize resource view updates * fix(copilot): close resource persistence races * fix(copilot): retain resource removal intent * fix: isolate copilot resource persistence by chat * fix(tables): restore view when returning to chat * fix(copilot): bound resource-write locks and repair reorder persistence Review follow-ups on the saved-view pinning work. Correctness: - Reorder persistence was parked by ANY pending write. A repeatedly failing update to an already-stored resource (a view pin) blocked tab ordering for the rest of the session; gate on unpersisted writes only, which are the ones the server's identity check can actually reject. - A parked reorder body that the server rejects was re-parked verbatim, so a tab closed after the order was captured poisoned it permanently. Discard on 400; keep retrying everything transient. - adoptScope merged the provisional and chat-scoped updates in the wrong order, letting an older pending write overwrite a newer one. - A view pin that arrived before the table finished its first adoption was dropped for good when the table data resolved after the views list. The stream path self-rescued through query invalidation; the restore path did not. Re-run the effect when adoption becomes possible. - Reordering a chat holding a legacy duplicate row 400'd forever. Compare identity sets so the duplicate collapses on write instead. - mergeChatResource aliased the caller's object into React state, the query cache and the pending-write queue at once. Copy it. Robustness: - The new copilot_chats FOR UPDATE transactions had no lock_timeout, and neither the pool nor the deployment sets one. finalizeAssistantTurn holds that same row across an assistant-message append, so a waiter could park a pool connection indefinitely. Bound all five writers. - mergeChatResource's field list is now one declaration that fails to compile when MothershipResource gains a field, rather than silently dropping it from both the merge and its no-op check. - Extraction can no longer emit viewId and clearViewId together, a pair the wire contract rejects and the merge would resolve to neither. - Restrict the eager view-id URL write to embedded tables, leaving standalone table behaviour identical to staging. - Drop the queue's unreachable unscoped bucket, its uncalled clear(), and its test-only getPendingUpdates(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FwmaLmAXSK2hsPBGZmkPnT * fix(copilot): hold a reorder for pending deletes too The reorder gate landed one case short: a delete that has not reached the server leaves the server holding a resource the client's order omits, so the order fails its identity check exactly as an unlanded add does. Gating only on unpersisted adds let that order fire and be discarded as unsatisfiable, losing the tab order until the next reorder or hydration. Name the predicate for what it actually decides — whether a pending write changes WHICH resources the chat holds — and cover both directions. A failing update to an already-stored resource still does not park the order, which is what the gate was narrowed for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FwmaLmAXSK2hsPBGZmkPnT --------- Co-authored-by: Waleed Latif <walif6@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… self-hosting docs (#7437) * feat(supply-chain): sign and attest every published image, and inventory the chart's images Enterprise security reviews ask for artifacts Sim did not publish: a signature proving who built an image, SLSA provenance describing how, and an SBOM listing what is inside. A new attest-subjects job resolves the tags this run actually published to their digests, and attest-images signs each with cosign keyless signing and attaches provenance and, for single-architecture subjects, a Syft SBOM. All of it is pushed to the registry as referrers, so a mirrored registry carries the attestations with the image. Resolving tags rather than assuming them is what makes the coverage complete. `imagetools create` writes an index even from one manifest, so `:version-amd64` has a different digest from the `:sha-amd64` manifest it wraps, and which tags exist depends on whether the run is a release and whether the latest guard held. A moving alias is taken only when it resolves to this run's index, and that read is retried so registry lag is not mistaken for a deliberate skip. The SBOM is generated from the plain `<sha>-<arch>` manifest rather than the subject digest. Syft resolves an index against the runner's platform, so an arm64-only index fails outright on an amd64 runner — and the plain manifest is identical content, shared by both subjects for that architecture. helm/sim/images.yaml is the set of images a complete install pulls, generated from the rendered chart because an image can be referenced from a template that no values key names. Each entry carries the reference to pull from and the path the chart resolves to under a mirror; those differ, and the second is derived from a second render rather than described as a rule that could drift. The NVIDIA device plugin was hardcoded, so an air-gapped cluster could not redirect it. It now takes its image from `ollama.gpu.devicePlugin.image` through the shared helper, and the chart minor is bumped for the new key. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * docs(self-hosting): add sandboxes and reference architectures, and correct the existing pages An audit of 27 pages checked every concrete claim against the code it describes. It found claims that made a documented path fail outright, and claims that understated what ships. Errors that broke a documented path: - `openssl rand -hex 32` prints 64 hex characters, not the "32 hex chars" five entries claimed, and only two of those secrets are length-validated at all. - `API_ENCRYPTION_KEY` was missing from the whole Kubernetes path. It is optional, and unset means API keys are stored in plain text behind one warning. - Bucket fallback behaviour was wrong for all three object-storage providers, in both directions. - The remote-sandbox table omitted the immutable template and snapshot refs, so the documented configuration could not work. - The GKE snippet set a values key no template renders. - Retention deletion is scheduled by neither shipped deployment, so a deployment that followed the docs deleted nothing. - Every "Settings → Enterprise/Security → X" path named nav groups that do not exist, across eleven pages. Claims that understated what ships: - The outbound-proxy section said no platform traffic can be proxied. The server runs on Bun, whose fetch honors the proxy variables, so provider and Resend traffic already proxies; the guarded egress, SMTP, S3 and OTLP do not. Replaced with a per-path table and the NO_PROXY guidance internal services need. - Telemetry is off by default on Helm, not on. - The CronJob count was 18 in two places; there are 22. - The permission-group table was stale throughout, named a deployment key that does not exist, and inverted the checkbox semantic. Adds two pages. Sandboxes carries the base-image runbook that was buried in an enterprise enablement page. Reference architectures answers what an operator provisions versus what the chart provisions, per cloud — the question behind "do you have Terraform or CloudFormation templates?". Also documents around 25 environment variables nothing described, the migration advisory lock and its operational knobs, and `sim-setup doctor`. Removes redundancy where a page restated itself: FAQ blocks repeating their own body, and four drifting copies of the same tables. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the first review round on the combined PR The mirror override in the inventory header double-prefixed. It told operators to override the device plugin with `<your-registry>/nvidia/k8s-device-plugin` while global rewriting stayed on, so `sim.image` prepended the registry a second time and the DaemonSet landed in ImagePullBackOff. Verified by rendering both forms; the override has to be the bare repository. The plugin's DaemonSet also now carries `global.imagePullSecrets`, which it was the only workload to omit — an authenticated mirror could not pull it. The resolver skipped `latest*` whenever the alias did not resolve to this run's index, which conflated "the guard withheld it" with "the read was stale". create-ghcr-manifests now publishes its guard decision and the resolver fails instead of skipping when the guard says latest was published this run. Docs corrections, each verified against source: - The loopback carve-out does lift plain HTTP and the private-address block; it leaves only the denied-port list in force. `egress.ts` skips the port check for allowlist vouching alone, which is what makes `http://localhost:5432` stay refused. - Not every guarded response is capped — the pinned fetch used by provider SDKs sets no limit. - A partially configured storage backend is skipped when a later one is complete; it fails only when none is. - GCE Ingress can serve a provisioned TLS Secret, so cert-manager is an alternative to a ManagedCertificate rather than requiring a different controller. - Workload Identity needs roles/iam.serviceAccountTokenCreator to sign GCS URLs. - NEXT_PUBLIC_BRAND_BACKGROUND_COLOR is a contrast flag, not a background. - NEXT_PUBLIC_SANDBOXES_ENABLED is a browser gate, not provider readiness. - An incomplete certificate chain is not fixed by NODE_EXTRA_CA_CERTS; the endpoint has to serve the intermediate. - Script migrations run once — the eight retries are for SQL lock timeouts. - NO_PROXY belongs in the application environment, not a shell assignment, and the GKE annotate command no longer hard-codes a release name and namespace. - Pi's own image, the Microsoft login callback, the custom-block identity split, the whitelabeling surfaces that ignore configured URLs, and sim-setup's Compose-only scope are all now stated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the second review round Two chart defects, both the same shape as the uploads-path bug this branch already documents: a path that looks right but sits outside where the app actually serves from. - `branding.mountPath` defaulted to `/app/public/branding`, while the standalone build places the public directory at `/app/apps/sim/public`. Every branded asset the docs tell operators to reference would have 404'd. Corrected in the chart and the example. - The device plugin was the only `sim.image` call site not passing `chartAppVersion`, so `tag: ""` — a natural way to ask for the chart default — errored instead of falling back. Docs, each verified against source: - Local disk is selected unless a backend's *activating* variables are set; credentials alone activate nothing. - Six backoff retries follow the initial failure, so up to seven attempts. - The migrations init container reads only the database Secret, and the chart's Secret templates carry just the password — so `MIGRATION_DATABASE_URL` needs an operator-supplied Secret, not the generated one. - The Ollama stack's missing Redis also breaks CLI authentication outright, while the idempotency store and progress markers fall back to Postgres. - `sim-setup reset` leaves the PostgreSQL PVC on Kubernetes. - The copilot ingress route renders only when `copilot.enabled` is set. - The Postgres exposure applies to whichever Compose file started the install. - The GKE annotate command resolves the Service name from the release rather than assuming it. - `API_ENCRYPTION_KEY` warns on every use, not once, and the whitelabeling coverage claim now describes the two surfaces that only partially honour it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the third review round Two of these were fixes from earlier rounds that never landed — the edits were in batches that aborted partway, and I reported them applied without re-verifying. The LM Studio recreate command still hard-coded the production Compose file, and the proxy table still claimed "four exceptions" above a row listing fifteen providers. The rest: - The NO_PROXY example omitted `ollama`, so on the Ollama stack local model requests would go to the external proxy. - The `lock_timeout` sentence had its trade-off backwards: it trades a table-wide stall for a failed migration, not the reverse. - `NEXT_PUBLIC_CUSTOM_CSS_URL` has to be same-origin or CSP-allowlisted; `style-src` blocks a stylesheet from another origin. - Any of a storage backend's keys activates it — a dedicated bucket, `S3_ENDPOINT`, an Azure credential — so a half-configured backend fails at startup rather than falling back to local disk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the fourth review round - The LM Studio recreate snippet now shows both Compose assignments instead of describing the alternative in a comment. - `sim-setup logs` on a source checkout prints where the logs are rather than following a stream; the table said it follows. - The GKE BackendConfig annotation targeted `<release>-realtime`, but the chart names the Service `<fullname>-realtime` — and the fullname is `<release>-sim` unless the release name already contains `sim`. It now selects by the release and component labels, which holds for either. - The Helm `NO_PROXY` example listed only the `.svc` suffixes, which match none of the bare Service names the chart wires into `SOCKET_SERVER_URL`, `PII_URL`, and `OLLAMA_URL`. Those requests would still have gone to the proxy. - `sim-setup` detects only the production and local Compose files, so an Ollama-stack install is invisible to it. The upgrade section now says so and gives the direct commands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the fifth review round - The Ollama upgrade commands ran unprofiled, so Compose would have skipped the profile-scoped `ollama` service and left `OLLAMA_URL` pointing at nothing. Both commands now carry the install's `--profile`. - `start` / `stop` / `restart` are only whole-install operations on Compose. On a source checkout they manage just the Postgres and Redis containers, and on Kubernetes they print kubectl commands rather than acting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the sixth review round - The Ollama upgrade commands hard-coded `--profile gpu` while the comment said to use the install's profile. It is a variable now. - `sim-setup reset` on Kubernetes runs `helm uninstall`, which leaves the Postgres volumes behind — they come from a StatefulSet's `volumeClaimTemplates`, which Kubernetes does not delete. The row promised a full data wipe. - Noted that the command table drops the `npx` prefix for brevity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the seventh review round - "`statement_timeout` is 0, so a long migration is never cut off" was absolute, and `0076_damp_vector.sql` disproves it — it sets 180s locally. It is the session default now, with the override named. - The migration-log command hard-coded the production Compose file, so a source-built or Ollama install would have inspected the wrong stack. - The Compose rollback applied `SIM_VERSION` to every install, but the local and Ollama stacks build from source and ignore it. Those now get the checkout-and-rebuild path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: address the eighth review round `docker-compose.ollama.yml` builds the app, realtime server, and migrator from source, which I had missed: - The Ollama upgrade block ran `pull` then `up -d`. That refreshes only the Ollama and Postgres images and leaves every Sim service on its old build. It is a checkout plus `build --pull` now. - The source rollback showed only the local file and no profile; the Ollama stack needs both. - Dropped the Ollama mention from the `COMPOSE_FILE` comment above, since that stack is covered by its own profile-aware commands rather than the CLI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: audit the docs against source instead of one review round at a time Eight review rounds kept finding defects because I was patching what the last round flagged and writing new prose in the process, which nothing then checked. This is a full pass over the Compose files, the sim-setup CLI, and the chart. The mirror inventory (Greptile's blocker): the bare device-plugin override changes where the chart pulls from, to `<registry>/nvidia/k8s-device-plugin`, while the inventory tells operators to mirror to `<registry>/nvcr.io/nvidia/...`. Following both instructions produced an unpopulated path. Verified by rendering the chart both ways; the header now names the override's destination. Compose: - Nine `docker compose` commands across troubleshooting, verify, and background-jobs had no `-f`. The repo ships no default compose file, so every one of them failed with "no configuration file provided". - `simstudio` declares `env_file: .env` in all three files, so the LM Studio fix does not need a hand-edited `environment:` block — and hand-editing a managed Compose file makes `sim-setup update` refuse. - `OLLAMA_URL` as a one-shot shell prefix reverts to the localhost default on the next `up -d`, pointing the container at itself. It belongs in `.env`. - `pg_dump` through `exec` without `-T` corrupts the dump; the restore beside it already used `-T`. - Live cross-process status is a Redis consequence, not a cron one. - The local Compose file was missing from three "the file that started your install" lists and from the exposed-Postgres warning. - No shipped Compose file defines a `pii` service. - The rollback said "edit .env" but appended a second SIM_VERSION line. sim-setup CLI — the table stated Compose behavior as universal: - `down` runs `helm uninstall` on Kubernetes and removes only Postgres and Redis on a source checkout; both prompt first. - `reset` archives four env files on anything but Compose, including when no install is detected. - `--no-open` was described backwards: the link always prints, the flag skips the download prompt. - `start`/`restart` print port-forward hints on Kubernetes, not equivalents. - `--dir` is a global option and scopes the lifecycle commands. - `--quick` only preselects the setup style; `add` and `desktop` have refusals worth knowing; the update hand-edit refusal is narrower than stated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: parameterize the Ollama rollback profile The rollback hard-coded `--profile gpu` while the upgrade example above it uses a variable, so a CPU install would have started the GPU service. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: correct the Kubernetes and chart claims the audit found The install command was the serious one: the cloud values files hardcode a placeholder domain in six places, and the documented command overrode only `NEXT_PUBLIC_APP_URL`. Following the page literally left sign-in pointed at the placeholder, realtime rejecting every socket upgrade, and the Ingress serving the wrong host — the exact failure the page warns about further down. - The PII recipe's `INTERNAL_API_BASE_URL` self-call is blocked by the NetworkPolicy the same page recommends enabling: the app policy permits Postgres, Redis, realtime, Ollama, PII, telemetry, DNS, and 443, but has no app-to-app rule. Redaction would fail closed. - The GPU device-plugin DaemonSet is not `restricted`-compliant — it sets only two of the four controls and mounts three `hostPath` volumes — so enforcing the label namespace-wide breaks a GPU install. - Under External Secrets the chart writes nothing from `app.env`; an unmapped key fails the render. The page said the two modes behave alike. - The CronJob pods have no NetworkPolicy either. - `app.env.DATABASE_URL` is silently discarded: the chart computes it and inlines it on the container, so the PgBouncer advice pointed at a key that does nothing. - The PDB also renders from `autoscaling.minReplicas`, the reference topology could not render without an `externalDatabase.password`, `copilot`'s security-context keys nest one level deeper, and `NO_PROXY` needs the app's own Service name. - Untangled a garbled `externalSecrets.apiVersion` comment that contradicted its own default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: correct the env-var and runtime-behavior claims the audit found - White-labeling, session policies, and the inbox are already on wherever billing is disabled, which is every self-hosted deployment. The docs told operators to enable white-labeling with a flag it does not need, and listed all thirteen features as symmetric opt-ins. - `NEXT_PUBLIC_CUSTOM_CSS_URL` must be same-origin. The CSP's `style-src` is a hardcoded literal that takes no configured hosts, so both the "point them at absolute URLs" advice and my own earlier "or a host you have added to style-src" were wrong — there is no such remedy short of editing source. - Client variables are read at boot from the container environment, not baked at build. The troubleshooting note invited a pointless image rebuild. - Script migrations run with `lock_timeout = 0` and `statement_timeout = 0`, so one blocked on an app-held lock hangs forever rather than failing with `55P03`. The table described only the SQL phase. - With no mail provider the mailer logs recipient, subject, and sender at `info` and reports success — never the body, and nothing at all at the production default of `ERROR`. - A malformed storage backend stops startup; it does not fall through to a later fully configured one. - `EMAIL_PASSWORD_SIGNUP_ENABLED=false` disables sign-in and OTP too, and `SIGNUP_MX_VALIDATION_ENABLED` covers only email signup. - The Pi template accepts an immutable ref; the page forbade one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: correct the audit pass's own errors, and six it missed Four of these are defects I introduced in the audit commits by applying suggested replacement text without verifying the replacement the way I verified the finding: - `postgresql.enabled: false` alone renders an empty `DATABASE_URL`; `externalDatabase.enabled: true` is required too. I dropped it. - The external-database password check is skipped under `existingSecret` and External Secrets, so "the chart refuses to render" was false. Under ESO the mapping is what matters. - `LOG_LEVEL` is an uppercase-only enum, so the `info` I told operators to set would fail validation. - `add cache` writes `REDIS_URL`, which `realtime` reads too, so recreating only the app container leaves Socket.IO pointed at the old Redis. Six the audits missed: - `PII_URL` used `<release>-pii`, the same fullname bug already fixed for the realtime Service. A manual value is also ignored when `pii.enabled`. - External Redis does not need `redis.enabled: false` — a non-empty `app.env.REDIS_URL` overrides the bundled URL and suppresses its Deployment. - `sim-setup doctor` was recommended to Ollama-stack users, which it cannot detect. - The CA-bundle snippet said "in either case" directly after explaining that an incomplete chain must be repaired server-side. - `update` refuses only the `dev` mode; a source checkout on `docker-compose.local.yml` is a Compose install and does update. - Doctor cannot tell whether migrations are current on a standalone install — the published package ships no migration journal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: describe the PDB activation condition accurately The autoscaling term is computed once from the global `autoscaling` block and reused for both PodDisruptionBudgets, so `minReplicas > 1` creates realtime's even when `autoscaling.realtime.enabled` is false and no realtime HPA exists. Calling the two independent was wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt * fix: use a renderable placeholder for externalDatabase.password The chart validates the password against ^[a-zA-Z0-9._-]+$, so the `<password>` placeholder I added last round made the reference topology fail to render if copied as-is. Replaced with a compliant placeholder and documented the character restriction, which is a real constraint operators hit when generating one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015BwsJTEQRzWJaY4BRCkPZt --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Too many files changed for review (875 files, 100 file limit). |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
There was a problem hiding this comment.
All reported issues were addressed across 875 files
Not reviewed (too large): apps/sim/lib/knowledge/connectors/sync-engine.ts (~2,879 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Re-trigger cubic
…strand it (#7449) Reopening a chat hands the embedded table its saved view through `initialViewId`, which the table honours only while its views query already lists that id. A cached list from before the agent created the view resolves it to nothing, so adoption settles on the default and stamps itself closed — nothing revisits the id when the refetch lands, and the restored view is lost until the tab is reopened. Pin on mount as well as on later changes, so the handoff waits for the list that carries the view. When adoption already applied the same view the table consumes the pin without touching the URL, and a table opened with no saved view still pins nothing. This also makes a first mount agree with a tab switch, which already re-pins the saved view through the same path.
|
Worked through the 13 review threads here. #7451 (against
Three I pushed back on with reasoning on their threads: the custom-blocks tracing wording (the page addresses the publisher, and the toggle is publisher-side), the composer-retains-query one (the suggested fix destroys user-typed text and breaks the queued-edit fix), and the One left for a decision rather than fixed: the table view-pin. Real but needs a >60s stale cache, and the fix changes documented ownership plus |
…on (#7447) * feat(manageengine): add ManageEngine ServiceDesk Plus Cloud integration 31 tools across requests, problems, changes, assets, and knowledge base solutions, with full create/read/list/update/delete on each plus notes on requests, problems, and changes. Auth reuses the existing Zoho OAuth client: ServiceDesk Plus authenticates through Zoho and scopes are chosen per authorization request rather than per registered client, so manageengine-sdp aliases onto the zoho-desk deployment capability and needs no new env pair. Connecting requires a Zoho account in the US data center, matching the existing Zoho connector's limitation. The standalone Tasks module is deliberately not implemented: its endpoints are documented but the scope table publishes no tasks entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HbUGaP3XyrSm3Lk3k959j2 * fix(manageengine): address review findings - display_id on problems, changes and solutions reused the timestamp property map, so generated metadata described a record number like "140" as epoch milliseconds. Give it its own map. - parseSdpResponse turned a non-empty, non-JSON 2xx body into {} and reported success — a proxy or login page answering 200 with HTML would make a read look empty and a delete look like it worked. Throw instead; an empty body is still tolerated. - Scope the docs setup step: connect a Zoho account, with client registration called out as self-hosted only. - The block described notes on "any ITSM record"; only requests, problems and changes have note endpoints here. - Assert all ten data-center hosts, not four, and guard the count so a new code fails the test until asserted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HbUGaP3XyrSm3Lk3k959j2 * fix(manageengine): reject JSON-array response bodies A 2xx body that parses to an array passed the `typeof 'object'` check, so it was accepted as a v3 envelope. It carries no `response_status`, so a list read as empty and a delete reported success. Reject arrays and `null` alongside the scalar case already handled. Also name the display_id children in its description — the docs generator renders one level of output nesting repo-wide, so the shape is otherwise invisible on the page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HbUGaP3XyrSm3Lk3k959j2 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ovider in one place (#7453)
Uh oh!
There was an error while loading. Please reload this page.