ワンクリックで
ingest
Stage and commit approved investigation findings through the Knowledge Workspace Port into the spotlight namespace.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Stage and commit approved investigation findings through the Knowledge Workspace Port into the spotlight namespace.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when an investigation step may need an external integration such as browser acquisition, Maigret account discovery, Junkipedia narrative tracking, OSINT Navigator tool discovery, Noosphere C2PA signing, or Unpaywall access lookup.
Turn investigation findings into explicit, user-approved monitoring recommendations. Spotlight recommends; Mycroft owns any Scoutpost action.
OSINT investigation orchestrator — guides verified investigations from lead to findings to knowledge ingestion. Triggers on "investigate", "investigation", "OSINT", "look into", "dig into".
OSINT investigation toolkit — local SQL index of 12,500 tools (`osint-tools find`, offline), methodology guides, and optional OSINT Navigator (subscription tier). Works offline with any LLM.
Portable claim-to-evidence grounding for knowledge work, fact-checking, and Spotlight investigations. Use when extracting claims or findings, assigning confidence, diagnosing weak support, or deciding whether material is a lead, partially grounded claim, verified finding, disputed claim, or false claim.
Portable safe command construction for any agent skill or recipe. Use before a shell call containing user, model, scraped, configuration, filesystem, or generated values.
| name | ingest |
| description | Stage and commit approved investigation findings through the Knowledge Workspace Port into the spotlight namespace. |
| version | 1.0 |
| invocable_by | ["orchestrator","user"] |
| requires | [] |
You are archiving confirmed investigation findings into the shared knowledge workspace. Active case files and raw evidence remain case-local.
All durable mutations use the Knowledge Workspace Port. Set logical_space: spotlight_verified, actor type spotlight, the case and editorial decision IDs, classification, request ID, idempotency key, and expected versions. Never infer a destination from prose and never fall back to a different write backend. The destination path must resolve below <workspace>/spotlight/; the intelligence vault is always denied.
This skill instructs. You — the host runtime — execute. You read investigation files, write vault notes, update registries, and maintain the knowledge graph. The user sees the result; you do the work.
Two input modes:
The orchestrator passes project path and vault config (from .spotlight-config.json). All inputs are known:
knowledge_destination — typed target from .spotlight-config.jsonproject — project slugAt entry, write {CASE_DIR}/data/ingestion.json with
{"schema_version":"1.0","status":"requested"}. This receipt is case-local even
when every durable output is written to an external vault.
Read these case files:
{CASE_DIR}/data/findings.json
{CASE_DIR}/data/fact-check.json
{CASE_DIR}/data/investigation-log.json
{CASE_DIR}/data/summary.json
For an activated 1.1 case, also read
data/case-contract.json and data/source-expressions.json.
Skip to the Ingestion Process.
The user requests ingestion directly.
Step 1 — Findings source:
"Point me to your findings file (JSON with claims, sources, and evidence)."
read-file(<path>). Validate it contains a findings array where entries have sources. If the structure is wrong:
"This file doesn't match the expected format. I need a JSON file with a
findingsarray where each finding hasclaim,sources, andevidencefields."
STOP.
Step 2 — Knowledge destination:
"Which configured Knowledge Workspace should receive this approved package?"
Require a typed destination (openknowledge, markdown, or obsidian_legacy) and namespace spotlight. Standalone ingestion still requires an explicit editorial approval record; a bare filesystem path is insufficient.
Step 3 — Supplementary files:
Check whether these exist alongside the findings file. Use whatever is available; do not require all of them:
fact-check.json — verdict annotationsinvestigation-log.json — methodology, tools, search queriessummary.json — overview and conclusionsProceed to the Ingestion Process with whatever files were found.
Before mutation, use batch_stage to validate the exact Markdown and JSON registry package. Record its hash in the case-local receipt and show additions, updates, and exclusions to the journalist. Require approval of that exact hash. Then call batch_commit with the stable idempotency key; the port creates the checkpoint and durable journal.
If a pending journal exists, resume or restore that exact operation. Never start a second ingest over partial state. A filesystem lock is only an additional local guard, not the transaction mechanism.
list-files("{vault}/.ingest-lock")
If present:
"Another ingestion is in progress. Wait for it to complete before running again."
STOP. Do not proceed.
If absent:
Create the lock:
write-file("{vault}/.ingest-lock", "{project-id} {ISO timestamp}")
Remove the lock when ingestion completes — whether successful or failed. Always clean up (these commands follow the shell-safety skill's probe → resolve → operate pattern via scripts/spotlight_safe.py):
execute-shell("python3 scripts/spotlight_safe.py destructive-probe --base {vault} --path .ingest-lock")
execute-shell("python3 scripts/spotlight_safe.py resolve-path --base {vault} --path .ingest-lock")
execute-shell("rm <resolved-lock-path-from-probe>")
If the process errors partway through, remove the lock before reporting the error.
Eight steps. Execute in order. Do not skip steps.
In the steps below, examples written as read-file and write-file describe document construction only. Read durable state with port read; collect every proposed write-file result in the staged package instead of writing immediately. Publish them together only through the approved batch_commit. Reconcile hashes, registry IDs, and links before marking the case-local receipt committed.
Read all registry files:
read-file("{vault}/_registry.json")
read-file("{vault}/investigations/_registry.json")
read-file("{vault}/entities/_registry.json")
read-file("{vault}/entities/_aliases.json")
read-file("{vault}/entities/_merge-proposals.json")
read-file("{vault}/methodology/_registry.json")
read-file("{vault}/tools/_registry.json")
read-file("{vault}/claims/_registry.json")
If the vault is empty (registries do not exist), initialize each with the empty schema from references/registry-spec.md and schema_version: "1.0". A vault created before the claims layer existed simply lacks claims/_registry.json, entities/_aliases.json, and entities/_merge-proposals.json — initialize those with their empty schemas and continue; nothing else about the vault needs migration. Create the directories:
{vault}/investigations/
{vault}/entities/
{vault}/methodology/
{vault}/tools/
{vault}/claims/
write-file("{vault}/investigations/{project-id}.md", ...).
Frontmatter — per references/entity-model.md Investigation Note schema:
---
id: {project-id}
type: investigation
title: {from summary.json title, or derive from findings}
description: {one sentence, ~150 chars, summarizing the investigation — retrieval hint}
status: confirmed
date: {today YYYY-MM-DD}
regions: [{from findings}]
entities: [{entity IDs extracted in Step 3}]
methodology: [{technique IDs extracted in Step 4}]
tools: [{tool IDs extracted in Step 5}]
tags: [{derived from findings topics}]
verified_count: {count of high-confidence verified findings}
total_findings: {total findings count}
---
Body:
summary.json overview. If no summary.json, synthesize from findings.findings.json:
fact-check.json matching claim. If verdict is disputed or false, flag prominently: > **DISPUTED** — {reason} or > **FALSE** — {reason}[entity-id](../entities/entity-id.md) for each entity involved.[technique-id](../methodology/technique-id.md), [tool-id](../tools/tool-id.md).Extract entities from:
findings.json — connections[].from and connections[].tofindings.json — named entities in findings[].claim (apply basic NER: proper nouns, organization names, geographic names)Infer entity type:
| Pattern | Type |
|---|---|
| Person names (first + last) | person |
| Known organization patterns (UN, EU, ministry, commission, etc.) | organization |
| Company indicators (Inc, Ltd, GmbH, AG, SA, etc.) | company |
| Geographic names (countries, cities, regions) | place |
Generate kebab-case ID from entity name.
If entity exists in {vault}/entities/_registry.json (match on id):
read-file("{vault}/entities/{entity-id}.md")| [{project-id}](../investigations/{project-id}.md) | {role description} | {date} |{project-id} to frontmatter investigations array (if not already present)write-file the updated noteIf entity is new — alias collision check first:
Normalize the new entity's name and aliases (lowercase, trim, collapse whitespace) and look each up in entities/_aliases.json. If any normalized form maps to a different existing entity ID, this may be the same real-world entity under another name. Do not merge. Append a proposal to entities/_merge-proposals.json (per references/registry-spec.md — skip if the same pair already has a proposal in any status) and proceed with both entities separate. Then:
write-file("{vault}/entities/{entity-id}.md", ...) per references/entity-model.md:
---
id: {entity-id}
type: {inferred type}
description: {one sentence on who/what this entity is — retrieval hint}
subtype: {if determinable, else omit}
aliases: [{alternate names found in findings}]
country: {if determinable}
region: {if determinable}
investigations: [{project-id}]
first_seen: {today YYYY-MM-DD}
---
Body: Description, Role in Investigations table (one row for this project), Key Relationships (relative markdown links to other entities from same investigation).
Extract techniques from investigation-log.json:
cycles[].methodology.techniques_usedIf technique exists in {vault}/methodology/_registry.json:
read-file the existing note| [{project-id}](../investigations/{project-id}.md) | {context} | {date} |cycles[].methodology.failed_approaches to "Lessons Learned" section{project-id} to frontmatter investigations arraywrite-file the updated noteIf technique is new:
write-file("{vault}/methodology/{technique-id}.md", ...) per references/entity-model.md:
---
id: {technique-id}
type: technique
description: {one sentence, ~150 chars, on what this technique does — retrieval hint}
category: {infer from technique name}
tools: [{tool IDs used with this technique}]
investigations: [{project-id}]
---
Body: Description, Steps (if inferable from log), Tools (wikilinked), Usage History table, Lessons Learned.
Extract tools from investigation-log.json:
cycles[].methodology.tools_usedIf tool exists in {vault}/tools/_registry.json:
read-file the existing noteusage_count in frontmattercycles[].methodology.search_queries only if they are not duplicates of existing advice{project-id} to frontmatter investigations arraywrite-file the updated noteIf tool is new:
write-file("{vault}/tools/{tool-id}.md", ...) per references/entity-model.md:
---
id: {tool-id}
type: tool
description: {one sentence, ~150 chars, on what this tool does — retrieval hint}
category: {infer from tool name}
url: {if known}
access: {if known, else omit}
methodology: [{technique IDs that use this tool}]
investigations: [{project-id}]
usage_count: 1
---
Body: Capabilities, Access Notes, Usage History table (one row), Tips for Future Agents (from search queries if useful).
For each finding in findings.json, join its matching fact-check entry from fact-check.json (on finding ID) and apply the eligibility gate from references/entity-model.md:
verified or partially_verified.confidence_cap is above low.data/rlm-analysis.json (or an RLM artifact ID), or it carries an rlm_assisted: true marker. The investigator contract already forbids copying RLM artifacts into findings.json; this check is the defensive backstop, not the primary control.Ineligible findings are never written as claims. Record each exclusion for the ingest summary — finding ID and reason (e.g., F3: verdict disputed, F7: grounding capped low, F9: no sources). Filtering must be visible in the final report, never silent. Excluded findings still appear in the investigation note (Step 2), flagged as before.
For each eligible finding, claim ID is {project-id}-f{n}:
If the claim is new — write-file("{vault}/claims/{claim-id}.md", ...) per the Claim Note schema in references/entity-model.md:
verdict, confidence, confidence_cap carried from the finding and fact-check unchangedlayer: durable when verdict is verified; layer: lead + needs_verification: true when partially_verifiedrecorded = today; verified = fact-check date; verified_by = this projectentities = entity IDs from Step 3 that this finding references[{project-id}](../investigations/{project-id}.md))If the claim already exists (re-ingest of the same project, or a later project re-verifying the same claim):
re-verified / strengthened / superseded), update frontmatter verified/verified_by to the latest verification, and promote layer to durable if the new verdict is verified. History is append-only.For an activated 1.1 case, complete Step 7 so eligible claim notes and
claims/_registry.json both exist, then run the deterministic writer before
Step 8 while the project-owned vault lock is still held:
execute-shell("python3 scripts/ingest-source-expressions.py --case-dir {CASE_DIR} --vault {vault} --lock-held")
Never author its managed claim blocks directly. The writer verifies activation
and claim eligibility, embeds snapshots only in matching claims, retains
inactive lifecycle history, and writes a content-addressed ingest event. It
also extends data/ingestion.json with the source input hash and written/skipped
IDs. Re-ingest is byte-identical; publication failure is rolled back. There is
no standalone expression registry, and legacy expression-less claims remain
unchanged.
This is mandatory. Update every registry affected by the ingestion.
{vault}/investigations/_registry.json — add or update the investigation entry.{vault}/entities/_registry.json — add new entities, update investigations arrays for existing ones.{vault}/methodology/_registry.json — add new techniques, update investigations arrays for existing ones.{vault}/tools/_registry.json — add new tools, update investigations and usage_count for existing ones.{vault}/claims/_registry.json — add new claims, update verified/layer/needs_verification for re-verified ones. Entries stay minimal per references/registry-spec.md.{vault}/entities/_aliases.json — rebuild in full from the frontmatter of every entity note (canonical names + all aliases, normalized). This is a derived artifact; never merge by hand.{vault}/entities/_merge-proposals.json — write any proposals collected in Step 3. Preserve resolved (accepted/rejected) proposals.{vault}/_registry.json (master) — update stats counts (including claims) and last_updated to current ISO 8601 timestamp.See references/registry-spec.md for exact schemas.
write-file("{vault}/index.md", ...) using the template from references/registry-spec.md.
Use relative markdown links in the investigations table ([project-id](investigations/project-id.md)).
After Step 8 and, when applicable, Step 6a complete, remove the .ingest-lock.
Include both the claim exclusions and source-expression written/skipped IDs in
the ingest summary.
In pipeline mode, update {CASE_DIR}/data/ingestion.json to
{"schema_version":"1.0","status":"completed"} after the lock is removed. If
ingestion fails, write status failed before reporting the failure.
All cross-references use relative markdown links, regardless of vault type. Obsidian
resolves these into its graph and backlinks exactly like [[wikilinks]] (verified), and
they stay portable to non-Obsidian consumers:
[entity-id](../entities/entity-id.md)[project-id](../investigations/project-id.md)[technique-id](../methodology/technique-id.md)[tool-id](../tools/tool-id.md)[claim-id](../claims/claim-id.md)index.md browse section uses relative links from the vault root ([project-id](investigations/project-id.md)).Frontmatter and registry JSON are identical regardless of vault type.
id. If it exists, update it.references/entity-model.md. Agents rely on it programmatically. Never omit or rename fields.[entity-id](../entities/entity-id.md) form for all cross-references — Obsidian resolves these into its graph and backlinks, and they stay portable to other consumers.swiss-leaks, john-doe, reverse-image-search.> **LOW CONFIDENCE** — {reason} if included at all.verified or partially_verified, grounding cap above low, sources present, non-RLM origin. Every exclusion is logged with its reason in the ingest summary.entities/_aliases.json from entity frontmatter every run; alias collisions become merge proposals, never automatic merges.scripts/ingest-source-expressions.py may write managed source-expression blocks, under the project-owned vault lock. Never create an expression registry.For runtimes that provide a native vault-write(vault_path, note_path, content) verb, prefer it over raw write-file. The vault-write verb should handle:
.ingest-lock coordinationIf the adapter doesn't implement vault-write, fall back to the per-step write-file + read-file pattern described above.
Reads from:
{CASE_DIR}/data/findings.json
{CASE_DIR}/data/fact-check.json
{CASE_DIR}/data/investigation-log.json
{CASE_DIR}/data/summary.json
{CASE_DIR}/data/case-contract.json (activated 1.1 cases)
{CASE_DIR}/data/source-expressions.json (activated 1.1 cases)
{vault}/_registry.json
{vault}/investigations/_registry.json
{vault}/entities/_registry.json
{vault}/entities/_aliases.json
{vault}/entities/_merge-proposals.json
{vault}/methodology/_registry.json
{vault}/tools/_registry.json
{vault}/claims/_registry.json
Writes to:
{vault}/investigations/{project-id}.md
{vault}/entities/{entity-id}.md (per entity)
{vault}/methodology/{technique-id}.md (per technique)
{vault}/tools/{tool-id}.md (per tool)
{vault}/claims/{claim-id}.md (per eligible finding)
{vault}/investigations/_registry.json
{vault}/entities/_registry.json
{vault}/entities/_aliases.json (rebuilt, derived)
{vault}/entities/_merge-proposals.json (human-gated)
{vault}/methodology/_registry.json
{vault}/tools/_registry.json
{vault}/claims/_registry.json
{vault}/_registry.json (master)
{vault}/index.md
{CASE_DIR}/data/ingestion.json (source-expression receipt)