openapi: 3.0.3 info: title: Knowtation Hub API description: REST API for the Knowtation Hub (vault read/write, proposals, capture). Same contract as CLI/MCP where applicable. version: 1.0.0 links: - description: API contract (human-readable) url: ./HUB-API.md servers: - url: /api/v1 description: Relative to Hub base URL (e.g. https://hub.example.com) tags: - name: Health - name: Auth - name: Notes - name: Search - name: Proposals - name: Capture - name: Flows security: - BearerAuth: [] paths: /health: get: tags: [Health] summary: Health check security: [] responses: '200': description: Hub is up content: application/json: schema: { type: object, properties: { ok: { type: boolean } }, required: [ok] } /api/v1/auth/session: get: tags: [Auth] summary: C7 Session introspection — current identity and scopes description: | Returns the verified identity and derived API scopes for the caller. Accepts only a `Bearer` JWT in the `Authorization` header — no cookie required, making it safe to call cross-origin from Scooling or any other consumer. The response is derived entirely from the signed token — no database call is made. Scopes are role-derived today (C4 will replace this with per-user explicit grants without changing the response shape). security: - bearerAuth: [] responses: '200': description: Verified session content: application/json: schema: type: object required: [sub, provider, id, name, role, iat, exp, scopes] properties: sub: type: string description: Canonical user ID (`provider:id`) example: google:104164334692309763642 provider: type: string enum: [google, github] id: type: string description: Provider-specific user ID name: type: string description: Display name (empty for refresh-path tokens) role: type: string enum: [admin, member] iat: type: integer description: Token issued-at (Unix seconds) exp: type: integer description: Token expires-at (Unix seconds) scopes: type: array items: { type: string } description: Derived API scopes. `admin` role → `[vault:read, vault:write, admin]`; `member` → `[vault:read, vault:write]` example: [vault:read, vault:write] '401': description: Missing, expired, or tampered token content: application/json: schema: type: object properties: error: { type: string } code: { type: string, enum: [UNAUTHORIZED] } /auth/providers: get: tags: [Auth] summary: OAuth providers configured security: [] responses: '200': content: application/json: schema: type: object properties: google: { type: boolean } github: { type: boolean } /notes/facets: get: tags: [Notes] summary: Projects, tags, folders for filter dropdowns responses: '200': content: application/json: schema: type: object properties: projects: { type: array, items: { type: string } } tags: { type: array, items: { type: string } } folders: { type: array, items: { type: string } } /notes: get: tags: [Notes] summary: List notes parameters: - name: folder in: query schema: { type: string } - name: project in: query schema: { type: string } - name: tag in: query schema: { type: string } - name: since in: query schema: { type: string } - name: until in: query schema: { type: string } - name: limit in: query schema: { type: integer, minimum: 0, maximum: 100 } - name: offset in: query schema: { type: integer, minimum: 0 } - name: order in: query schema: { type: string, enum: [date, date-asc] } - name: fields in: query schema: { type: string, enum: [path, path+metadata, full] } - name: count_only in: query schema: { type: boolean } responses: '200': content: application/json: schema: oneOf: - type: object properties: notes: { type: array, items: { $ref: '#/components/schemas/NoteListItem' } } total: { type: integer } - type: object properties: total: { type: integer } post: tags: [Notes] summary: Write or update a note requestBody: required: true content: application/json: schema: type: object required: [path] properties: path: { type: string } body: { type: string } frontmatter: { type: object } append: { type: boolean } responses: '200': content: application/json: schema: { type: object, properties: { path: { type: string }, written: { type: boolean } } } '400': '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /notes/{path}: get: tags: [Notes] summary: Get one note by path parameters: - name: path in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/NoteFull' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /note-outline: get: tags: [Notes] summary: Body-free NoteOutline headings for one note description: > Returns knowtation.note_outline/v1 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, vectors, OCR, PageIndex output, persistence records, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/NoteOutline' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /document-tree: get: tags: [Notes] summary: Body-free DocumentTree heading hierarchy for one note description: > Returns knowtation.document_tree/v0 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, vectors, OCR, PageIndex output, persistence records, sidecars, LLM calls, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/DocumentTree' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/timeline: get: tags: [Calendar] summary: Merged note-date and external-event timeline (self-hosted) description: > Returns knowtation.calendar_timeline/v0 items for an authorized vault. Merges note-date buckets and stored calendar events. No OAuth tokens or connector secrets. parameters: - name: from in: query required: true schema: { type: string } description: Range start (YYYY-MM-DD or ISO8601). - name: to in: query required: true schema: { type: string } description: Range end (YYYY-MM-DD or ISO8601). - name: layers in: query required: false schema: { type: string } description: Comma-separated layers (`notes`, `events`). Default both. - name: source_calendar_ids in: query required: false schema: { type: string } description: Comma-separated source calendar ids to include for the events layer. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarTimeline' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/agent-context: get: tags: [Calendar] summary: Tier-enforced calendar context for agents (self-hosted) description: > Returns knowtation.calendar_agent_context/v0 — redacted calendar events an agent may see. Enforced server-side: calendars with enabled_for_agents=false contribute nothing; per-calendar agent_context_tier_max and the org policy cap clamp the tier; the v0 retrieval ceiling is tier 2. Agent visibility is independent of enabled_for_display. Tier 1 omits the event summary; tier 0 returns no events. Calendar text is untrusted prompt content. parameters: - name: from in: query required: true schema: { type: string } description: Range start (YYYY-MM-DD or ISO8601). - name: to in: query required: true schema: { type: string } description: Range end (YYYY-MM-DD or ISO8601). - name: agent_context_tier in: query required: true schema: { type: integer, minimum: 0, maximum: 2 } description: Requested agent tier (0 none, 1 busy blocks, 2 titles + label). Clamped by caps. - name: source_calendar_ids in: query required: false schema: { type: string } description: Comma-separated source calendar ids to restrict the agent scope. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarAgentContext' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/source-calendars: get: tags: [Calendar] summary: List source calendars and display/agent toggles (self-hosted) responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarList' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/source-calendars/{id}: patch: tags: [Calendar] summary: Update source calendar display/agent toggles (self-hosted) description: > Partial update for enabled_for_display, enabled_for_agents, agent_context_tier_max (0–4), and optional user_group. Org policy may cap agent_context_tier_max via KNOWTATION_CALENDAR_AGENT_TIER_MAX_CAP or data/hub_calendar_policy.json. parameters: - name: id in: path required: true schema: { type: string } description: Source calendar id. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarPatchRequest' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarPatchResult' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/events/import: post: tags: [Calendar] summary: Import ICS text into the local event store (read-only, self-hosted) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CalendarIcsImportRequest' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarIcsImportResult' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows: get: tags: [Flows] summary: List scope-visible flows (content-minimized) description: > Returns knowtation.flow_list/v0 summaries for flows visible in the caller's authorized workspace scope. Scope query param narrows only — never widens. Step bodies are never included in list responses. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } description: Narrow within authorized scopes only. - name: tag in: query schema: { type: string } description: Filter by single tag membership. - name: limit in: query schema: { type: integer, minimum: 1, maximum: 200 } description: Max summaries (default 200). responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowListResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } post: tags: [Flows] summary: Propose a new Flow (review-before-write) description: > Validates a knowtation.flow/v0 + flow_step/v0 bundle, resolves write authority server-side (scope × role, deny-by-default), and creates a standard proposal targeting the Flow's mirror note (SD-4). Returns a knowtation.flow_proposal/v0 envelope (pointers/labels only). Gated by FLOW_AUTHORING_WRITES — when off returns 403 FLOW_AUTHORING_DISABLED. No Flow index write happens here; the index changes only at approve→apply. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowProposeRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/import: post: tags: [Flows] summary: Import a portable Flow bundle as a scope-checked proposal description: > Routes a portable { flow, steps } bundle through the same propose path. The bundle scope is validated against the actor's write tier; unwritable ⇒ 403 FLOW_IMPORT_SCOPE_DENIED, malformed ⇒ 400 FLOW_IMPORT_BUNDLE_MALFORMED. Lineage pointers (external_ref / source_vault_hint) are preserved. Never auto-applied — creates a proposed proposal. Gated by FLOW_AUTHORING_WRITES. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowImportRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/capture/observe: post: tags: [Flows] summary: Observe content-minimized session signals (capture detection) description: > Runs bounded structural detectors when FLOW_CAPTURE_DETECTION_ENABLED is on. Creates/updates flow_candidate/v0 records; returns content-minimized summaries. When detection is off, returns detection_authorized=false with no store mutation. requestBody: required: true content: application/json: schema: type: object required: [session_id, step_sequence_refs, observed_counts] responses: '200': description: Observe envelope '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates: get: tags: [Flows] summary: List flow capture candidates (read store) description: Returns pending_review candidate summaries; read path does not require detection sub-gate. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } - name: include_low_confidence in: query schema: { type: boolean } - name: limit in: query schema: { type: integer, minimum: 1, maximum: 50 } responses: '200': description: Candidate list envelope '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates/{candidate_id}/propose: post: tags: [Flows] summary: Propose candidate promotion (review-before-write) description: > Creates a flow_candidate_promote or flow_candidate_merge proposal. Gated by FLOW_CAPTURE_WRITES_ENABLED (default off). parameters: - name: candidate_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [confirmed_scope, intent] responses: '201': description: Capture proposal envelope '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates/{candidate_id}/dismiss: post: tags: [Flows] summary: Propose candidate dismissal description: > Creates a flow_candidate_dismiss proposal; on approve candidate status becomes rejected. Gated by FLOW_CAPTURE_WRITES_ENABLED (default off). parameters: - name: candidate_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [intent] responses: '201': description: Capture proposal envelope '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/proposals: post: tags: [Flows] summary: Propose an edit to an existing Flow (review-before-write) description: > Like POST /flows but for an edit. Requires base_version + base_state_id (the flowst1_ optimistic-concurrency token); a mismatch at propose or approve time ⇒ 409 FLOW_LINEAGE_CONFLICT. flow.version must be strictly greater than base_version. Editing a flow the actor cannot read ⇒ 404 unknown_flow (no existence leak). Gated by FLOW_AUTHORING_WRITES. parameters: - name: id in: path required: true schema: { type: string } description: Flow id (flow_); must match the bundle's flow_id. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowProposeEditRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/projection: get: tags: [Flows] summary: Derive a read-only harness projection of a canonical flow description: > Renders the canonical flow (latest visible, or pinned ?version) into the requested harness as knowtation.flow_project/v0. Derived and read-only — generated_from_canonical is always true and editable is always false. No secrets appear in rendered text. parameters: - name: id in: path required: true schema: { type: string } - name: harness in: query required: true schema: type: string enum: [cursor_rule, cursor_skill, mcp_prompt, cli_runbook, agent_bundle] - name: version in: query schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowProjectResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/external-grants: post: tags: [Flows] summary: Mint a short-lived external-agent grant (gated; default off) description: > Mints knowtation.flow_external_grant/v0 for a pinned flow version and requested tools. Returns a one-time bearer at mint only. Requires FLOW_EXTERNAL_AGENT_ENABLED. parameters: - name: id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantMintRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantMintResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/external-grants: get: tags: [Flows] summary: List external-agent grant metadata (no bearer) parameters: - name: flow_id in: query schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantListResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/external-grants/{grant_id}: delete: tags: [Flows] summary: Revoke an external-agent grant parameters: - name: grant_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrant' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}: get: tags: [Flows] summary: Get one flow definition + ordered steps description: > Returns knowtation.flow_get/v0 with full flow definition and steps in ascending ordinal order. Missing and scope-invisible flows both return 404 unknown_flow. Step text is untrusted input — returned verbatim as data. parameters: - name: id in: path required: true schema: { type: string } description: Flow id (flow_). - name: version in: query schema: { type: string } description: Pin semver version; default latest visible. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowGetResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flow-runs/{run_id}: get: tags: [Flows] summary: Get one flow run by run id description: > Returns knowtation.flow_run/v0 for a scope-visible run. Missing and scope-invisible runs both return 404 unknown_run. parameters: - name: run_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/runs: get: tags: [Flows] summary: List runs for a flow parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunListResponse' } post: tags: [Flows] summary: Start a flow run description: > Gated by FLOW_RUN_WRITES_ENABLED (default off). Pins flow_version for the run life. parameters: - name: id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [flow_version] properties: flow_version: { type: string } task_ref: { type: string } external_ref: { type: string } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowRunStartResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/runs/{run_id}: get: tags: [Flows] summary: Get one flow run parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } post: tags: [Flows] summary: Advance, record evidence, execute automatable, or submit review description: > Use dedicated sub-paths (/advance, /evidence, /execute-automatable, /submit-review). Run writes gated by FLOW_RUN_WRITES_ENABLED; automatable by FLOW_AUTOMATABLE_EXECUTION_ENABLED. /flows/{id}/runs/{run_id}/advance: post: tags: [Flows] summary: Advance a step manually parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, to_status] properties: step_id: { type: string } to_status: { type: string } skip_reason: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } /flows/{id}/runs/{run_id}/evidence: post: tags: [Flows] summary: Record evidence pointer on a step parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, evidence_ref, pointer_kind] properties: step_id: { type: string } evidence_ref: { type: string } pointer_kind: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } /flows/{id}/runs/{run_id}/execute-automatable: post: tags: [Flows] summary: Execute an automatable step (server orchestration stub) description: Requires valid knowtation.flow_execution_consent/v0. Gated by FLOW_AUTOMATABLE_EXECUTION_ENABLED. parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, consent_id] properties: step_id: { type: string } consent_id: { type: string } model_lane: { type: string } dry_run: { type: boolean } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExecuteAutomatableResponse' } /flows/{id}/runs/{run_id}/submit-review: post: tags: [Flows] summary: Submit run outcome to review tray parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [intent] properties: intent: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunSubmitReviewResponse' } /flows/{id}/runs/{run_id}/consent: post: tags: [Flows] summary: Mint execution consent for automatable steps parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [allowed_lanes, cost_cap_units] properties: allowed_lanes: { type: array, items: { type: string } } cost_cap_units: { type: integer } ttl_seconds: { type: integer } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowExecutionConsentMintResponse' } /metadata-facets: get: tags: [Notes] summary: Body-free MetadataFacets hints for one note description: > Returns knowtation.metadata_facets/v0 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, labels, vectors, OCR, PageIndex output, media metadata, memory events, persistence records, sidecars, LLM calls, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/MetadataFacets' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /section-source: get: tags: [Notes] summary: Body-free SectionSource metadata for one note description: > Returns knowtation.section_source/v0 metadata for one authorized vault-relative note. The response excludes note body text, section body text, snippets, full frontmatter, line ranges, byte offsets, section body lengths, absolute paths, raw canister payloads, provider payloads, and MCP resource URIs. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SectionSource' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /index: post: tags: [Notes] summary: Re-run indexer (vault to vector store) responses: '200': content: application/json: schema: type: object properties: ok: { type: boolean } notesProcessed: { type: integer } chunksIndexed: { type: integer } vectors_deleted: { type: integer, description: Rows removed for this vault before upsert (hosted sqlite-vec) } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /export: post: tags: [Notes] summary: Export one note to content (returns body + filename for client download) requestBody: required: true content: application/json: schema: type: object required: [path] properties: path: { type: string } format: { type: string, enum: [md, html] } responses: '200': content: application/json: schema: type: object properties: content: { type: string } filename: { type: string } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /import: post: tags: [Notes] summary: Import from uploaded file or ZIP (multipart: source_type; file except for google-sheets; optional project, tags, spreadsheet_id for google-sheets) requestBody: required: true content: multipart/form-data: schema: type: object required: [source_type] properties: source_type: type: string description: Importer id. For google-sheets, omit file and set spreadsheet_id; optional sheets_range (A1 notation). See lib/import-source-types.mjs. file: { type: string, format: binary, description: Required for all importers except google-sheets. } spreadsheet_id: type: string description: Required when source_type is google-sheets (id from the Google Sheets URL). sheets_range: type: string description: Optional for google-sheets; A1 range. Omit to read the first sheet from A1. project: { type: string } output_dir: { type: string } tags: { type: string } responses: '200': content: application/json: schema: type: object properties: imported: { type: array, items: { type: object } } count: { type: integer } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /import-url: post: tags: [Notes] summary: Import from a public https URL (JSON body; editor/admin) requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: { type: string, description: 'Full https URL' } mode: { type: string, enum: [auto, bookmark, extract], description: 'Capture mode (default auto)' } project: { type: string } output_dir: { type: string } tags: { oneOf: [{ type: string }, { type: array, items: { type: string } }] } responses: '200': content: application/json: schema: type: object properties: imported: { type: array, items: { type: object } } count: { type: integer } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /settings: get: tags: [Notes] summary: Config status for Settings UI (no secrets) responses: '200': content: application/json: schema: type: object properties: vault_path_display: { type: string } vault_git: type: object properties: enabled: { type: boolean } has_remote: { type: boolean } auto_commit: { type: boolean } auto_push: { type: boolean } /vault/sync: post: tags: [Notes] summary: Manual vault backup (git add, commit, push) description: Self-hosted runs local git. Hosted (bridge) pushes notes as Markdown plus `.knowtation/backup/v1/snapshot.json` with full proposals. responses: '200': content: application/json: schema: type: object properties: ok: { type: boolean } message: { type: string } notesCount: { type: integer, description: Hosted bridge only } proposalsCount: { type: integer, description: Hosted bridge only } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /search: post: tags: [Search] summary: Vault search (semantic or keyword) requestBody: required: true content: application/json: schema: type: object required: [query] properties: query: { type: string } mode: { type: string, enum: [semantic, keyword], description: Omitted or semantic = vector search; keyword = substring/token match on note text } match: { type: string, enum: [phrase, all_terms], description: Keyword only; phrase = full query substring; all_terms = every token must appear } folder: { type: string } project: { type: string } tag: { type: string } since: { type: string } until: { type: string } chain: { type: string } entity: { type: string } episode: { type: string } limit: { type: integer } order: { type: string } fields: { type: string } content_scope: { type: string, enum: [notes, approval_logs], description: Narrow to normal notes vs approvals/ logs } snippetChars: { type: integer } count_only: { type: boolean } countOnly: { type: boolean } responses: '200': content: application/json: schema: type: object properties: results: { type: array, items: { $ref: '#/components/schemas/SearchResult' } } query: { type: string } mode: { type: string, enum: [semantic, keyword] } count: { type: integer, description: Present when count_only keyword search } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } /proposals: get: tags: [Proposals] summary: List proposals parameters: - name: status in: query schema: { type: string } - name: limit in: query schema: { type: integer } - name: offset in: query schema: { type: integer } - name: label in: query description: Match if proposal labels include this string (case-insensitive) schema: { type: string } - name: source in: query schema: { type: string } - name: path_prefix in: query schema: { type: string } - name: evaluation_status in: query description: Filter by evaluation_status (none, pending, passed, failed, needs_changes) schema: { type: string } - name: review_queue in: query description: Exact match on proposal review_queue schema: { type: string } - name: review_severity in: query description: standard or elevated schema: { type: string } responses: '200': content: application/json: schema: type: object properties: proposals: { type: array, items: { $ref: '#/components/schemas/Proposal' } } total: { type: integer } post: tags: [Proposals] summary: Create proposal requestBody: content: application/json: schema: type: object properties: path: { type: string } body: { type: string } frontmatter: { type: object } intent: { type: string } base_state_id: { type: string } external_ref: { type: string } labels: { type: array, items: { type: string } } source: { type: string } responses: '201': content: application/json: schema: type: object properties: proposal_id: { type: string } path: { type: string } status: { type: string, enum: [proposed] } '400': /proposals/{id}: get: tags: [Proposals] summary: Get one proposal parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '404': /proposals/{id}/review-hints: post: tags: [Proposals] summary: Store async LLM review hints (canister; not a merge gate) parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object properties: review_hints: { type: string } review_hints_model: { type: string } responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } ok: { type: boolean } /proposals/{id}/evaluation: post: tags: [Proposals] summary: Submit human evaluation (admin or evaluator) parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object required: [outcome] properties: outcome: type: string enum: [pass, fail, needs_changes] checklist: type: array items: type: object properties: id: { type: string } passed: { type: boolean } grade: { type: string } comment: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '400': '404': /proposals/{id}/approve: post: tags: [Proposals] summary: Apply proposal to vault parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object properties: base_state_id: { type: string } waiver_reason: type: string description: Admin override when evaluation is not passed (min length 3 after trim) external_ref: type: string description: Optional cross-system lineage id (e.g. Muse); server may resolve via MUSE_URL when omitted responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } status: { type: string, enum: [approved] } external_ref: { type: string } '403': description: EVALUATION_REQUIRED — pass evaluation or provide waiver_reason '409': description: base_state_id mismatch (CONFLICT) /proposals/{id}/enrich: post: tags: [Proposals] summary: Optional LLM summary and suggested labels (KNOWTATION_HUB_PROPOSAL_ENRICH=1) parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '400': description: >- ICP canister — suggested_labels_json or assistant_suggested_frontmatter_json is valid JSON but exceeds max length (4000 / 14000 characters) after validation. '404': /proposals/{id}/discard: post: tags: [Proposals] summary: Discard proposal parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } status: { type: string, enum: [discarded] } /capture: post: tags: [Capture] summary: Ingest message into vault inbox (webhook-style) description: Same contract as capture-webhook. If CAPTURE_WEBHOOK_SECRET is set, require X-Webhook-Secret header. security: [] requestBody: content: application/json: schema: type: object required: [body] properties: body: { type: string } source_id: { type: string } source: { type: string } project: { type: string } tags: { type: array, items: { type: string } } responses: '200': content: application/json: schema: { type: object, properties: { ok: { type: boolean }, path: { type: string } } } '400': components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: Error: type: object properties: error: { type: string } code: { type: string } NoteListItem: type: object properties: path: { type: string } title: { type: string, nullable: true } project: { type: string, nullable: true } tags: { type: array, items: { type: string } } date: { type: string, nullable: true } NoteFull: type: object properties: path: { type: string } frontmatter: { type: object } body: { type: string } SearchResult: type: object properties: path: { type: string } snippet: { type: string } score: { type: number } project: { type: string } tags: { type: array, items: { type: string } } NoteOutline: type: object required: [schema, path, headings, truncated] properties: schema: type: string enum: [knowtation.note_outline/v1] path: { type: string } title: { type: string, nullable: true } headings: type: array maxItems: 500 items: { $ref: '#/components/schemas/NoteOutlineHeading' } truncated: { type: boolean } NoteOutlineHeading: type: object required: [level, text, id] properties: level: { type: integer, minimum: 1, maximum: 6 } text: { type: string } id: { type: string } DocumentTree: type: object required: [schema, path, root, truncated] properties: schema: type: string enum: [knowtation.document_tree/v0] path: { type: string } title: { type: string, nullable: true } root: type: object required: [children] properties: children: type: array maxItems: 500 items: { $ref: '#/components/schemas/DocumentTreeNode' } truncated: { type: boolean } DocumentTreeNode: type: object required: [id, level, text, children] properties: id: { type: string } level: { type: integer, minimum: 1, maximum: 6 } text: { type: string } children: type: array items: { $ref: '#/components/schemas/DocumentTreeNode' } MetadataFacets: type: object required: [schema, path, facets, inferred, truncated] properties: schema: type: string enum: [knowtation.metadata_facets/v0] path: { type: string } facets: type: object required: [project, tags, date, updated, causal_chain_id, entity, episode_id] properties: project: { type: string, nullable: true } tags: type: array maxItems: 100 items: { type: string } date: { type: string, nullable: true } updated: { type: string, nullable: true } causal_chain_id: { type: string, nullable: true } entity: type: array maxItems: 100 items: { type: string } episode_id: { type: string, nullable: true } inferred: type: object required: [folder, source_type] properties: folder: { type: string, nullable: true } source_type: { nullable: true, enum: [null] } truncated: { type: boolean } SectionSource: type: object required: [schema, path, sections, truncated] properties: schema: type: string enum: [knowtation.section_source/v0] path: { type: string } title: { type: string, nullable: true } sections: type: array items: { $ref: '#/components/schemas/SectionSourceSection' } truncated: { type: boolean } SectionSourceSection: type: object required: - section_id - heading_id - level - heading_path - heading_text - child_section_ids - body_available - body_returned - snippet_returned properties: section_id: { type: string } heading_id: { type: string } level: { type: integer, minimum: 1, maximum: 6 } heading_path: { type: array, items: { type: string } } heading_text: { type: string } child_section_ids: { type: array, items: { type: string } } body_available: { type: boolean } body_returned: { type: boolean, enum: [false] } snippet_returned: { type: boolean, enum: [false] } Proposal: type: object properties: proposal_id: { type: string } path: { type: string } status: { type: string } intent: { type: string } base_state_id: { type: string } external_ref: { type: string } vault_id: { type: string } proposed_by: { type: string } labels: { type: array, items: { type: string } } source: { type: string } suggested_labels: { type: array, items: { type: string } } assistant_notes: { type: string } assistant_model: { type: string } assistant_at: { type: string } created_at: { type: string } updated_at: { type: string } evaluation_status: type: string enum: [none, pending, passed, failed, needs_changes] evaluation_grade: { type: string } evaluation_comment: { type: string } evaluated_by: { type: string } evaluated_at: { type: string } evaluation_waiver: type: object nullable: true properties: by: { type: string } at: { type: string } reason: { type: string } review_queue: { type: string } review_severity: { type: string, enum: [standard, elevated] } auto_flag_reasons: type: array items: { type: string } auto_flag_reasons_json: { type: string, description: JSON array string on canister } review_hints: { type: string } review_hints_at: { type: string } review_hints_model: { type: string } assistant_suggested_frontmatter: type: object description: Normalized SPEC-aligned suggested note metadata from Enrich (object on GET); omitted or empty on older proposals additionalProperties: true ProposalDetail: allOf: - { $ref: '#/components/schemas/Proposal' } - type: object properties: body: { type: string } frontmatter: { type: object } evaluation_checklist: type: array items: type: object properties: id: { type: string } label: { type: string } passed: { type: boolean } CalendarTimeline: type: object required: [schema, vault_id, from, to, layers, items] properties: schema: type: string enum: [knowtation.calendar_timeline/v0] vault_id: { type: string } from: { type: string } to: { type: string } layers: type: array items: type: string enum: [notes, events] items: type: array items: oneOf: - { $ref: '#/components/schemas/CalendarTimelineNoteItem' } - { $ref: '#/components/schemas/CalendarTimelineEventItem' } CalendarTimelineNoteItem: type: object required: [kind, date, path, title, project, tags, sort_at] properties: kind: type: string enum: [note] date: { type: string } path: { type: string } title: { type: string, nullable: true } project: { type: string, nullable: true } tags: type: array items: { type: string } sort_at: { type: string } CalendarTimelineEventItem: type: object required: [kind, event_id, source_calendar_id, start, end, timezone, summary, busy, status, calendar_label, sort_at] properties: kind: type: string enum: [event] event_id: { type: string } source_calendar_id: { type: string } start: { type: string } end: { type: string } timezone: { type: string } summary: { type: string, nullable: true } busy: { type: boolean } status: type: string enum: [confirmed, cancelled, tentative] calendar_label: { type: string, nullable: true } sort_at: { type: string } CalendarAgentContext: type: object required: [schema, vault_id, from, to, requested_tier, effective_tier, policy_agent_context_tier_max_cap, source_calendars, items] properties: schema: type: string enum: [knowtation.calendar_agent_context/v0] vault_id: { type: string } from: { type: string } to: { type: string } requested_tier: type: integer minimum: 0 maximum: 2 effective_tier: type: integer minimum: 0 maximum: 2 description: Requested tier after the org policy cap is applied. policy_agent_context_tier_max_cap: type: integer minimum: 0 maximum: 4 source_calendars: type: array items: { $ref: '#/components/schemas/AgentContextCalendarSummary' } items: type: array items: { $ref: '#/components/schemas/CalendarAgentContextEventItem' } AgentContextCalendarSummary: type: object required: [source_calendar_id, display_name, user_group, enabled_for_agents, agent_context_tier_max, effective_tier, event_count] properties: source_calendar_id: { type: string } display_name: { type: string } user_group: type: string nullable: true enum: [personal, work, school, other, null] enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 effective_tier: type: integer minimum: 0 maximum: 2 event_count: { type: integer } CalendarAgentContextEventItem: type: object required: [event_id, source_calendar_id, external_uid, start, end, timezone, busy, status, agent_tier] description: > Redacted event. `summary` and `calendar_label` are present only at tier 2; tier 1 omits the event title entirely. properties: event_id: { type: string } source_calendar_id: { type: string } external_uid: { type: string } start: { type: string } end: { type: string } timezone: { type: string } busy: { type: boolean } status: type: string enum: [confirmed, cancelled, tentative] agent_tier: type: integer minimum: 1 maximum: 2 summary: { type: string, nullable: true } calendar_label: { type: string, nullable: true } SourceCalendarList: type: object required: [schema, vault_id, source_calendars] properties: schema: type: string enum: [knowtation.source_calendars/v0] vault_id: { type: string } source_calendars: type: array items: { $ref: '#/components/schemas/SourceCalendar' } SourceCalendar: type: object required: [source_calendar_id, connector_id, display_name, enabled_for_sync, enabled_for_display, enabled_for_agents, agent_context_tier_max] properties: source_calendar_id: { type: string } connector_id: { type: string } display_name: { type: string } color: { type: string, nullable: true } user_group: type: string nullable: true enum: [personal, work, school, other, null] enabled_for_sync: { type: boolean } enabled_for_display: { type: boolean } enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 provider: { type: string } CalendarIcsImportRequest: type: object required: [ics_text] properties: ics_text: { type: string } display_name: { type: string } source_calendar_id: { type: string } connector_id: { type: string } default_timezone: { type: string } CalendarIcsImportResult: type: object required: [schema, vault_id, source_calendar_id, connector_id, imported, updated] properties: schema: type: string enum: [knowtation.calendar_import/v0] vault_id: { type: string } source_calendar_id: { type: string } connector_id: { type: string } imported: { type: integer } updated: { type: integer } SourceCalendarPatchRequest: type: object minProperties: 1 properties: enabled_for_display: { type: boolean } enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 user_group: type: string nullable: true enum: [personal, work, school, other, null] SourceCalendarPatchResult: type: object required: [schema, vault_id, policy_agent_context_tier_max_cap, source_calendar] properties: schema: type: string enum: [knowtation.source_calendar_patch/v0] vault_id: { type: string } policy_agent_context_tier_max_cap: type: integer minimum: 0 maximum: 4 source_calendar: { $ref: '#/components/schemas/SourceCalendar' } FlowListResponse: type: object required: [schema, vault_id, effective_scope, flows, truncated] properties: schema: type: string enum: [knowtation.flow_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] flows: type: array maxItems: 200 items: { $ref: '#/components/schemas/FlowSummary' } truncated: { type: boolean } FlowSummary: type: object required: [schema, flow_id, title, version, scope, summary, tags, step_count, updated, truncated] properties: schema: type: string enum: [knowtation.flow/v0] flow_id: { type: string } title: { type: string } version: { type: string } scope: type: string enum: [personal, project, org] summary: { type: string } tags: type: array maxItems: 32 items: { type: string } step_count: { type: integer, minimum: 0 } updated: { type: string } truncated: { type: boolean } FlowGetResponse: type: object required: [schema, vault_id, flow, steps] properties: schema: type: string enum: [knowtation.flow_get/v0] vault_id: { type: string } flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } FlowProposeRequest: type: object required: [flow, steps, intent] description: Propose a new Flow. intent is untrusted and recorded verbatim. properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } FlowProposeEditRequest: type: object required: [flow, steps, intent, base_version, base_state_id] description: > Propose an edit. base_version + base_state_id (flowst1_ token) gate optimistic concurrency; flow.version must exceed base_version. properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } base_version: { type: string } base_state_id: { type: string } FlowImportRequest: type: object required: [bundle, intent] description: Import a portable bundle through the same scope-checked propose path. properties: bundle: type: object required: [flow, steps] properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } external_ref: { type: string } source_vault_hint: { type: string } FlowProposalResponse: type: object required: [schema, proposal_id, flow_id, scope, auto_approvable, status, review_queue] description: > knowtation.flow_proposal/v0 envelope — pointers/labels only, never a rendered Flow body or secret. base_version/base_state_id are null for new. properties: schema: type: string enum: [knowtation.flow_proposal/v0] proposal_id: { type: string } flow_id: { type: string } base_version: { type: string, nullable: true } base_state_id: { type: string, nullable: true } scope: type: string enum: [personal, project, org] auto_approvable: { type: boolean } status: type: string enum: [proposed] review_queue: { type: string } FlowExternalGrantMintRequest: type: object required: [flow_version, requested_tools] properties: flow_version: { type: string } requested_tools: type: array minItems: 1 items: { type: string } ttl_seconds: { type: integer, minimum: 1 } actor_label: { type: string } FlowExternalGrant: type: object required: - schema - grant_id - vault_id - scope - flow_id - flow_version - allowed_tools - allowed_harnesses - expires_at - issued_at - revoked_at - actor_hash - invocation_count properties: schema: type: string enum: [knowtation.flow_external_grant/v0] grant_id: { type: string } vault_id: { type: string } scope: type: string enum: [personal, project, org] flow_id: { type: string } flow_version: { type: string } allowed_tools: type: array items: { type: string } allowed_harnesses: type: array items: { type: string } expires_at: { type: string, format: date-time } issued_at: { type: string, format: date-time } revoked_at: { type: string, format: date-time, nullable: true } actor_hash: { type: string } max_invocations: { type: integer } invocation_count: { type: integer } FlowExternalGrantMintResponse: type: object required: [schema, grant, bearer, expires_at] properties: schema: type: string enum: [knowtation.flow_external_grant_mint/v0] grant: { $ref: '#/components/schemas/FlowExternalGrant' } bearer: { type: string } expires_at: { type: string, format: date-time } FlowExternalGrantListResponse: type: object required: [schema, vault_id, grants] properties: schema: type: string enum: [knowtation.flow_external_grant_list/v0] vault_id: { type: string } grants: type: array items: { $ref: '#/components/schemas/FlowExternalGrant' } FlowProjectResponse: type: object required: [schema, vault_id, projection, staleness, generator] properties: schema: type: string enum: [knowtation.flow_project/v0] vault_id: { type: string } projection: { $ref: '#/components/schemas/FlowProjection' } staleness: { $ref: '#/components/schemas/FlowProjectionStaleness' } generator: { $ref: '#/components/schemas/FlowProjectionGenerator' } FlowProjection: type: object required: [schema, flow_id, flow_version, harness, rendered, generated_from_canonical, editable] properties: schema: type: string enum: [knowtation.flow_projection/v0] flow_id: { type: string } flow_version: { type: string } harness: type: string enum: [cursor_rule, cursor_skill, mcp_prompt, cli_runbook, agent_bundle] rendered: type: string maxLength: 65536 generated_from_canonical: type: boolean enum: [true] editable: type: boolean enum: [false] fidelity: type: object required: [dropped_fields] properties: dropped_fields: type: array items: { type: string } notes: { type: string } FlowProjectionStaleness: type: object required: [stale, projection_version, latest_version] properties: stale: { type: boolean } projection_version: { type: string } latest_version: { type: string } FlowProjectionGenerator: type: object required: [generator_version, content_hash, generated_at] properties: generator_version: { type: string } content_hash: { type: string } generated_at: { type: string } Flow: type: object required: [schema, flow_id, title, version, scope, summary, steps, updated, truncated] properties: schema: type: string enum: [knowtation.flow/v0] flow_id: { type: string } title: { type: string } version: { type: string } scope: type: string enum: [personal, project, org] summary: { type: string } tags: type: array maxItems: 32 items: { type: string } steps: type: array maxItems: 100 items: { type: string } inputs: type: array items: type: object required: [name, type, required] properties: name: { type: string } type: { type: string } required: { type: boolean } vault_mirror_path: { type: string, nullable: true } updated: { type: string } truncated: { type: boolean } FlowStep: type: object required: [schema, step_id, flow_id, ordinal, owned_job, instruction, trigger, when_not_to_run, boundaries, output_shape, verification, automatable] properties: schema: type: string enum: [knowtation.flow_step/v0] step_id: { type: string } flow_id: { type: string } ordinal: { type: integer, minimum: 1 } owned_job: { type: string } instruction: { type: string } trigger: { type: string } when_not_to_run: { type: string } requires: type: array items: type: object required: [kind, id] properties: kind: type: string enum: [vault_scope, tool, file, artifact] id: { type: string } boundaries: type: array items: { type: string } skill_refs: type: array items: type: object required: [kind, id] properties: kind: type: string enum: [mcp_prompt, skill_pack, cli, external_tool] id: { type: string } inputs: type: array items: type: object required: [name, from] properties: name: { type: string } from: { type: string } outputs: type: array items: type: object required: [name, type] properties: name: { type: string } type: { type: string } output_shape: { type: string } verification: type: object required: [kind, evidence_required, description] properties: kind: type: string enum: [human_review, artifact_exists, value_match, test_pass, agent_check] evidence_required: { type: boolean } description: { type: string } automatable: type: string enum: [manual, agent_assisted, automatable]