@anchrd/intel 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +496 -0
- package/bin/intel.mjs +134 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
- package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
- package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
- package/dist/adapters/cloudflare/cloudflare.js +416 -0
- package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
- package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
- package/dist/adapters/content/content.d.ts +3 -0
- package/dist/adapters/content/content.js +128 -0
- package/dist/adapters/content/content.types.d.ts +38 -0
- package/dist/adapters/content/content.types.js +1 -0
- package/dist/adapters/db/db-audit.d.ts +62 -0
- package/dist/adapters/db/db-audit.js +150 -0
- package/dist/adapters/db/db-boards.d.ts +6 -0
- package/dist/adapters/db/db-boards.js +324 -0
- package/dist/adapters/db/db-feed.d.ts +30 -0
- package/dist/adapters/db/db-feed.js +276 -0
- package/dist/adapters/db/db-flows.d.ts +42 -0
- package/dist/adapters/db/db-flows.js +1311 -0
- package/dist/adapters/db/db-grants.d.ts +133 -0
- package/dist/adapters/db/db-grants.js +269 -0
- package/dist/adapters/db/db-indexing.d.ts +3 -0
- package/dist/adapters/db/db-indexing.js +200 -0
- package/dist/adapters/db/db-oauth.d.ts +3 -0
- package/dist/adapters/db/db-oauth.js +26 -0
- package/dist/adapters/db/db-prompts.d.ts +46 -0
- package/dist/adapters/db/db-prompts.js +144 -0
- package/dist/adapters/db/db.d.ts +6 -0
- package/dist/adapters/db/db.js +1687 -0
- package/dist/adapters/db/db.types.d.ts +16 -0
- package/dist/adapters/db/db.types.js +1 -0
- package/dist/adapters/document-converter/document-converter.d.ts +2 -0
- package/dist/adapters/document-converter/document-converter.js +26 -0
- package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
- package/dist/adapters/document-converter/document-converter.types.js +1 -0
- package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
- package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
- package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
- package/dist/adapters/index-queue/index-queue.d.ts +10 -0
- package/dist/adapters/index-queue/index-queue.js +12 -0
- package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
- package/dist/adapters/index-queue/index-queue.types.js +1 -0
- package/dist/adapters/json-schema/json-schema.d.ts +2 -0
- package/dist/adapters/json-schema/json-schema.js +77 -0
- package/dist/adapters/openid/openid.d.ts +5 -0
- package/dist/adapters/openid/openid.js +301 -0
- package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
- package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
- package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
- package/dist/adapters/remote-tools/remote-tools.js +129 -0
- package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
- package/dist/adapters/semantic-index/semantic-index.js +139 -0
- package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
- package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
- package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
- package/dist/adapters/session-cookie/session-cookie.js +87 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
- package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
- package/dist/audit/audit.d.ts +2 -0
- package/dist/audit/audit.js +87 -0
- package/dist/audit/audit.types.d.ts +23 -0
- package/dist/audit/audit.types.js +1 -0
- package/dist/auth/auth.d.ts +2 -0
- package/dist/auth/auth.js +284 -0
- package/dist/auth/auth.types.d.ts +108 -0
- package/dist/auth/auth.types.js +1 -0
- package/dist/boards/boards.d.ts +44 -0
- package/dist/boards/boards.js +447 -0
- package/dist/boards/boards.types.d.ts +117 -0
- package/dist/boards/boards.types.js +1 -0
- package/dist/build/build.d.ts +2 -0
- package/dist/build/build.js +192 -0
- package/dist/build/build.types.d.ts +17 -0
- package/dist/build/build.types.js +1 -0
- package/dist/bundle/bundle.d.ts +4 -0
- package/dist/bundle/bundle.js +1194 -0
- package/dist/bundle/bundle.types.d.ts +33 -0
- package/dist/bundle/bundle.types.js +1 -0
- package/dist/cli/cli.d.ts +4 -0
- package/dist/cli/cli.js +262 -0
- package/dist/cli/cli.types.d.ts +14 -0
- package/dist/cli/cli.types.js +1 -0
- package/dist/contract/audit.d.ts +50 -0
- package/dist/contract/audit.js +72 -0
- package/dist/contract/board.d.ts +150 -0
- package/dist/contract/board.js +262 -0
- package/dist/contract/bundle.d.ts +147 -0
- package/dist/contract/bundle.js +139 -0
- package/dist/contract/contract.d.ts +112 -0
- package/dist/contract/contract.js +143 -0
- package/dist/contract/feed.d.ts +126 -0
- package/dist/contract/feed.js +149 -0
- package/dist/contract/flow-run.d.ts +346 -0
- package/dist/contract/flow-run.js +194 -0
- package/dist/contract/flow.d.ts +1057 -0
- package/dist/contract/flow.js +552 -0
- package/dist/contract/node.d.ts +551 -0
- package/dist/contract/node.js +456 -0
- package/dist/contract/share.d.ts +216 -0
- package/dist/contract/share.js +134 -0
- package/dist/contract/table.d.ts +171 -0
- package/dist/contract/table.js +117 -0
- package/dist/contract/tool.d.ts +122 -0
- package/dist/contract/tool.js +172 -0
- package/dist/feed/feed.d.ts +2 -0
- package/dist/feed/feed.js +77 -0
- package/dist/feed/feed.types.d.ts +23 -0
- package/dist/feed/feed.types.js +1 -0
- package/dist/flows/flows.d.ts +41 -0
- package/dist/flows/flows.js +2216 -0
- package/dist/flows/flows.types.d.ts +339 -0
- package/dist/flows/flows.types.js +1 -0
- package/dist/http/http.d.ts +9 -0
- package/dist/http/http.js +874 -0
- package/dist/http/http.types.d.ts +22 -0
- package/dist/http/http.types.js +1 -0
- package/dist/indexing/indexing.d.ts +6 -0
- package/dist/indexing/indexing.js +239 -0
- package/dist/indexing/indexing.types.d.ts +11 -0
- package/dist/indexing/indexing.types.js +1 -0
- package/dist/intel/intel.d.ts +3 -0
- package/dist/intel/intel.js +194 -0
- package/dist/intel/intel.types.d.ts +24 -0
- package/dist/intel/intel.types.js +1 -0
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +1292 -0
- package/dist/mcp/mcp.types.d.ts +25 -0
- package/dist/mcp/mcp.types.js +1 -0
- package/dist/nodes/document-links/document-links.d.ts +20 -0
- package/dist/nodes/document-links/document-links.js +57 -0
- package/dist/nodes/nodes.d.ts +2 -0
- package/dist/nodes/nodes.js +1667 -0
- package/dist/nodes/nodes.types.d.ts +511 -0
- package/dist/nodes/nodes.types.js +1 -0
- package/dist/prepare/prepare.d.ts +4 -0
- package/dist/prepare/prepare.js +16 -0
- package/dist/prepare/prepare.types.d.ts +9 -0
- package/dist/prepare/prepare.types.js +1 -0
- package/dist/prompts/prompts.d.ts +2 -0
- package/dist/prompts/prompts.js +65 -0
- package/dist/prompts/prompts.types.d.ts +71 -0
- package/dist/prompts/prompts.types.js +1 -0
- package/dist/shared/callers/callers.d.ts +28 -0
- package/dist/shared/callers/callers.js +30 -0
- package/dist/shared/csv/csv.d.ts +13 -0
- package/dist/shared/csv/csv.js +85 -0
- package/dist/shared/document-text/document-text.d.ts +21 -0
- package/dist/shared/document-text/document-text.js +31 -0
- package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
- package/dist/shared/gate-authorization/gate-authorization.js +26 -0
- package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
- package/dist/shared/grant-expiry/grant-expiry.js +26 -0
- package/dist/shared/intel-error/intel-error.d.ts +22 -0
- package/dist/shared/intel-error/intel-error.js +27 -0
- package/dist/shared/plain-title/plain-title.d.ts +16 -0
- package/dist/shared/plain-title/plain-title.js +45 -0
- package/dist/shared/problem-details/problem-details.d.ts +30 -0
- package/dist/shared/problem-details/problem-details.js +18 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
- package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
- package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
- package/dist/shared/safe-return-path/safe-return-path.js +11 -0
- package/dist/shared/sha256/sha256.d.ts +2 -0
- package/dist/shared/sha256/sha256.js +14 -0
- package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
- package/dist/tools/tool-servers/tool-servers.js +177 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +207 -0
- package/dist/tools/tools.types.d.ts +56 -0
- package/dist/tools/tools.types.js +1 -0
- package/examples/branding/favicon.svg +5 -0
- package/examples/branding/logo.svg +5 -0
- package/examples/branding/theme.css +16 -0
- package/examples/dev.vars.example +7 -0
- package/examples/intel.json +9 -0
- package/migrations/0000_intel_foundation.sql +203 -0
- package/migrations/0001_portal_native_tools.sql +21 -0
- package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
- package/migrations/0003_folder_permissions.sql +211 -0
- package/migrations/0004_subflow_runs.sql +14 -0
- package/migrations/0005_flow_node_cleanup.sql +28 -0
- package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
- package/migrations/0006_links_are_written_in_the_text.sql +20 -0
- package/migrations/0007_no_node_waits.sql +69 -0
- package/migrations/0008_three_layers.sql +130 -0
- package/migrations/0009_no_context_policy.sql +48 -0
- package/migrations/0010_no_run_waits_a_year.sql +27 -0
- package/migrations/0011_one_name_for_the_tree.sql +53 -0
- package/migrations/0012_table_snapshots.sql +29 -0
- package/migrations/0017_a_vector_per_card.sql +38 -0
- package/migrations/0018_no_context_policy_at_last.sql +97 -0
- package/migrations/0019_one_name_for_the_grants.sql +52 -0
- package/migrations/0020_cascade_purge_replay.sql +11 -0
- package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
- package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
- package/migrations/0023_a_board_and_its_tasks.sql +116 -0
- package/migrations/0024_the_archive_is_a_column.sql +33 -0
- package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
- package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
- package/migrations/0027_the_runs_of_every_flow.sql +27 -0
- package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
- package/package.json +118 -0
- package/src/contract/CLAUDE.md +152 -0
- package/src/contract/README.md +102 -0
- package/src/contract/audit.ts +85 -0
- package/src/contract/board.ts +306 -0
- package/src/contract/bundle.ts +157 -0
- package/src/contract/contract.ts +161 -0
- package/src/contract/feed.ts +165 -0
- package/src/contract/flow-run.ts +241 -0
- package/src/contract/flow.ts +675 -0
- package/src/contract/node.ts +583 -0
- package/src/contract/share.ts +196 -0
- package/src/contract/table.ts +178 -0
- package/src/contract/tool.ts +202 -0
- package/ui/README.md +110 -0
- package/ui/components.json +21 -0
- package/ui/index.html +13 -0
- package/ui/package.json +11 -0
- package/ui/scripts/lint-tokens.mjs +187 -0
- package/ui/src/access-summary/access-summary.tsx +385 -0
- package/ui/src/app/action-slot/action-slot.tsx +27 -0
- package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
- package/ui/src/app/app-tree/app-tree.tsx +1175 -0
- package/ui/src/app/app.tsx +87 -0
- package/ui/src/app/header-search/header-search.tsx +294 -0
- package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
- package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
- package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
- package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
- package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
- package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
- package/ui/src/app/tree-move/tree-move.tsx +427 -0
- package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
- package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
- package/ui/src/app/user-footer/user-footer.tsx +188 -0
- package/ui/src/app/view-menu/view-menu.tsx +115 -0
- package/ui/src/app-root/app-root.tsx +109 -0
- package/ui/src/app-root/app-root.types.ts +27 -0
- package/ui/src/archive/archive.tsx +417 -0
- package/ui/src/attachment-detail/attachment-detail.ts +52 -0
- package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
- package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
- package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
- package/ui/src/board/board-assignee/board-assignee.ts +48 -0
- package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
- package/ui/src/board/board-chip/board-chip.tsx +46 -0
- package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
- package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
- package/ui/src/board/board-data/board-data.ts +195 -0
- package/ui/src/board/board-data/board-data.types.ts +38 -0
- package/ui/src/board/board-dates/board-dates.ts +40 -0
- package/ui/src/board/board-dates/board-dates.tsx +105 -0
- package/ui/src/board/board-kanban/board-kanban.ts +162 -0
- package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
- package/ui/src/board/board-open-task/board-open-task.ts +32 -0
- package/ui/src/board/board-panel/board-panel.tsx +259 -0
- package/ui/src/board/board-settings/board-settings.tsx +226 -0
- package/ui/src/board/board-status/board-status.tsx +60 -0
- package/ui/src/board/board-stripes/board-stripes.ts +128 -0
- package/ui/src/board/board-table/board-table.ts +53 -0
- package/ui/src/board/board-table/board-table.tsx +610 -0
- package/ui/src/board/board-task/board-task.ts +108 -0
- package/ui/src/board/board-task/board-task.tsx +335 -0
- package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
- package/ui/src/branding/branding.tsx +17 -0
- package/ui/src/branding/custom-logo.ts +1 -0
- package/ui/src/branding/favicon.default.svg +5 -0
- package/ui/src/branding/favicon.svg +5 -0
- package/ui/src/components/ui/avatar.tsx +39 -0
- package/ui/src/components/ui/button.tsx +64 -0
- package/ui/src/components/ui/collapsible.tsx +20 -0
- package/ui/src/components/ui/command.tsx +160 -0
- package/ui/src/components/ui/dialog.tsx +143 -0
- package/ui/src/components/ui/dropdown-menu.tsx +187 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/popover.tsx +41 -0
- package/ui/src/components/ui/select.tsx +163 -0
- package/ui/src/components/ui/separator.tsx +26 -0
- package/ui/src/components/ui/sheet.tsx +136 -0
- package/ui/src/components/ui/sidebar.tsx +693 -0
- package/ui/src/components/ui/skeleton.tsx +13 -0
- package/ui/src/components/ui/switch.tsx +25 -0
- package/ui/src/components/ui/table.tsx +82 -0
- package/ui/src/components/ui/tabs.tsx +52 -0
- package/ui/src/components/ui/tooltip.tsx +51 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
- package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
- package/ui/src/data/query-client.ts +18 -0
- package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
- package/ui/src/data/request-refusal/request-refusal.ts +51 -0
- package/ui/src/data/sign-in/sign-in.ts +97 -0
- package/ui/src/data/sign-in/sign-in.types.ts +15 -0
- package/ui/src/document-link/document-link.tsx +114 -0
- package/ui/src/document-markdown/document-markdown.ts +195 -0
- package/ui/src/editor-schema/editor-schema.ts +23 -0
- package/ui/src/entry-picker/entry-picker.tsx +238 -0
- package/ui/src/feed/feed.tsx +344 -0
- package/ui/src/file-preview/file-preview-view.tsx +496 -0
- package/ui/src/file-preview/file-preview.tsx +28 -0
- package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
- package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
- package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
- package/ui/src/file-preview/word-file-preview.tsx +5 -0
- package/ui/src/flow-runs/flow-runs.tsx +238 -0
- package/ui/src/flows/flows.tsx +1646 -0
- package/ui/src/flows/node-icon/node-icon.ts +34 -0
- package/ui/src/flows/node-palette/node-palette.tsx +214 -0
- package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
- package/ui/src/folder-contents/folder-contents.tsx +321 -0
- package/ui/src/folder-index/folder-index.ts +55 -0
- package/ui/src/frontmatter/frontmatter.tsx +193 -0
- package/ui/src/graph-pane/graph-pane.tsx +53 -0
- package/ui/src/hooks/use-mobile.ts +19 -0
- package/ui/src/i18n/custom.json +4 -0
- package/ui/src/i18n/de.json +609 -0
- package/ui/src/i18n/en.json +609 -0
- package/ui/src/i18n/es.json +609 -0
- package/ui/src/i18n/i18n-context.tsx +65 -0
- package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
- package/ui/src/i18n/i18n.ts +32 -0
- package/ui/src/i18n/i18n.types.ts +49 -0
- package/ui/src/kind-icon.ts +97 -0
- package/ui/src/lib/utils.ts +6 -0
- package/ui/src/main.tsx +68 -0
- package/ui/src/modal/modal.tsx +53 -0
- package/ui/src/node-details/node-details.tsx +545 -0
- package/ui/src/node-editor/node-editor.tsx +304 -0
- package/ui/src/node-graph/graph-notice.tsx +21 -0
- package/ui/src/node-graph/node-graph.ts +214 -0
- package/ui/src/node-graph/node-graph.tsx +295 -0
- package/ui/src/node-import/node-import.tsx +418 -0
- package/ui/src/node-save-error.ts +46 -0
- package/ui/src/node-source/node-source.tsx +128 -0
- package/ui/src/node-table/node-table.tsx +107 -0
- package/ui/src/nodes/nodes.tsx +240 -0
- package/ui/src/rename-resource/rename-resource.ts +80 -0
- package/ui/src/resolved-names/resolved-names.tsx +67 -0
- package/ui/src/resource-error.ts +33 -0
- package/ui/src/resource-menu/resource-menu.tsx +1156 -0
- package/ui/src/router/router-context.ts +6 -0
- package/ui/src/router/router.tsx +67 -0
- package/ui/src/router/router.types.ts +8 -0
- package/ui/src/router/selection-search.ts +88 -0
- package/ui/src/save-button/save-button.tsx +94 -0
- package/ui/src/section-hint/section-hint.tsx +40 -0
- package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
- package/ui/src/styles.css +189 -0
- package/ui/src/table-columns/table-columns.tsx +379 -0
- package/ui/src/theme/custom.css +1 -0
- package/ui/src/theme/theme-context.tsx +89 -0
- package/ui/src/theme/theme.ts +70 -0
- package/ui/src/time/relative-time.tsx +41 -0
- package/ui/src/time/time-context.tsx +78 -0
- package/ui/src/time/time.ts +165 -0
- package/ui/src/title-row/title-row.tsx +185 -0
- package/ui/src/tools/tools.tsx +546 -0
- package/ui/src/user-name/user-name.ts +61 -0
- package/ui/src/verbatim/verbatim.tsx +68 -0
- package/ui/tsconfig.json +17 -0
- package/ui/vite.config.ts +129 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { IdempotencyKey, IntelId, IsoDateTime, NamedOrCounted } from "./contract.ts";
|
|
3
|
+
|
|
4
|
+
// ⚠️ There is no `ContextPolicy`, and it is not coming back in this shape (#76). It said whether a
|
|
5
|
+
// document should be pinned into a context, be found by relevance, or be named explicitly — an
|
|
6
|
+
// instruction to a retrieval Intel does not perform. Intel hands out references and the agent
|
|
7
|
+
// fetches what it needs (D24), so nothing here could ever have read it, and nothing did.
|
|
8
|
+
//
|
|
9
|
+
// Semantic search stays: as an MCP tool the agent calls, over everything or over one area.
|
|
10
|
+
|
|
11
|
+
// One verb per grant, granted independently (ADR-0004 §2). Not a ladder: seeing a process must be
|
|
12
|
+
// separable from being allowed to start it, and `execute` is meaningful only where a flow can live.
|
|
13
|
+
export const ResourceVerb = z.enum(["read", "write", "execute", "share"]);
|
|
14
|
+
export type ResourceVerb = z.infer<typeof ResourceVerb>;
|
|
15
|
+
|
|
16
|
+
export const SharePrincipal = z.discriminatedUnion("type", [
|
|
17
|
+
z.strictObject({ type: z.literal("user"), id: IntelId }),
|
|
18
|
+
z.strictObject({ type: z.literal("email"), email: z.email() }),
|
|
19
|
+
z.strictObject({ type: z.literal("organization") }),
|
|
20
|
+
]);
|
|
21
|
+
export type SharePrincipal = z.infer<typeof SharePrincipal>;
|
|
22
|
+
|
|
23
|
+
export const ListGrantsInput = z.strictObject({
|
|
24
|
+
resourceId: IntelId.describe(
|
|
25
|
+
"Node whose direct grants to list. Access inherited from a folder above is not a grant on this node and is not listed here.",
|
|
26
|
+
),
|
|
27
|
+
});
|
|
28
|
+
export type ListGrantsInput = z.infer<typeof ListGrantsInput>;
|
|
29
|
+
|
|
30
|
+
// The same three shapes for a flow (#530). Separate inputs rather than a widened `resourceId`,
|
|
31
|
+
// because the id names a different table and a caller that passes the wrong kind should be told so
|
|
32
|
+
// by the route it called rather than by a lookup that finds nothing.
|
|
33
|
+
export const ListFlowGrantsInput = z.strictObject({
|
|
34
|
+
flowId: IntelId.describe(
|
|
35
|
+
"Flow whose direct grants to list. Access inherited from the folder it is filed in is not a grant on this flow and is not listed here.",
|
|
36
|
+
),
|
|
37
|
+
});
|
|
38
|
+
export type ListFlowGrantsInput = z.infer<typeof ListFlowGrantsInput>;
|
|
39
|
+
|
|
40
|
+
// ⚠️ A DIFFERENT question from the two above, not a fuller answer to them (#586). The `*GrantsInput`
|
|
41
|
+
// pair asks what sits ON this one resource; these two ask who REACHES it once inheritance is
|
|
42
|
+
// resolved — every grant along the folders above plus the owners, who need no grant row at all. On
|
|
43
|
+
// the everyday resource, whose access comes from the folder it is filed in, the first answer is
|
|
44
|
+
// empty and this one is not, so a caller that reaches for the wrong one is told "nobody" about
|
|
45
|
+
// something half the organization can open.
|
|
46
|
+
//
|
|
47
|
+
// Two inputs rather than one widened `resourceId`, for the reason the pair above gives: the id
|
|
48
|
+
// names a different table, and the queries behind them are two statements over two sets of tables.
|
|
49
|
+
export const ListEffectiveAccessInput = z.strictObject({
|
|
50
|
+
resourceId: IntelId.describe(
|
|
51
|
+
"Node to answer for. The answer crosses the folders above it and names owners as well as grants, so it is not the list node_grant_list gives.",
|
|
52
|
+
),
|
|
53
|
+
});
|
|
54
|
+
export type ListEffectiveAccessInput = z.infer<typeof ListEffectiveAccessInput>;
|
|
55
|
+
|
|
56
|
+
export const ListFlowEffectiveAccessInput = z.strictObject({
|
|
57
|
+
flowId: IntelId.describe(
|
|
58
|
+
"Flow to answer for. The answer includes what the folder it is filed in passes down and names owners as well as grants, so it is not the list flow_grant_list gives.",
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
export type ListFlowEffectiveAccessInput = z.infer<typeof ListFlowEffectiveAccessInput>;
|
|
62
|
+
|
|
63
|
+
export const ResourceGrant = z.strictObject({
|
|
64
|
+
id: IntelId,
|
|
65
|
+
resourceId: IntelId,
|
|
66
|
+
principal: SharePrincipal,
|
|
67
|
+
verb: ResourceVerb,
|
|
68
|
+
expiresAt: IsoDateTime.nullable(),
|
|
69
|
+
createdBy: IntelId,
|
|
70
|
+
createdAt: IsoDateTime,
|
|
71
|
+
});
|
|
72
|
+
export type ResourceGrant = z.infer<typeof ResourceGrant>;
|
|
73
|
+
|
|
74
|
+
// The word means exactly the same thing on a node grant and on a flow grant, so it is described
|
|
75
|
+
// once — the rule this package states about `IdempotencyKey`, applied within one file rather than
|
|
76
|
+
// across the package. Two copies are two places to drift, and both of them ship in `tools/list`.
|
|
77
|
+
//
|
|
78
|
+
// ⚠️ What is refused is decided by `deps.now()` in the application layer and cannot be expressed
|
|
79
|
+
// here (#442); this sentence names the answer a caller gets, it does not produce it.
|
|
80
|
+
const GrantExpiresAt = IsoDateTime.nullable()
|
|
81
|
+
.default(null)
|
|
82
|
+
.describe(
|
|
83
|
+
"When the grant stops working, or `null` for one that does not expire on its own. A time that is not in the future is refused with `grant_already_expired`: a grant that could never work is not created.",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
export const ShareInput = z.strictObject({
|
|
87
|
+
resourceId: IntelId.describe(
|
|
88
|
+
"Node to grant access to. A grant on a folder is inherited by everything beneath it, which is the usual way to share a whole area.",
|
|
89
|
+
),
|
|
90
|
+
principal: SharePrincipal.describe(
|
|
91
|
+
"Who gets the access: a Gate user by id, someone by verified email address, or the whole organization.",
|
|
92
|
+
),
|
|
93
|
+
verb: ResourceVerb.describe(
|
|
94
|
+
"What they may do. Each verb is granted on its own and none implies another: `read` reads, `write` writes, `execute` runs a flow, `share` passes access on. Seeing a process is deliberately separable from being allowed to start it.",
|
|
95
|
+
),
|
|
96
|
+
expiresAt: GrantExpiresAt,
|
|
97
|
+
idempotencyKey: IdempotencyKey,
|
|
98
|
+
});
|
|
99
|
+
export type ShareInput = z.infer<typeof ShareInput>;
|
|
100
|
+
|
|
101
|
+
export const RevokeGrantInput = z.strictObject({
|
|
102
|
+
resourceId: IntelId.describe(
|
|
103
|
+
"Node the grant sits on. Named alongside the grant id so access is decided on the node.",
|
|
104
|
+
),
|
|
105
|
+
grantId: IntelId.describe("The grant to withdraw, from node_grant_list."),
|
|
106
|
+
idempotencyKey: IdempotencyKey,
|
|
107
|
+
});
|
|
108
|
+
export type RevokeGrantInput = z.infer<typeof RevokeGrantInput>;
|
|
109
|
+
|
|
110
|
+
export const ShareFlowInput = z.strictObject({
|
|
111
|
+
flowId: IntelId.describe(
|
|
112
|
+
"Flow to grant access to. A grant here reaches this flow and nothing else — not the folder it is filed in, and not the flows it calls.",
|
|
113
|
+
),
|
|
114
|
+
principal: SharePrincipal.describe(
|
|
115
|
+
"Who gets the access: a Gate user by id, someone by verified email address, or the whole organization.",
|
|
116
|
+
),
|
|
117
|
+
verb: ResourceVerb.describe(
|
|
118
|
+
"What they may do. Each verb is granted on its own and none implies another: `read` opens the flow, `write` edits it, `execute` runs it, `share` passes access on.",
|
|
119
|
+
),
|
|
120
|
+
expiresAt: GrantExpiresAt,
|
|
121
|
+
idempotencyKey: IdempotencyKey,
|
|
122
|
+
});
|
|
123
|
+
export type ShareFlowInput = z.infer<typeof ShareFlowInput>;
|
|
124
|
+
|
|
125
|
+
export const RevokeFlowGrantInput = z.strictObject({
|
|
126
|
+
flowId: IntelId.describe(
|
|
127
|
+
"Flow the grant sits on. Named alongside the grant id so access is decided on the flow.",
|
|
128
|
+
),
|
|
129
|
+
grantId: IntelId.describe("The grant to withdraw, from flow_grant_list."),
|
|
130
|
+
idempotencyKey: IdempotencyKey,
|
|
131
|
+
});
|
|
132
|
+
export type RevokeFlowGrantInput = z.infer<typeof RevokeFlowGrantInput>;
|
|
133
|
+
|
|
134
|
+
// What a grant does not cover, reported to whoever just made it. A flow in the shared folder may
|
|
135
|
+
// read a document outside it, and the run is re-authorized against the person running it — so the
|
|
136
|
+
// grant can be complete and the flow still stop for them (ADR-0004 §4).
|
|
137
|
+
//
|
|
138
|
+
// ⚠️ `titles` holds only the documents the sharer may see; everything else is in `hidden` as a
|
|
139
|
+
// number. A warning must not become a way of reading titles out of the tree.
|
|
140
|
+
//
|
|
141
|
+
// It IS `NamedOrCounted` — one schema, under the name this domain calls it by. The shape moved to
|
|
142
|
+
// `contract.ts` when a refusal started carrying it too (#448); it is not restated here, because two
|
|
143
|
+
// declarations of one rule are two places for it to drift.
|
|
144
|
+
export const UnreadableNodes = NamedOrCounted;
|
|
145
|
+
export type UnreadableNodes = z.infer<typeof UnreadableNodes>;
|
|
146
|
+
|
|
147
|
+
// The grant is in the answer, so the warning cannot be mistaken for a refusal: it is written first
|
|
148
|
+
// and described afterwards. Blocking would force everyone who uses a central policy document to
|
|
149
|
+
// duplicate it, which is the opposite of what one tree is for (ADR-0004 §4).
|
|
150
|
+
//
|
|
151
|
+
// ⚠️ `unrunnable` is the second half of the same sentence and belongs to the flow grant (#530). A
|
|
152
|
+
// grant on a folder covered every flow beneath it, so there was nothing to say; a grant on one flow
|
|
153
|
+
// reaches that flow and stops, and the sub-flows it calls are then somebody else's to grant. It
|
|
154
|
+
// carries the shape of `UnreadableNodes` because it answers the same question — what this grant
|
|
155
|
+
// does NOT reach, named where the sharer may see it and counted where they may not — and it is
|
|
156
|
+
// empty for every node grant.
|
|
157
|
+
export const ShareResult = z.strictObject({
|
|
158
|
+
grant: ResourceGrant,
|
|
159
|
+
unreadable: UnreadableNodes,
|
|
160
|
+
unrunnable: UnreadableNodes.default({ titles: [], hidden: 0 }),
|
|
161
|
+
});
|
|
162
|
+
export type ShareResult = z.infer<typeof ShareResult>;
|
|
163
|
+
|
|
164
|
+
// `applicableVerbs` travels with the list because the answer is the business layer's, not the
|
|
165
|
+
// screen's: a document has nothing to execute, so `execute` is not offered on one (ADR-0004 §2).
|
|
166
|
+
export const ResourceGrantList = z.strictObject({
|
|
167
|
+
resourceId: IntelId,
|
|
168
|
+
applicableVerbs: z.array(ResourceVerb).min(1),
|
|
169
|
+
items: z.array(ResourceGrant),
|
|
170
|
+
});
|
|
171
|
+
export type ResourceGrantList = z.infer<typeof ResourceGrantList>;
|
|
172
|
+
|
|
173
|
+
// A display-only, effective view. Unlike ResourceGrantList it crosses ancestor folders and names
|
|
174
|
+
// owners, because ownership grants access without creating a revocable grant row.
|
|
175
|
+
//
|
|
176
|
+
// ⚠️ `sources` names the folders the inherited grants sit on, so a reader can be told WHERE to go
|
|
177
|
+
// to take one back rather than "somewhere above" (#803). It carries only the folders this reader
|
|
178
|
+
// may see, by the rule `resolveVisibleTitles` follows for document links (#41): a folder that is
|
|
179
|
+
// not theirs to see is absent, and absent is not distinguishable from "no longer there". A grant
|
|
180
|
+
// can be in force from a folder the reader may not open — naming it would disclose an ancestor the
|
|
181
|
+
// tree never showed them.
|
|
182
|
+
export const ResourceAccessList = z.strictObject({
|
|
183
|
+
resourceId: IntelId,
|
|
184
|
+
ownerIds: z.array(IntelId),
|
|
185
|
+
items: z.array(ResourceGrant),
|
|
186
|
+
sources: z
|
|
187
|
+
.array(z.strictObject({ resourceId: IntelId, title: z.string() }))
|
|
188
|
+
.default([])
|
|
189
|
+
.describe(
|
|
190
|
+
"The folders the inherited grants sit on, named where the caller may see them. A grant whose resourceId is absent here is still in force; it is only unnamed.",
|
|
191
|
+
),
|
|
192
|
+
});
|
|
193
|
+
export type ResourceAccessList = z.infer<typeof ResourceAccessList>;
|
|
194
|
+
|
|
195
|
+
export const RevokeGrantResult = z.strictObject({ revoked: z.boolean() });
|
|
196
|
+
export type RevokeGrantResult = z.infer<typeof RevokeGrantResult>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { IdempotencyKey, IntelId } from "./contract.ts";
|
|
3
|
+
import { Node, NodeVersion } from "./node.ts";
|
|
4
|
+
|
|
5
|
+
const distinctPositions = { error: "Row positions must be distinct" };
|
|
6
|
+
|
|
7
|
+
// A table is CSV, and CSV is the whole format: it is what is stored, what is downloaded and what a
|
|
8
|
+
// machine reads. There is no second representation to keep in step with it (#40).
|
|
9
|
+
export const TableMediaType = "text/csv";
|
|
10
|
+
|
|
11
|
+
// A column name is the contract between the table and everyone who appends to it, so it is trimmed,
|
|
12
|
+
// non-empty and bounded like a title. Cells are not: a cell is text, and text is what CSV carries.
|
|
13
|
+
export const TableColumn = z.string().trim().min(1).max(120);
|
|
14
|
+
export const TableCell = z.string().max(4_000);
|
|
15
|
+
export const TableRow = z.array(TableCell).min(1).max(64);
|
|
16
|
+
|
|
17
|
+
// Writing the header, once. The columns are the contract (#40's comment), which is why this refuses
|
|
18
|
+
// on a table that already has one: changing the header would silently reinterpret every row that
|
|
19
|
+
// was appended under the old one.
|
|
20
|
+
export const DefineTableInput = z.strictObject({
|
|
21
|
+
nodeId: IntelId.describe("Table node whose header is being written. It must not have one yet."),
|
|
22
|
+
columns: z
|
|
23
|
+
.array(TableColumn)
|
|
24
|
+
.describe(
|
|
25
|
+
"The column names, in order. They are the contract every later append is checked against, which is why this refuses on a table that already has a header — changing it would silently reinterpret the stored rows. Use node_table_update for that.",
|
|
26
|
+
)
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(64)
|
|
29
|
+
.refine(
|
|
30
|
+
(columns) => new Set(columns.map((column) => column.toLowerCase())).size === columns.length,
|
|
31
|
+
{ error: "Column names must be distinct" },
|
|
32
|
+
),
|
|
33
|
+
idempotencyKey: IdempotencyKey,
|
|
34
|
+
});
|
|
35
|
+
export type DefineTableInput = z.infer<typeof DefineTableInput>;
|
|
36
|
+
|
|
37
|
+
// ⚠️ No `baseVersionId`, and that absence is the feature. A document replaces its content and needs
|
|
38
|
+
// to know which content it replaces; an append adds to the end and cannot collide with a second
|
|
39
|
+
// append, so demanding a base version would invent a conflict that does not exist and force the
|
|
40
|
+
// caller to read the whole table first — the exact cost #40 exists to remove.
|
|
41
|
+
export const AppendTableRowsInput = z.strictObject({
|
|
42
|
+
nodeId: IntelId.describe("Table node to append to. It must already have a header."),
|
|
43
|
+
rows: z
|
|
44
|
+
.array(TableRow)
|
|
45
|
+
.min(1)
|
|
46
|
+
.max(1_000)
|
|
47
|
+
.describe(
|
|
48
|
+
"Rows to add at the end, each an array of cell strings in the header's order. No base version is needed: appending collides with nothing, so the table does not have to be read first.",
|
|
49
|
+
),
|
|
50
|
+
idempotencyKey: IdempotencyKey,
|
|
51
|
+
});
|
|
52
|
+
export type AppendTableRowsInput = z.infer<typeof AppendTableRowsInput>;
|
|
53
|
+
|
|
54
|
+
export const GetTableInput = z.strictObject({
|
|
55
|
+
nodeId: IntelId.describe("Table node to read as a grid: header and every row."),
|
|
56
|
+
});
|
|
57
|
+
export type GetTableInput = z.infer<typeof GetTableInput>;
|
|
58
|
+
|
|
59
|
+
export const AppendTableRowsResult = z.strictObject({
|
|
60
|
+
node: Node,
|
|
61
|
+
version: NodeVersion,
|
|
62
|
+
appended: z.number().int().positive(),
|
|
63
|
+
});
|
|
64
|
+
export type AppendTableRowsResult = z.infer<typeof AppendTableRowsResult>;
|
|
65
|
+
|
|
66
|
+
// A row's address is its position among the table's current rows, counted from zero and without the
|
|
67
|
+
// header. Deliberately not an ID: rows carry no identity of their own (#135, and the same decision
|
|
68
|
+
// the grid documents), so every mutation instead pins the state its positions refer to.
|
|
69
|
+
export const TableRowPosition = z.number().int().nonnegative();
|
|
70
|
+
|
|
71
|
+
// Replacing rows in place (#135). `baseVersionId` is the version the caller read the positions
|
|
72
|
+
// from — required, never nullable, because a position into a table one has not read is a guess.
|
|
73
|
+
// A table that moved on since answers `version_conflict` rather than editing the wrong rows; that
|
|
74
|
+
// is the same optimistic concurrency the document save uses, and the deliberate opposite of
|
|
75
|
+
// `append`, which needs no base because it collides with nothing.
|
|
76
|
+
export const UpdateTableRowsInput = z.strictObject({
|
|
77
|
+
nodeId: IntelId.describe("Table node to change rows in."),
|
|
78
|
+
baseVersionId: IntelId.describe(
|
|
79
|
+
"The `versionId` from the node_table_get whose rows these positions were counted in. Required and never null: a position into a table nobody has read is a guess. If the table has moved on since, the call is refused rather than editing the wrong rows.",
|
|
80
|
+
),
|
|
81
|
+
updates: z
|
|
82
|
+
.array(
|
|
83
|
+
z.strictObject({
|
|
84
|
+
position: TableRowPosition.describe(
|
|
85
|
+
"Zero-based position of the row to replace, counting data rows and not the header.",
|
|
86
|
+
),
|
|
87
|
+
row: TableRow.describe(
|
|
88
|
+
"The complete replacement row, as cell strings in the header's order.",
|
|
89
|
+
),
|
|
90
|
+
}),
|
|
91
|
+
)
|
|
92
|
+
.describe(
|
|
93
|
+
"Which rows to replace and with what. `position` is zero-based and counts data rows, not the header; `row` is the complete replacement, not a patch.",
|
|
94
|
+
)
|
|
95
|
+
.min(1)
|
|
96
|
+
.max(1_000)
|
|
97
|
+
.refine(
|
|
98
|
+
(updates) => new Set(updates.map((update) => update.position)).size === updates.length,
|
|
99
|
+
distinctPositions,
|
|
100
|
+
),
|
|
101
|
+
idempotencyKey: IdempotencyKey,
|
|
102
|
+
});
|
|
103
|
+
export type UpdateTableRowsInput = z.infer<typeof UpdateTableRowsInput>;
|
|
104
|
+
|
|
105
|
+
export const UpdateTableRowsResult = z.strictObject({
|
|
106
|
+
node: Node,
|
|
107
|
+
version: NodeVersion,
|
|
108
|
+
updated: z.number().int().positive(),
|
|
109
|
+
});
|
|
110
|
+
export type UpdateTableRowsResult = z.infer<typeof UpdateTableRowsResult>;
|
|
111
|
+
|
|
112
|
+
export const DeleteTableRowsInput = z.strictObject({
|
|
113
|
+
nodeId: IntelId.describe("Table node to remove rows from."),
|
|
114
|
+
baseVersionId: IntelId.describe(
|
|
115
|
+
"The `versionId` from the node_table_get these positions were counted in. A table that has moved on since is refused rather than losing the wrong rows.",
|
|
116
|
+
),
|
|
117
|
+
positions: z
|
|
118
|
+
.array(TableRowPosition)
|
|
119
|
+
.describe(
|
|
120
|
+
"Zero-based positions of the data rows to remove. All of them go in one call; the positions are read against the base version, so they do not shift while it runs.",
|
|
121
|
+
)
|
|
122
|
+
.min(1)
|
|
123
|
+
.max(1_000)
|
|
124
|
+
.refine((positions) => new Set(positions).size === positions.length, distinctPositions),
|
|
125
|
+
idempotencyKey: IdempotencyKey,
|
|
126
|
+
});
|
|
127
|
+
export type DeleteTableRowsInput = z.infer<typeof DeleteTableRowsInput>;
|
|
128
|
+
|
|
129
|
+
export const DeleteTableRowsResult = z.strictObject({
|
|
130
|
+
node: Node,
|
|
131
|
+
version: NodeVersion,
|
|
132
|
+
deleted: z.number().int().positive(),
|
|
133
|
+
});
|
|
134
|
+
export type DeleteTableRowsResult = z.infer<typeof DeleteTableRowsResult>;
|
|
135
|
+
|
|
136
|
+
// One entry per column the table will have afterwards, in order. `source` names the current column
|
|
137
|
+
// whose cells fill it; `null` adds an empty column, and a current column no entry names is removed
|
|
138
|
+
// together with its cells. Renaming is naming a source under a new name.
|
|
139
|
+
export const RedefineTableColumn = z.strictObject({
|
|
140
|
+
name: TableColumn.describe("What the column is called afterwards."),
|
|
141
|
+
source: TableColumn.nullable()
|
|
142
|
+
.default(null)
|
|
143
|
+
.describe(
|
|
144
|
+
"The current column whose cells fill it, by its current name, or `null` for a new empty column.",
|
|
145
|
+
),
|
|
146
|
+
});
|
|
147
|
+
export type RedefineTableColumn = z.infer<typeof RedefineTableColumn>;
|
|
148
|
+
|
|
149
|
+
// Changing the header of a table that has one (#135). The mapping is explicit because it is the
|
|
150
|
+
// whole difference to the blind re-definition `defineTable` keeps refusing: without it a new header
|
|
151
|
+
// would silently reinterpret every stored row under names nobody matched to the old ones.
|
|
152
|
+
export const RedefineTableInput = z.strictObject({
|
|
153
|
+
nodeId: IntelId.describe("Table node whose header is being changed."),
|
|
154
|
+
baseVersionId: IntelId.describe(
|
|
155
|
+
"The `versionId` from the node_table_get this mapping was written against. A table that has moved on since is refused.",
|
|
156
|
+
),
|
|
157
|
+
columns: z
|
|
158
|
+
.array(RedefineTableColumn)
|
|
159
|
+
.describe(
|
|
160
|
+
"The complete new header, in order. Each entry names the current column whose cells fill it, or `null` for a new empty one; a current column that no entry names is removed with its cells. Renaming is naming a source under a new name. The mapping is explicit on purpose — a new header without one would reinterpret every stored row.",
|
|
161
|
+
)
|
|
162
|
+
.min(1)
|
|
163
|
+
.max(64)
|
|
164
|
+
.refine(
|
|
165
|
+
(columns) =>
|
|
166
|
+
new Set(columns.map((column) => column.name.toLowerCase())).size === columns.length,
|
|
167
|
+
{ error: "Column names must be distinct" },
|
|
168
|
+
)
|
|
169
|
+
.refine(
|
|
170
|
+
(columns) => {
|
|
171
|
+
const sources = columns.map((column) => column.source).filter((source) => source !== null);
|
|
172
|
+
return new Set(sources).size === sources.length;
|
|
173
|
+
},
|
|
174
|
+
{ error: "A current column can fill only one new column" },
|
|
175
|
+
),
|
|
176
|
+
idempotencyKey: IdempotencyKey,
|
|
177
|
+
});
|
|
178
|
+
export type RedefineTableInput = z.infer<typeof RedefineTableInput>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
function normalizedHostname(url: URL): string {
|
|
4
|
+
return url.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isPrivateIpv4(hostname: string): boolean {
|
|
8
|
+
const parts = hostname.split(".").map(Number);
|
|
9
|
+
if (
|
|
10
|
+
parts.length !== 4 ||
|
|
11
|
+
parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)
|
|
12
|
+
) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const [first = 0, second = 0] = parts;
|
|
16
|
+
return (
|
|
17
|
+
first === 0 ||
|
|
18
|
+
first === 10 ||
|
|
19
|
+
first === 127 ||
|
|
20
|
+
(first === 100 && second >= 64 && second <= 127) ||
|
|
21
|
+
(first === 169 && second === 254) ||
|
|
22
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
23
|
+
(first === 192 && second === 168) ||
|
|
24
|
+
(first === 198 && (second === 18 || second === 19)) ||
|
|
25
|
+
first >= 224
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isPublicToolHost(url: URL): boolean {
|
|
30
|
+
const hostname = normalizedHostname(url);
|
|
31
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
|
|
32
|
+
return url.protocol === "http:";
|
|
33
|
+
}
|
|
34
|
+
if (
|
|
35
|
+
!hostname.includes(".") ||
|
|
36
|
+
hostname.endsWith(".local") ||
|
|
37
|
+
hostname.endsWith(".localhost") ||
|
|
38
|
+
hostname.endsWith(".internal") ||
|
|
39
|
+
isPrivateIpv4(hostname) ||
|
|
40
|
+
hostname.includes(":")
|
|
41
|
+
) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return url.protocol === "https:";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One MCP server as the portal names it. The handle is what the portal puts in front of every tool
|
|
49
|
+
* that server offers (`notion_notion-search` belongs to `notion`), and it is the only identifier
|
|
50
|
+
* Intel can both store and recognise again in a live `tools/list`.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ A handle is never invented from a tool name. Which servers exist is the portal's answer
|
|
53
|
+
* (`portal_list_servers`), and the prefix is only used to attribute a tool to a server that answer
|
|
54
|
+
* already named — see `packages/intel/src/tools/tool-servers` for why splitting on the underscore
|
|
55
|
+
* alone would be ambiguous.
|
|
56
|
+
*/
|
|
57
|
+
export const ToolServerHandle = z
|
|
58
|
+
.string()
|
|
59
|
+
.trim()
|
|
60
|
+
.min(1)
|
|
61
|
+
.max(120)
|
|
62
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "A server handle is the portal's own identifier");
|
|
63
|
+
export type ToolServerHandle = z.infer<typeof ToolServerHandle>;
|
|
64
|
+
|
|
65
|
+
export const ToolSourceUrl = z.url().refine((value) => {
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(value);
|
|
68
|
+
return !url.username && !url.password && isPublicToolHost(url);
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}, "The portal must use an approved public HTTPS host without embedded credentials");
|
|
73
|
+
|
|
74
|
+
// The portal namespaces every upstream tool, so the name alone identifies the target server. The
|
|
75
|
+
// portal is still the one that resolves it and attaches the credentials — Intel never holds an
|
|
76
|
+
// upstream credential. Since D30 Intel does read the namespace for one purpose: attributing a tool
|
|
77
|
+
// to a server the portal's own `portal_list_servers` already named, so a delegation can be cut to
|
|
78
|
+
// whole servers. That is attribution, not routing.
|
|
79
|
+
export const ToolName = z.string().min(1).max(240);
|
|
80
|
+
|
|
81
|
+
export const ToolAnnotations = z.strictObject({
|
|
82
|
+
title: z.string().max(240).optional(),
|
|
83
|
+
readOnlyHint: z.boolean().optional(),
|
|
84
|
+
destructiveHint: z.boolean().optional(),
|
|
85
|
+
idempotentHint: z.boolean().optional(),
|
|
86
|
+
openWorldHint: z.boolean().optional(),
|
|
87
|
+
});
|
|
88
|
+
export type ToolAnnotations = z.infer<typeof ToolAnnotations>;
|
|
89
|
+
|
|
90
|
+
export const ToolCapability = z.strictObject({
|
|
91
|
+
name: ToolName,
|
|
92
|
+
title: z.string().max(240).nullable(),
|
|
93
|
+
description: z.string().max(10_000).nullable(),
|
|
94
|
+
inputSchema: z.record(z.string(), z.unknown()),
|
|
95
|
+
outputSchema: z.record(z.string(), z.unknown()).nullable(),
|
|
96
|
+
annotations: ToolAnnotations,
|
|
97
|
+
fingerprint: z.string().regex(/^[a-f0-9]{64}$/),
|
|
98
|
+
});
|
|
99
|
+
export type ToolCapability = z.infer<typeof ToolCapability>;
|
|
100
|
+
|
|
101
|
+
// The catalog reflects one live tools/list for the requesting user. It is never stored as a
|
|
102
|
+
// permission mirror, so there is no per-source state and no Intel-owned connection status.
|
|
103
|
+
export const ToolCatalog = z.strictObject({
|
|
104
|
+
portalConnected: z.boolean(),
|
|
105
|
+
items: z.array(ToolCapability),
|
|
106
|
+
/**
|
|
107
|
+
* Which delegated servers actually contributed a tool to this catalog (#289).
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ Present only where the attribution was actually made — a delegated caller whose catalog was
|
|
110
|
+
* read. It is absent for an ordinary user, and absent as well when the answer comes from one of
|
|
111
|
+
* the short paths that never reach the portal (nothing delegated, no portal sign-in, connection
|
|
112
|
+
* dropped). Absent therefore means "not stated", never "nothing arrived"; `[]` is the second one.
|
|
113
|
+
*
|
|
114
|
+
* That it is missing rather than empty on those paths is deliberate rather than half-finished:
|
|
115
|
+
* the attribution already happens for a delegation — `capabilities` has to make it to cut the
|
|
116
|
+
* list — so naming it costs nothing there, while computing the same thing for an ordinary user
|
|
117
|
+
* would mean a second portal request per call, for a question their screen does not ask.
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ It is the answer to "what arrived", never to "what was granted". A server missing here has
|
|
120
|
+
* been switched off, revoked, or is failing right now; the delegation in the definition is
|
|
121
|
+
* unchanged. Reading it the other way round would turn an outage into a permission change.
|
|
122
|
+
*/
|
|
123
|
+
reached: z.array(ToolServerHandle).optional(),
|
|
124
|
+
});
|
|
125
|
+
export type ToolCatalog = z.infer<typeof ToolCatalog>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* One MCP server the asking user reaches right now, as the portal itself names it (D30).
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ `toolCount` is a fact about this moment and this user, not a size. It exists so a picker can
|
|
131
|
+
* say "9 tools" instead of showing a handle alone, and it must never be read as what an agent will
|
|
132
|
+
* get: the delegated run asks the portal again, with the delegator's token.
|
|
133
|
+
*/
|
|
134
|
+
export const ToolServer = z.strictObject({
|
|
135
|
+
handle: ToolServerHandle,
|
|
136
|
+
name: z.string().min(1).max(240),
|
|
137
|
+
toolCount: z.number().int().min(0),
|
|
138
|
+
});
|
|
139
|
+
export type ToolServer = z.infer<typeof ToolServer>;
|
|
140
|
+
|
|
141
|
+
// The same live-query rule as the tool catalog, one level up. `portalConnected: false` is the state
|
|
142
|
+
// of somebody who has not signed into the portal yet, and it is not an error.
|
|
143
|
+
export const ToolServerCatalog = z.strictObject({
|
|
144
|
+
portalConnected: z.boolean(),
|
|
145
|
+
items: z.array(ToolServer),
|
|
146
|
+
});
|
|
147
|
+
export type ToolServerCatalog = z.infer<typeof ToolServerCatalog>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Which of the named servers a tool belongs to, or `null` for none of them.
|
|
151
|
+
*
|
|
152
|
+
* ⚠️ THE TRAP: a tool name does not say where its server name ends.
|
|
153
|
+
*
|
|
154
|
+
* The portal writes `<server>_<tool>`, and both halves may contain underscores — `intel_flow_get`
|
|
155
|
+
* reads equally well as server `intel` with tool `flow_get` and as a server called `intel_flow`
|
|
156
|
+
* with tool `get`. Splitting on the first underscore is therefore a guess that is wrong the day
|
|
157
|
+
* somebody adds a server whose name contains one, and on the API side being wrong means an agent
|
|
158
|
+
* delegated server A quietly reaching server B.
|
|
159
|
+
*
|
|
160
|
+
* So the prefix is never split. It is only ever MATCHED against handles the portal itself named,
|
|
161
|
+
* and the longest match wins: with `intel` and `intel_flow` both declared, `intel_flow_get` belongs
|
|
162
|
+
* to `intel_flow`, which is the only reading in which both declarations stay true.
|
|
163
|
+
*
|
|
164
|
+
* ⚠️ This lives in the contract because HOW A NAME IS READ is a property of the wire, and both
|
|
165
|
+
* surfaces read the same wire: `packages/intel` cuts a delegation with it, `packages/intel/ui` groups the
|
|
166
|
+
* tools screen with it (#212). A second implementation in the browser would be the third answer to
|
|
167
|
+
* one question — the underscore rule has already been answered differently in two places once
|
|
168
|
+
* (#106, #107), and the copies disagreed. What deliberately stays OUT of here is everything about
|
|
169
|
+
* reach: which handles are declared, which are enabled, which may be delegated and which one owns
|
|
170
|
+
* the portal's own management tools are decisions with consequences, and they belong to
|
|
171
|
+
* `packages/intel/src/tools/tool-servers`. This function only reads a name.
|
|
172
|
+
*/
|
|
173
|
+
export function serverOf(toolName: string, handles: Iterable<string>): string | null {
|
|
174
|
+
let best: string | null = null;
|
|
175
|
+
for (const handle of handles) {
|
|
176
|
+
if (!toolName.startsWith(`${handle}_`)) continue;
|
|
177
|
+
if (best === null || handle.length > best.length) best = handle;
|
|
178
|
+
}
|
|
179
|
+
return best;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const TestToolInput = z.strictObject({
|
|
183
|
+
name: ToolName.describe(
|
|
184
|
+
"The tool's full name as tool_list reported it, including the server handle it is prefixed with. The portal routes on that prefix, so a bare name reaches nothing.",
|
|
185
|
+
),
|
|
186
|
+
arguments: z
|
|
187
|
+
.record(z.string(), z.unknown())
|
|
188
|
+
.default({})
|
|
189
|
+
.describe(
|
|
190
|
+
"The arguments, shaped by that tool's own inputSchema from tool_list. Validated against it before anything is sent, so a wrong shape is refused here rather than by the far side.",
|
|
191
|
+
),
|
|
192
|
+
});
|
|
193
|
+
export type TestToolInput = z.infer<typeof TestToolInput>;
|
|
194
|
+
export const ExecuteToolInput = TestToolInput;
|
|
195
|
+
export type ExecuteToolInput = z.infer<typeof ExecuteToolInput>;
|
|
196
|
+
|
|
197
|
+
export const ToolTestResult = z.strictObject({
|
|
198
|
+
isError: z.boolean(),
|
|
199
|
+
content: z.array(z.unknown()),
|
|
200
|
+
structuredContent: z.unknown().optional(),
|
|
201
|
+
});
|
|
202
|
+
export type ToolTestResult = z.infer<typeof ToolTestResult>;
|