{"contextKind":"public_baseline","language":"en","id":"artifact-navigation","version":"1","contractDigest":"sha256:c7c7be4669d239d218f80f9056f6eacf946d8218fa30fe7e47bf00cd39b275c4","title":"Artifact Navigation","kind":"message-contract","files":[{"path":"docs/foundation/artifact-navigation-contract.md","digest":"sha256:f88fe3beb8cf136ca0cbde007bf510aa83f9eac9a05b5fabd546cad7732ca663","mediaType":"text/markdown","content":"---\nrole: authority\nlifecycle: current\nversion: \"1\"\nlanguage: en\n---\n# OrivelaIQ – Artifact Navigation Message Contract\n\n## Purpose\n\nThis document defines the client-side message contract through which an interactive HTML artifact can request navigation inside the current OrivelaIQ workspace.\n\nThis is not an HTTP endpoint. It is a versioned `window.postMessage` protocol available only to registered artifact frames whose effective source execution policy is `interactive`.\n\n## Protocol identifier\n\n```text\norivelaiq.artifact.v1\n```\n\nUnknown protocols and unknown message types are ignored silently.\n\n## Host handshake\n\nThe viewer sends this message after a registered interactive frame loads:\n\n```json\n{\n  \"protocol\": \"orivelaiq.artifact.v1\",\n  \"type\": \"host-ready\",\n  \"capabilities\": [\"navigate\"]\n}\n```\n\nThe artifact must not assume host integration before receiving the handshake. No handshake means standalone mode.\n\n## Navigation request\n\n```json\n{\n  \"protocol\": \"orivelaiq.artifact.v1\",\n  \"type\": \"navigate\",\n  \"requestId\": \"opaque-request-id\",\n  \"userActivated\": true,\n  \"target\": {\n    \"kind\": \"artifactPath\",\n    \"path\": \"docs/service-handover.md\"\n  }\n}\n```\n\n### Common fields\n\n| Field | Contract |\n|---|---|\n| `protocol` | Must equal `orivelaiq.artifact.v1`. |\n| `type` | Must equal `navigate`. |\n| `requestId` | Non-empty opaque string, maximum 64 characters. |\n| `userActivated` | Must be exactly `true`; requests during initial load are not honored. |\n| `target` | Must match one supported target shape. |\n\n### Supported targets\n\nArtifact path:\n\n```json\n{\n  \"kind\": \"artifactPath\",\n  \"path\": \"docs/service-handover.md\"\n}\n```\n\nArtifact ID:\n\n```json\n{\n  \"kind\": \"artifactId\",\n  \"id\": \"artifact-example\"\n}\n```\n\nPrefilled index search:\n\n```json\n{\n  \"kind\": \"search\",\n  \"query\": \"service handover\"\n}\n```\n\nSearch queries are limited to 200 characters. Path and artifact-ID bounds follow the centrally validated index contract.\n\nSearch requests open the existing index search with a prefilled query. They do not give the artifact access to search results or execute an independent search API.\n\n## Navigation result\n\n```json\n{\n  \"protocol\": \"orivelaiq.artifact.v1\",\n  \"type\": \"navigate-result\",\n  \"requestId\": \"opaque-request-id\",\n  \"status\": \"accepted\"\n}\n```\n\nAllowed status values:\n\n| Status | Meaning |\n|---|---|\n| `accepted` | The viewer accepted the request and performs the navigation or opens the prefilled search. |\n| `not-found` | The target is not resolvable in the current workspace, including cross-workspace targets. |\n| `denied` | The target exists but is not visible to the current user, or the registered source may not request navigation. |\n\nNegative replies carry no further detail.\n\n## Sender identification\n\nThe sandboxed artifact has an opaque origin, so `event.origin` is `\"null\"` and must not be used as the trust signal.\n\nThe viewer identifies the sender using exact `event.source` object identity against a lifecycle-managed frame registry containing at least:\n\n```text\ncontentWindow -> workspaceId, artifactId, sourceId, effectiveExecutionPolicy\n```\n\nOnly a currently mounted frame with effective policy `interactive` is eligible.\n\nRegister on mount and unregister on unmount. Messages from unregistered, foreign or stale windows are ignored silently.\n\n## Resolution and visibility\n\n- Resolve targets only against the current frame workspace.\n- A target from another workspace returns `not-found`.\n- Apply the same visibility semantics as protected index and artifact-content delivery.\n- A hidden target returns `denied` without metadata or reason detail.\n- The artifact receives no index, artifact, source, workspace or user data.\n\n## Validation and rate limiting\n\nThe viewer validates protocol, type, request ID, target kind, payload shape, length limits, user-activation marker, frame registration, effective policy, workspace and visibility.\n\nPer-frame baseline rate limit:\n\n```text\nmaximum 5 requests per 2000 milliseconds\n```\n\nThe current host behavior below distinguishes ignored messages from bounded negative replies. Unknown protocol/type, an absent or unusable reply request ID, and messages from unregistered frames are ignored silently. A recognized `navigate` message from a registered frame with a reply-safe request ID but invalid fields receives only `denied` while the per-frame reply budget permits; further malformed messages are ignored after that budget is exhausted. A valid-shaped navigation request exceeding the per-frame request limit receives only `denied`, without details.\n\nThe implementation must not add:\n\n- `allow-top-navigation`;\n- `allow-top-navigation-by-user-activation`;\n- `allow-same-origin`;\n- any other iframe capability for this protocol.\n\n## Reply transport\n\nOpaque-origin frames require:\n\n```js\nevent.source.postMessage(reply, \"*\")\n```\n\nThe viewer must send the reply only to the exact registered source window.\n\nAllowed reply data is limited to:\n\n- protocol;\n- type;\n- request ID;\n- status;\n- capability names in `host-ready`.\n\nNo reply may include content, tokens, credentials, user data, workspace metadata, resolved paths or authorization details.\n\n## Standalone degradation\n\nArtifacts must remain usable when:\n\n- no host handshake is received;\n- a request times out;\n- a result is `not-found`;\n- a result is `denied`.\n\nThe target reference should remain visible. Navigation is an enhancement, not the sole presentation of knowledge.\n\n## Out of scope\n\n- artifact-initiated search beyond prefilled query UI;\n- index or workspace read access;\n- artifact-to-artifact communication;\n- cross-workspace navigation;\n- persistent artifact state;\n- arbitrary viewer commands;\n- direct browser or top-level navigation.\n\n## Required tests\n\n- handshake after registered frame load;\n- accepted current-workspace path and ID navigation;\n- prefilled search navigation;\n- unknown target returns `not-found`;\n- hidden target returns `denied` without detail;\n- foreign window ignored;\n- stale frame ignored;\n- malformed and unknown payload ignored;\n- overlong request ID and query rejected;\n- load-time request not honored;\n- per-frame rate limit enforced;\n- cross-workspace target returns `not-found`;\n- reply contains no sensitive or contextual data;\n- artifact remains usable without host;\n- static frames receive no navigation capability;\n- iframe sandbox remains unchanged and contains no top-navigation allowance.\n\n## Current host behavior\n\nThe viewer implements the `orivelaiq.artifact.v1` contract as a mediated, workspace-local request channel for registered interactive HTML iframes only. Unknown protocol or message type payloads are ignored without a result reply. Recognized `navigate` payloads with a safe request ID but invalid fields receive only `denied`. The root `navigate` message shape is strict and contains exactly `protocol`, `type`, `requestId`, `userActivated` and `target`. Target shapes are exact: `artifactPath` carries `path`, `artifactId` carries `id`, and `search` carries `query`. `requestId` is 1–64 characters, artifact paths and IDs are 1–512 characters, and search queries are 1–200 characters; whitespace-only strings are invalid.\n\nArtifact paths are not URLs. They must not contain schemes, `//`, query strings, hash fragments, backslashes, absolute filesystem roots, traversal segments or encoded separators. Resolution is performed by a UI-free planner only against the currently loaded workspace index, using exact `path` or `logicalPath` matches. Ambiguous path matches are denied rather than guessed.\n\nThe viewer registry maps the exact `iframe.contentWindow` to `workspaceId`, `artifactId`, `sourceId`, effective `interactive` policy, a generation token and rate-limit state. Because sandboxed interactive frames have opaque origin and commonly send `event.origin === \"null\"`, origin is not a trust signal. The central message listener trusts only `event.source === registered contentWindow` plus the registry metadata, sent-handshake state, active artifact identity and current workspace state. Replies to the opaque frame use `targetOrigin: \"*\"` and contain only the minimal protocol fields. The per-frame limit is 5 navigation requests in 2 seconds; excess requests receive `denied` without details.\n\nThe host-ready handshake is sent only after successful registration and the frame load lifecycle is observed and is exactly `{ \"protocol\": \"orivelaiq.artifact.v1\", \"type\": \"host-ready\", \"capabilities\": [\"navigate\"] }`. Static HTML, Markdown, SVG, malformed policy values and frames without an exact protected `contentRef` for the active workspace/artifact receive no handshake. No index data, workspace metadata, user data, credentials or resolved artifact details are ever returned to an artifact.\n"}]}