diff --git a/.gitattributes b/.gitattributes index cd3ef60..a45f352 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,3 +20,6 @@ *.icns binary verification/whole-v0.1.1/** -text verification/hesperos-capabilities/** -text + +verification/purpose-rebuild/** -text +verification/github-delivery/** -text diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5bc03..b681157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ -# 0.1.1 +# Changelog -Distinct delivery identity for the prior same-version cue repair, complete current-source kit and recognizable native atlas entries. Original doctrine/case revision/graph semantics retained. +## 0.2.0 — 2026-10-15 -# Changelog +Adds a persistent question-first inquiry desk, ordinary source notes/documents, entity and typed relationship authoring, native-case import/export, and a direct projection into the existing full atlas. Retains original attachment bytes and unknown case fields. + +The October 23 GitHub delivery corrects current-version download links and kit paths, and clarifies the inquiry desk installation and saved-state workflow. + +## 0.1.1 — 2026-10-14 + +Distinct delivery identity for the prior same-version cue repair, complete current-source kit and recognizable native atlas entries. Original doctrine/case revision/graph semantics retained. ## 0.1.0 — 2026-09-08 diff --git a/README.md b/README.md index eced703..799a2af 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # RED THREADS — Conspiracy Unraveler +## Start with your question + +Open the portable inquiry desk at `codex/red-threads/workspace/Open Red Threads.cmd` (Windows) or `Open Red Threads.command` (macOS) in the extracted release. Start an inquiry from a question, add a source note or ordinary document, name entities, and describe a typed relationship. The existing interactive atlas shows the result. You do not need to manufacture a case JSON first. Follow the [app workflow guide](src/red-threads/workspace/README.md) for saving, return, ordinary inputs and recovery. + + **Pull the thread. Find the mechanism.** ![RED THREADS investigative artwork: an investigator studies a network of people, institutions, and red connections](docs/assets/red-threads.png) @@ -8,7 +13,7 @@ A grant. A board seat. A shared adviser. Three articles repeating one claim. Int RED THREADS gives your AI investigative craft for following money, resources, obligations, influence, and decision rights. It helps turn a hunch or a pile of records into a source-linked case, competing explanations, useful next leads, and an interactive atlas you can inspect offline. -**[Explore the fictional demo](https://stunspot.github.io/red-threads/demo.html)** · **[Download v0.1.1](https://github.com/Stunspot/red-threads/releases/download/v0.1.1/red-threads-v0.1.1.zip)** · **[Install](documentation/installation.md)** · **[Run your first case](documentation/first-case.md)** +**[Explore the fictional demo](https://stunspot.github.io/red-threads/demo.html)** · **[Download v0.2.0](https://github.com/Stunspot/red-threads/releases/download/v0.2.0/red-threads-v0.2.0.zip)** · **[Install](documentation/installation.md)** · **[Run your first case](documentation/first-case.md)** ## See what a connection actually carries diff --git a/VERSION b/VERSION index 17e51c3..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.1 +0.2.0 diff --git a/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md b/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md new file mode 100644 index 0000000..0022687 --- /dev/null +++ b/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md @@ -0,0 +1,41 @@ +# Description + +RED THREADS turns a suspicious connection into an investigation you can see, interrogate and pursue. Feed it a transcript, a document, an institutional hairball or a well-earned hunch. It follows money, personnel, obligations, access, infrastructure, narratives and decision rights, then helps you work out what those relationships actually explain. Who funds the activity? Who can approve the brief? Who can withhold the resource? What changes when the dates line up—or fail to? + +The interactive atlas makes the investigation a geometry. Explore the wider network, focus on one actor, inspect a relationship's evidence, follow a path, filter the time period, or temporarily remove a node to see which connections remain. Linked views expose the timeline, source ancestry, rival explanations, unanswered leads and actor strategy. The source behind an impressive claim stays within reach, including when twelve articles turn out to be one article wearing eleven hats. + +RED THREADS gives intuition room to work. A hunch becomes a hypothesis with consequences worth checking; strategy and game theory expose constraints, incentives, outside options and credible moves. Funding, agreement, coordination and command keep their distinct meanings. The next research move aims to reveal a mechanism or separate explanations. Record-finding playbooks turn a suspicion into an obtainable next document. Campaign checkpoints preserve coverage and unfinished questions; guarded case revisions and transitive dependency reports help carry corrections through hypotheses, games and leads while retaining independent support. The local tools produce an offline atlas, structural discovery prompts, ranked research leads and JSON/CSV exports without a hosted service. Your red-string board has acquired standards. It is still allowed to be interesting. + +# Open the inquiry desk + +Choose New inquiry, write your question, and add a source note or ordinary TXT/Markdown/DOCX/PDF/image. Read text and DOCX in the desk; retained PDF/image originals remain available for inspection. Name entities and describe a precise sourced relationship. The full existing atlas stays below the workbench, with a separate-tab option for exploration. Cases persist outside the installation and can be downloaded in the native format. The app never invents relationships from attachments. + +# Usage Notes + +Copy the .zip from "Additional Files" to your Codex, Claude Code, or other harness, or add the .zip to a project knowledge base in Chat. Attach or reference the .zip in chat and say, "Install this Augment." The prompt in this post is optional. + +This archive contains a complete Codex skill. In a chat without skill installation, use its material as reference. Other harnesses need their own compatible skill-loading route. + +- Begin with a question, transcript, URL, document, existing case or rough theory. State any research boundary or time budget that matters. +- Ask for the atlas when a case becomes tangled, then inspect relationships, dates, sources, explanations and actor moves. The included Meridian case is entirely fictional and makes a useful first tour. +- Python 3.10+ and a modern browser enable the generated offline atlas and case tools. Live evidence collection uses the browsing and file tools available in your host. No paid API or database is required. +- Keep the current case JSON and evidence notes with your research files. Save/Load view preserves attention; resume restores the research frontier. Review source impact and use a new case revision when evidence changes. JSON/CSV export keeps records portable. +- Treat path traces and node removal as ways to inspect connectivity. Ask for the missing mechanism or next discriminating record before turning a visual pattern into an assertion about control. +- Draft articles and briefings when you want them. Publishing, outreach, paid data access and recurring monitoring remain actions you choose. + +The 0.2.0 ZIP contains its complete selected source kit, Codex runtime and nested Claude ZIP. The atlas has accepted-art tab/native icons and optional current-file desktop entries; follow the installation and native-entry guides. Mac execution remains unqualified. + +# Changelog + +v0.2.0 - Adds a persistent ordinary-input inquiry desk feeding the existing investigative atlas; retains native cases, source originals and unknown fields. + + +v0.1.1 - Distinct delivery identity, source-complete rebuild kit and recognizable native atlas entries; investigative doctrine and case operations unchanged. + +v0.1.0 - Initial release: investigative mechanism tracing; record-finding and reconciliation playbooks; rival hypotheses and actor strategy; campaign coverage and resumption; complete declared dependency tracing; guarded case revisions; curated offline atlas with label-aware routing and Save/Load views; structural scans, lead frontiers, JSON/CSV export and fictional worked investigations. + +# Tags + +investigative research, influence networks, power mapping, open source intelligence, source analysis, knowledge graphs, visual analysis, hypothesis testing, game theory, institutional analysis, narrative analysis, Agent SKILL, Augment + +Support: [stunspot Prompting Discord](https://discord.gg/stunspot) diff --git a/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md b/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md new file mode 100644 index 0000000..c3cc8c8 --- /dev/null +++ b/distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md @@ -0,0 +1,21 @@ +# RED THREADS Conspiracy Unraveler v0.2.0 + +Attach the ZIP and say, "Install this Augment." + +Use this optional prompt with the attached archive: + +```text +Install RED THREADS Conspiracy Unraveler from this ZIP using this host's supported skill installation route. The outer RED-THREADS-v0.2.0 source kit includes two host packages. For Codex, install the complete codex/red-threads folder. For Claude, import the inner claude/red-threads-v0.2.0.zip, which contains the complete red-threads skill. Each includes SKILL.md, references, knowledge, scripts, assets, examples and license notices. Keep the installed skill folder intact. Read SKILL.md and preserve the caller's identity; no additional persona or plugin server is required. Keep future investigative cases in my workspace, outside the installed skill. Verify the installed files and explain any host reload needed; distinguish file installation from successful skill discovery. If this chat cannot install skills, use the attached material as a reference and tell me that limitation plainly. + +Then help me begin with the lead or question I provide. Build a useful first map, follow the mechanisms, and keep my hunches available as working hypotheses. Use the bundled atlas when Python and a browser are available. Ask only for information that changes the investigation. +Open the inquiry desk for direct work from my question and source material. Use its ordinary source intake and typed connections to make a useful first neighborhood visible, then inspect the full atlas. Preserve existing native case fields and source originals when returning to advanced research. Read workspace/README.md for save and recovery behavior. + +``` + +After installation, start naturally: "Use RED THREADS to investigate this transcript and map the money, people, resources and decision rights behind it." + +For a quick tour, ask: "Render the included fictional Meridian case into my workspace and walk me through the source ancestry, dates and rival explanations." + +The generated atlas needs Python 3.10+ and a modern browser. Live investigation uses the host's available browsing and file tools; the atlas itself works offline. The method remains usable in conversation when interactive rendering is unavailable. + +If an output already exists, use a new explicit filename. A validation error identifies a case field or reference to correct before rendering. Keep the current case JSON, its evidence notes and the atlas together. Use Save view and Load view for presentation state. Ask RED THREADS to run resume when returning, and to inspect impact before applying a source correction to a new case revision. The case data and evidence let you revise the investigation later. diff --git a/distribution/install-red-threads.md b/distribution/install-red-threads.md index 6b7dbd9..c3cc8c8 100644 --- a/distribution/install-red-threads.md +++ b/distribution/install-red-threads.md @@ -1,13 +1,15 @@ -# RED THREADS Conspiracy Unraveler v0.1.1 +# RED THREADS Conspiracy Unraveler v0.2.0 Attach the ZIP and say, "Install this Augment." Use this optional prompt with the attached archive: ```text -Install RED THREADS Conspiracy Unraveler from this ZIP using this host's supported skill installation route. The outer RED-THREADS-v0.1.1 source kit includes two host packages. For Codex, install the complete codex/red-threads folder. For Claude, import the inner claude/red-threads-v0.1.1.zip, which contains the complete red-threads skill. Each includes SKILL.md, references, knowledge, scripts, assets, examples and license notices. Keep the installed skill folder intact. Read SKILL.md and preserve the caller's identity; no additional persona or plugin server is required. Keep future investigative cases in my workspace, outside the installed skill. Verify the installed files and explain any host reload needed; distinguish file installation from successful skill discovery. If this chat cannot install skills, use the attached material as a reference and tell me that limitation plainly. +Install RED THREADS Conspiracy Unraveler from this ZIP using this host's supported skill installation route. The outer RED-THREADS-v0.2.0 source kit includes two host packages. For Codex, install the complete codex/red-threads folder. For Claude, import the inner claude/red-threads-v0.2.0.zip, which contains the complete red-threads skill. Each includes SKILL.md, references, knowledge, scripts, assets, examples and license notices. Keep the installed skill folder intact. Read SKILL.md and preserve the caller's identity; no additional persona or plugin server is required. Keep future investigative cases in my workspace, outside the installed skill. Verify the installed files and explain any host reload needed; distinguish file installation from successful skill discovery. If this chat cannot install skills, use the attached material as a reference and tell me that limitation plainly. Then help me begin with the lead or question I provide. Build a useful first map, follow the mechanisms, and keep my hunches available as working hypotheses. Use the bundled atlas when Python and a browser are available. Ask only for information that changes the investigation. +Open the inquiry desk for direct work from my question and source material. Use its ordinary source intake and typed connections to make a useful first neighborhood visible, then inspect the full atlas. Preserve existing native case fields and source originals when returning to advanced research. Read workspace/README.md for save and recovery behavior. + ``` After installation, start naturally: "Use RED THREADS to investigate this transcript and map the money, people, resources and decision rights behind it." diff --git a/distribution/red-threads-companion.md b/distribution/red-threads-companion.md index 762fd74..0022687 100644 --- a/distribution/red-threads-companion.md +++ b/distribution/red-threads-companion.md @@ -6,6 +6,10 @@ The interactive atlas makes the investigation a geometry. Explore the wider netw RED THREADS gives intuition room to work. A hunch becomes a hypothesis with consequences worth checking; strategy and game theory expose constraints, incentives, outside options and credible moves. Funding, agreement, coordination and command keep their distinct meanings. The next research move aims to reveal a mechanism or separate explanations. Record-finding playbooks turn a suspicion into an obtainable next document. Campaign checkpoints preserve coverage and unfinished questions; guarded case revisions and transitive dependency reports help carry corrections through hypotheses, games and leads while retaining independent support. The local tools produce an offline atlas, structural discovery prompts, ranked research leads and JSON/CSV exports without a hosted service. Your red-string board has acquired standards. It is still allowed to be interesting. +# Open the inquiry desk + +Choose New inquiry, write your question, and add a source note or ordinary TXT/Markdown/DOCX/PDF/image. Read text and DOCX in the desk; retained PDF/image originals remain available for inspection. Name entities and describe a precise sourced relationship. The full existing atlas stays below the workbench, with a separate-tab option for exploration. Cases persist outside the installation and can be downloaded in the native format. The app never invents relationships from attachments. + # Usage Notes Copy the .zip from "Additional Files" to your Codex, Claude Code, or other harness, or add the .zip to a project knowledge base in Chat. Attach or reference the .zip in chat and say, "Install this Augment." The prompt in this post is optional. @@ -19,10 +23,13 @@ This archive contains a complete Codex skill. In a chat without skill installati - Treat path traces and node removal as ways to inspect connectivity. Ask for the missing mechanism or next discriminating record before turning a visual pattern into an assertion about control. - Draft articles and briefings when you want them. Publishing, outreach, paid data access and recurring monitoring remain actions you choose. -The0.1.1 ZIP contains its complete selected source kit, Codex runtime and nested Claude ZIP. The atlas has accepted-art tab/native icons and optional current-file desktop entries; follow the installation and native-entry guides. Mac execution remains unqualified. +The 0.2.0 ZIP contains its complete selected source kit, Codex runtime and nested Claude ZIP. The atlas has accepted-art tab/native icons and optional current-file desktop entries; follow the installation and native-entry guides. Mac execution remains unqualified. # Changelog +v0.2.0 - Adds a persistent ordinary-input inquiry desk feeding the existing investigative atlas; retains native cases, source originals and unknown fields. + + v0.1.1 - Distinct delivery identity, source-complete rebuild kit and recognizable native atlas entries; investigative doctrine and case operations unchanged. v0.1.0 - Initial release: investigative mechanism tracing; record-finding and reconciliation playbooks; rival hypotheses and actor strategy; campaign coverage and resumption; complete declared dependency tracing; guarded case revisions; curated offline atlas with label-aware routing and Save/Load views; structural scans, lead frontiers, JSON/CSV export and fictional worked investigations. diff --git a/docs/guide.html b/docs/guide.html index 5e989a3..1a5bcb2 100644 --- a/docs/guide.html +++ b/docs/guide.html @@ -13,7 +13,7 @@
-

RED THREADS / FIELD MANUAL 01 / v0.1.1

Start with a thread.
Keep the receipts.

Explore the fictional Meridian investigation, see what makes a connection meaningful, and create an atlas you can keep.

+

RED THREADS / FIELD MANUAL 01 / v0.2.0

Start with a thread.
Keep the receipts.

Explore the fictional Meridian investigation, see what makes a connection meaningful, and create an atlas you can keep.

Four things to inspect.

The Meridian File asks who can influence a proposed compute-licensing rule—and what would distinguish patron direction from coordinated or independent agreement. Every actor, amount, document, and quotation is invented teaching material.

Open the fictional atlas

Use your browser’s Back action to return to this guide, or open the atlas in a separate tab using your browser’s link menu.

@@ -25,7 +25,7 @@

Try Save view after choosing filters or a relationship. The downloaded snapshot preserves presentation state for this case. Load view restores a compatible snapshot; it does not edit the evidence.

-

Install the complete skill.

Download red-threads-v0.1.1.zip. For Codex install codex/red-threads; for Claude import the inner claude/red-threads-v0.1.1.zip. Keep the installed red-threads folder intact, including its references, scripts, assets, and example. The installation guide also covers the maintained src/red-threads source folder and the Codex installer.

Your AI host supplies the model and research tools. Python 3.10+ is needed for the bundled case commands and atlas generation. You can still investigate conversationally without Python.

Uploading a ZIP to an ordinary chat is reference use unless the host actually installs and discovers the skill. Use the host-specific installation guide to choose the right route.

Open the installation guide
+

Install the complete skill.

Download red-threads-v0.2.0.zip. For Codex install codex/red-threads; for Claude import the inner claude/red-threads-v0.2.0.zip. Keep the installed red-threads folder intact, including its references, scripts, assets, and example. The installation guide also covers the maintained src/red-threads source folder and the Codex installer.

Your AI host supplies the model and research tools. Python 3.10+ is needed for the bundled case commands and atlas generation. You can still investigate conversationally without Python.

Uploading a ZIP to an ordinary chat is reference use unless the host actually installs and discovers the skill. Use the host-specific installation guide to choose the right route.

Open the installation guide

Make the Meridian atlas.

For this local walkthrough, extract the complete kit and open a terminal in its codex/red-threads folder containing SKILL.md. If you use a source clone, open its src/red-threads folder instead. With Python 3.10+ available, run these commands in order. The output goes in the parent directory, outside the skill folder.

CHECK THE INCLUDED CASE
python scripts/red_threads.py validate examples/meridian-case.json

The JSON result should include "valid": true. This checks the data structure and references.

@@ -38,6 +38,6 @@

The next record
should change something.

A useful investigation leaves you with the best supported mechanism, a serious rival, and the observation that would change your mind.

Open the complete documentation
- + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 15b6cb2..3b7e738 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,7 +5,7 @@ RED THREADS — Follow the evidence. Find the mechanism. - + @@ -26,7 +26,7 @@ @@ -39,7 +39,7 @@
COLLABORATIVE DYNAMICS / INVESTIGATIVE AI AUGMENTFREE / VOL. 0.1.1

RED THREADS

-

CONSPIRACY UNRAVELER

Follow the money.
Find the mechanism.

Power has a paper trail. Sometimes it has a missing page.

Give your AI the craft to investigate money, influence, and hidden ties—with a case you can explore, challenge, and keep.

Get RED THREADS Free skill ZIP · Your host supplies the AI
+

CONSPIRACY UNRAVELER

Follow the money.
Find the mechanism.

Power has a paper trail. Sometimes it has a missing page.

Give your AI the craft to investigate money, influence, and hidden ties—with a case you can explore, challenge, and keep.

Get RED THREADS Free skill ZIP · Your host supplies the AI

OPEN FILE / THE MERIDIAN CASE

The money is real.
What does it buy?

A fictional teaching case. Every actor, amount, and record here is invented. The distinctions are the point.

@@ -108,11 +108,11 @@

RED THREADS

RED THREADS / CONSPIRACY UNRAVELERLOOK CLOSER.
RED THREADS — Conspiracy Unraveler. A magnifying glass reveals a network of red connections linking institutions on a dark map.
Connections are the beginning of the inquiry.
-

YOUR FIRST THREAD

Bring a hunch.
Keep the receipts.

Research, intuition, rival explanations, and game theory—working inside one portable investigation.

Download free · v0.1.1
+

YOUR FIRST THREAD

Bring a hunch.
Keep the receipts.

Research, intuition, rival explanations, and game theory—working inside one portable investigation.

Download free · v0.2.0

INSIDE THE COMPLETE SKILL ZIP

Investigative craft
One self-contained AI skill with references for mechanisms, source ancestry, rival hypotheses, and strategy.
A case you can carry
A worked fictional investigation, local Python tools, an offline HTML atlas, and CSV exports.
A place to pick up again
Your case.json holds the evidence. Save revisions, inspect corrections, and return to the strongest unresolved lead.

Your host supplies the AI, browsing, and source access. Python 3.10+ runs the bundled case tools. The renderer displays the case; it performs no research.

Install it. Open your first case.

THE FIELD MANUAL

Less guessing.
More investigating.

- + diff --git a/documentation-authorship.json b/documentation-authorship.json index a1105c4..c721318 100644 --- a/documentation-authorship.json +++ b/documentation-authorship.json @@ -1,84 +1,44 @@ { "authored_files": [ { - "bytes": 5565, + "bytes": 6097, "path": "README.md", - "sha256": "7621f8fa1e85342372346f43afffecefe93caa07de413ad82f8d920989812a06" - }, - { - "bytes": 1457, - "path": "CHANGELOG.md", - "sha256": "f92cc71d2aa2538a995cfb3dfb74c254bf0e04922ba657e894e000ba4e09f26a" + "sha256": "369b7d44451b1d41d8261648ea5699afb72d0a1a9213cb3db27945c5d2e7d4a9" }, { "bytes": 17796, "path": "docs/index.html", - "sha256": "f679778e51912aa315ac8d53233fe11bc354eb4bf210d70e04a3c2d149ec1ad5" + "sha256": "740b8b7a894d9753f8cf7df03dda0e70835f5cea78351034b19e139555856e6c" }, { "bytes": 13517, "path": "docs/guide.html", - "sha256": "2fed8414f839cd01a0dca7b698cde0bc4352c436e254748cee0bab0d3c01eb84" - }, - { - "bytes": 6134, - "path": "documentation/installation.md", - "sha256": "eca936ae05c06c9412e5253355756bea51883b0b2e5283e09c8a260abd9d4131" - }, - { - "bytes": 5031, - "path": "documentation/first-case.md", - "sha256": "69f48cdfb21e5cec3fdf7907fb97359c16b56cd5851068fc25134e362ce28995" + "sha256": "7c57dfe6e1db69012229af73f69f1bb114003326c50733a260e3bbc68380c52f" }, { - "bytes": 7715, - "path": "documentation/recovery.md", - "sha256": "e9827c61d20cf7c06c89a8bc73649c13d293fb16a9bca2c649bfbc91835d3e5b" + "bytes": 3042, + "path": "documentation/README.md", + "sha256": "8c32633b4b889ed6871f65ed0f517ed417ab89e16d13e6167797a9379f263573" }, { - "bytes": 1146, - "path": "documentation/native-entries.md", - "sha256": "0cfc84ae71217106f0b286cdcedfc10d201ebe1f4698c42951a524987c3910ce" + "bytes": 7058, + "path": "documentation/installation.md", + "sha256": "16b67ee923b9c60e67b501994cf613dda91f8df2e7beab780c68a5d83bf2c085" }, { "bytes": 1269, "path": "documentation/source-kit.md", - "sha256": "90e6521729d6f975630240965bc7a71365e7c05cd06a27696ab57628ad42c1a9" - }, - { - "bytes": 4583, - "path": "distribution/red-threads-companion.md", - "sha256": "5f65e0b70a0b76e4ca9c23fa9e2b714fe3dd80033a9d388854e6d88f12ead6ef" - }, - { - "bytes": 10174, - "path": "src/red-threads/references/visual-investigation.md", - "sha256": "d0f48bb1c0b3eaa9ecff487f5dc04134176c1d63e67b402ffcbe6e5675c6c69d" - }, - { - "bytes": 2357, - "path": "distribution/install-red-threads.md", - "sha256": "3c9219a2127f176ea08faa100f502fc2344f472d658cb8f1c5dbc7780c9bcaca" - }, - { - "bytes": 3242, - "path": "CONTRIBUTING.md", - "sha256": "1ac94e39119107630b96eac0fbee86d06d47d70e01be659bb8bbaa95ef011b82" - }, - { - "bytes": 2789, - "path": "documentation/README.md", - "sha256": "0f30269dffbeca2932f35c963c146352bcecebbc1021d9e53a141a2f562bcc75" + "sha256": "c61c2a3113c9a7bc143f2fbf0b0c08fffde55479be3cc4fef7d8876aee5e71a5" }, { - "bytes": 4707, + "bytes": 5148, "path": "documentation/evidence-and-licenses.md", - "sha256": "27332197e44c3b7bff91e4ac8df326b92034a8114bf116548e4d4be41a217fdd" + "sha256": "db8ce748416f56624996ef82688dd0713c7cb84f5bccae6064de4581908e3274" }, { - "bytes": 1484, - "path": "SECURITY.md", - "sha256": "20480e27a9c624968b3d54fd3a8a19cc324633777d35324a68c591779220d443" + "bytes": 1917, + "path": "CHANGELOG.md", + "sha256": "97e3e240f348ce2b3f7dcc3778b8b612a0f137ff70cfb9e0cef39909c029e4d6" } ], "authoring": { @@ -92,86 +52,46 @@ "sha256": "21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f" }, "mode": "in-process", - "run_id": "red-threads-v011-sixteen-topics-20261014" + "run_id": "red-threads-github-delivery-20261023" }, "authoring_response": { - "bytes": 747, - "path": "verification/whole-v0.1.1/response.md", - "sha256": "b8d02f298b07cc6bb6fabeda8fae5ee8fc0937fbf4fafcb4ecfaf09f8c9d84ba" + "bytes": 480, + "path": "verification/github-delivery/documentation-response.md", + "sha256": "6cbe66de168096f22f2dcd86cf925680e80d3758821448445cc444eddf8f10d3" }, "claim_boundary": "Binds retained evidence for one Hesperos authoring or material-revision run. It does not prove independent review, host activation, publication, accessibility conformance, or customer outcomes.", - "created_at": "2026-10-14T15:16:15+00:00", - "documentation_fingerprint": "cd5227b41aad626c818e8384d91b4b32a86e1bbcda8e6f19759ad612b7d66b2e", + "created_at": "2026-10-23T17:45:40+00:00", + "documentation_fingerprint": "ffa75b98fe87391c81be1a3de7af884f5b2e6cb025b6076225670def13e8dc4d", "documentation_manifest": { - "bytes": 740, - "path": "verification/whole-v0.1.1/documentation-delta.json", - "sha256": "c29e28cb599d1bd5c6ef447e58f31ecd991bbe8dd4d18a948792596f9a66dbd9" + "bytes": 523, + "path": "verification/github-delivery/documentation-delta.json", + "sha256": "8690de956db29c493522b9e43544f5a2151f70b15861466497a47b7c54f86196" }, "evidence_packet": { - "bytes": 545, - "path": "verification/whole-v0.1.1/evidence.json", - "sha256": "3cd99878d056b97487ebda7289e274f0ac782d532fe19cd02d356243288e6e35" + "bytes": 667, + "path": "verification/github-delivery/documentation-evidence.md", + "sha256": "dde986033ab77e6e69df459417c6f16c3caed5081b7390c7c2517f7aa9f1aff9" }, "execution_evidence": [ { - "bytes": 1600, - "path": "verification/whole-v0.1.1/FINAL-REVIEW.md", - "sha256": "49ce4bc933f04f3b896a35ede690ecce2231bb5182b1ae308749d04814b22a68" - }, - { - "bytes": 8138, - "path": "verification/whole-v0.1.1/final-review.json", - "sha256": "abbb4cc814a5c4093d5f21c7d7c0c8ed889423a6390d137e7641e8787bc1612b" - }, - { - "bytes": 8551, - "path": "verification/whole-v0.1.1/independent-review.json", - "sha256": "925203a0345813666e708a545a93379ec3048958c8e30735454ce84084e8ac9b" - }, - { - "bytes": 4218, - "path": "verification/whole-v0.1.1/REVIEW-OBSERVATIONS.md", - "sha256": "fd6e27fe7f76a6bd7b8e56e43d3873d0051b568615a624c24bb891f1f63f3a09" - }, - { - "bytes": 195, - "path": "verification/whole-v0.1.1/THREE-FILE-CONTROLS.log", - "sha256": "876efa2281e287c172207538967cfb6e3c5f963684a6ccd2dcf36b14514e06c9" - }, - { - "bytes": 2763, - "path": "verification/whole-v0.1.1/BROWSER-OBSERVATION.json", - "sha256": "c0d708825de2216a9f43bd1440a926d822eb7d6da7495ed3e131d55f8b0681eb" - }, - { - "bytes": 766, - "path": "verification/whole-v0.1.1/ENTRY-FIXTURE.json", - "sha256": "7ff8a64143ca4e2fd3e07537071b3ad3d7a2d1a0fe338c4311add8999ab799a8" - }, - { - "bytes": 883, - "path": "verification/whole-v0.1.1/RENDERER-CONTROLS.log", - "sha256": "f4e9f98cdbb5c2cf48fb9584e37a1af06ed08ac58be0d609c0c59cc1a53f75bc" - }, - { - "bytes": 230, - "path": "verification/whole-v0.1.1/SITE-CHECK.log", - "sha256": "34a9edf2746946ca7f81f79f1dc88f5552709f31229a37b9d90b16c80d15b828" + "bytes": 789, + "path": "verification/github-delivery/documentation-execution.md", + "sha256": "d82ea356d22e88f179bcd70838728dee0324de7692d28f65e5ffcc5c9f37aa50" }, { - "bytes": 14530, - "path": "verification/whole-v0.1.1/original-project.json", - "sha256": "7ba11cafd147fe35a4a7334896506f4c899dce56824f22d7234c8a273e36114f" + "bytes": 3867, + "path": "verification/github-delivery/previous-documentation-authorship.json", + "sha256": "5834d98a085f197ec9ffaf186af0ef44f1e774822a42f648d3bd1b8e21e63571" }, { - "bytes": 747, - "path": "verification/whole-v0.1.1/response.md", - "sha256": "b8d02f298b07cc6bb6fabeda8fae5ee8fc0937fbf4fafcb4ecfaf09f8c9d84ba" + "bytes": 480, + "path": "verification/github-delivery/documentation-response.md", + "sha256": "6cbe66de168096f22f2dcd86cf925680e80d3758821448445cc444eddf8f10d3" } ], "format": "cd-hesperos-documentation-authorship/v1", "product": { "slug": "red-threads", - "version": "0.1.1" + "version": "0.2.0" } } diff --git a/documentation-manifest.json b/documentation-manifest.json index f5a2af2..81b4357 100644 --- a/documentation-manifest.json +++ b/documentation-manifest.json @@ -1,7 +1,7 @@ { "format": "cd-customer-documentation/v1", "product": "RED THREADS", - "version": "0.1.1", + "version": "0.2.0", "customer_docs": [ "README.md", "documentation/README.md", @@ -21,7 +21,10 @@ "documentation/source-kit.md", "distribution/red-threads-companion.md", "src/red-threads/references/visual-investigation.md", - "distribution/install-red-threads.md" + "distribution/install-red-threads.md", + "src/red-threads/workspace/README.md", + "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md", + "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md" ], - "scope": "Full19: sixteen actually revised/new topics and three unchanged original bodies." -} + "scope": "Current complete inventory, with current app entrance and recovery delta separately authored; prior documentation bodies retained." +} \ No newline at end of file diff --git a/documentation/README.md b/documentation/README.md index a7c196a..827060d 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,9 +1,11 @@ # RED THREADS documentation -RED THREADS helps you investigate how people, institutions, resources, and decisions connect, while keeping the evidence available for inspection and correction. These guides apply to **v0.1.1** and case format **1**. +RED THREADS helps you investigate how people, institutions, resources, and decisions connect, while keeping the evidence available for inspection and correction. These guides apply to **v0.2.0** and case format **1**. Start with the [fictional atlas](https://stunspot.github.io/red-threads/demo.html) to see the result. The [first-case walkthrough](first-case.md) explains what to inspect and how to generate your own copy. +For local work, [open the inquiry desk](../src/red-threads/workspace/README.md), start with your question, and add notes or ordinary documents. The desk feeds the same evidence atlas used by the skill and saves your inquiries outside the installation. + ## Choose your next task | You want to… | Start here | diff --git a/documentation/evidence-and-licenses.md b/documentation/evidence-and-licenses.md index e63bbf9..769dac4 100644 --- a/documentation/evidence-and-licenses.md +++ b/documentation/evidence-and-licenses.md @@ -1,6 +1,6 @@ # Evidence, scope, and licenses -RED THREADS v0.1.1 provides one self-contained skill in Codex and Claude packages, plus its complete source/rebuild kit with investigative instructions, knowledge playbooks, deterministic case tools, and an offline atlas. Its useful claim is an inspectable workflow: follow evidence into mechanisms, compare explanations, and preserve the case for correction and resumption. +RED THREADS v0.2.0 provides one self-contained skill in Codex and Claude packages, plus its complete source/rebuild kit with investigative instructions, knowledge playbooks, deterministic case tools, and an offline atlas. Its useful claim is an inspectable workflow: follow evidence into mechanisms, compare explanations, and preserve the case for correction and resumption. ## What has been exercised @@ -12,6 +12,8 @@ The public [tests](../tests/) and [release builder](../tools/build_release.py) l The0.1.1 entry update also has bounded Edge map/focus/evidence and saved-view return observations, plus Windows shortcut creation, preservation and dispatch. These do not establish native macOS execution or fresh-host skill discovery. +The v0.2.0 inquiry desk adds a question-first local workspace for source notes and ordinary documents, entities, and typed relationships. Retained Windows qualification exercised the shipped launcher through first inquiry, source, entity, relationship, full atlas, and process restart. Host checks cover stale-write conflict handling and local DOCX reads. These checks do not establish native macOS execution or fresh-host skill discovery. + ## What those checks do not establish The fictional exercises do not measure live-web research accuracy or provide a numerical performance rating. This release has not been installed and discovered on a fresh Codex or Claude host. Account availability and host permissions can change whether a particular tool or installation route is usable. diff --git a/documentation/first-case.md b/documentation/first-case.md index 64b2f51..64bd1d1 100644 --- a/documentation/first-case.md +++ b/documentation/first-case.md @@ -1,5 +1,10 @@ # Explore your first case +## Start with your question + +Open the portable inquiry desk at `codex/red-threads/workspace/Open Red Threads.cmd` (Windows) or `Open Red Threads.command` (macOS) in the extracted release. Start an inquiry from a question, add a source note or ordinary document, name entities, and describe a typed relationship. The existing interactive atlas shows the result. You do not need to manufacture a case JSON first. Follow the [app workflow guide](../src/red-threads/workspace/README.md) for saving, return, ordinary inputs and recovery. + + Start with [the fictional Meridian atlas](https://stunspot.github.io/red-threads/demo.html). You can explore that generated result without installing a skill, running Python, or supplying private records. Every name, document, amount, and quotation in this example is invented teaching material. The question is concrete: **who can influence a proposed compute-licensing rule, by which mechanism, and what would distinguish broad funder direction from narrower control or independent agreement?** diff --git a/documentation/installation.md b/documentation/installation.md index 05421ca..180f160 100644 --- a/documentation/installation.md +++ b/documentation/installation.md @@ -1,10 +1,17 @@ # Install RED THREADS +## Start with your question + +[Download the v0.2.0 complete kit](https://github.com/Stunspot/red-threads/releases/download/v0.2.0/red-threads-v0.2.0.zip) and extract it into a new folder. With Python 3.10 or newer available, open `RED-THREADS-v0.2.0/codex/red-threads/workspace/Open Red Threads.cmd` on Windows. The macOS entry is `Open Red Threads.command`; native macOS execution remains untested. + +Choose **New inquiry**, enter a question, then use **Add a source or note** before naming entities and connecting them with a sourced relationship. The existing interactive atlas shows the result. **Saved on this computer** confirms a successful save outside the installation; closing and reopening the launcher returns to your collection. You do not need to manufacture a case JSON first. Follow the [app workflow guide](../src/red-threads/workspace/README.md) for supported documents, limits, case export, and recovery. + + Install the complete `red-threads` skill folder, then confirm that your host can discover and use it. An ordinary chat attachment can provide reference material; it does not by itself register a skill. ## Before you start -[Download the v0.1.1 complete kit ZIP](https://github.com/Stunspot/red-threads/releases/download/v0.1.1/red-threads-v0.1.1.zip). Its root folder is `RED-THREADS-v0.1.1`. Install the complete `codex/red-threads` runtime containing `SKILL.md`, references, knowledge, scripts, assets, examples and license notices. The same root includes raw source and rebuild tools. Keep the runtime folder intact. +[Download the v0.2.0 complete kit ZIP](https://github.com/Stunspot/red-threads/releases/download/v0.2.0/red-threads-v0.2.0.zip). Its root folder is `RED-THREADS-v0.2.0`. Install the complete `codex/red-threads` runtime containing `SKILL.md`, references, knowledge, scripts, assets, examples and license notices. The same root includes raw source and rebuild tools. Keep the runtime folder intact. You can also use the Codex installer route in this guide or the maintained [GitHub source folder](https://github.com/Stunspot/red-threads/tree/main/src/red-threads). To obtain a local source copy with Git: @@ -42,7 +49,7 @@ After using the installer, perform the same discovery and first-case checks in s ## Claude: uploaded custom skill -Download the complete kit, then use its nested `claude/red-threads-v0.1.1.zip` for this upload route. +Download the complete kit, then use its nested `claude/red-threads-v0.2.0.zip` for this upload route. 1. Enable **Code execution and file creation** in **Settings > Capabilities**. Organization settings may govern availability. 2. Open **Customize > Skills**, select **+**, then **+ Create skill**, then **Upload a skill**. @@ -75,5 +82,5 @@ Continue with [your first case](first-case.md). If discovery, Python, or renderi Before replacing an installed copy, finish the current pass and save the case, evidence notes, and views in your workspace. Preserve the old skill folder separately if you need rollback. Replace the installed folder with the complete new release, then repeat discovery and the fictional check. -To remove a local skill, remove only its installed `red-threads` folder. For an uploaded skill, disable or remove it through the host's skills management interface. Your cases remain in their separate workspace. Read the new release's changelog before migrating case data; v0.1.1 retains case format 1. +To remove a local skill, remove only its installed `red-threads` folder. For an uploaded skill, disable or remove it through the host's skills management interface. Your cases remain in their separate workspace. Read the new release's changelog before migrating case data; v0.2.0 retains case format 1. The current full-source kit can be rebuilt as described in [Rebuild the current kit](source-kit.md). Native atlas entries are explained in [Recognizable entries](native-entries.md). diff --git a/documentation/native-entries.md b/documentation/native-entries.md index 44253db..4ee7ac5 100644 --- a/documentation/native-entries.md +++ b/documentation/native-entries.md @@ -1,5 +1,10 @@ # Open your atlas with a recognizable entry +## Start with your question + +Open the portable inquiry desk at `codex/red-threads/workspace/Open Red Threads.cmd` (Windows) or `Open Red Threads.command` (macOS) in the extracted release. Start an inquiry from a question, add a source note or ordinary document, name entities, and describe a typed relationship. The existing interactive atlas shows the result. You do not need to manufacture a case JSON first. Follow the [app workflow guide](../src/red-threads/workspace/README.md) for saving, return, ordinary inputs and recovery. + + Enter the complete `codex/red-threads` skill folder in a release, or `src/red-threads` in a source clone. ## Recognizable atlas entries diff --git a/documentation/source-kit.md b/documentation/source-kit.md index 7ee6ac1..e50a0e2 100644 --- a/documentation/source-kit.md +++ b/documentation/source-kit.md @@ -1,6 +1,6 @@ # Rebuild the current source kit -Extract into a short, new folder. The `RED-THREADS-v0.1.1` root includes `src`, `tools`, `tests`, public site `docs`, customer `documentation`, `distribution`, configuration and current bound authorship references. Codex uses `codex/red-threads`; Claude imports `claude/red-threads-v0.1.1.zip`. Private evaluation history, case originals and unrelated estate records are excluded. +Extract into a short, new folder. The `RED-THREADS-v0.2.0` root includes `src`, `tools`, `tests`, public site `docs`, customer `documentation`, `distribution`, configuration and current bound authorship references. Codex uses `codex/red-threads`; Claude imports `claude/red-threads-v0.2.0.zip`. Private evaluation history, case originals and unrelated estate records are excluded. From that release root with Python3.10 or newer run `python -B tools/build_release.py --output path/to/new-candidate`. The destination must be new, outside src/, tools/, tests/, docs/, documentation/, distribution/ and .github/; existing outputs are preserved. The output contains the full customer ZIP, artwork, install prompt, companion guide, release manifest and checksum list. Current documentation references are checked before packaging; materially changed guides need their own current authorship/review, not a stale receipt. diff --git a/src/red-threads/SKILL.md b/src/red-threads/SKILL.md index 2864775..b9c8d37 100644 --- a/src/red-threads/SKILL.md +++ b/src/red-threads/SKILL.md @@ -35,6 +35,8 @@ For a sustained investigation or parallel acquisition, read [campaign operations ## Work through the atlas +For direct local work, open the inquiry desk in `workspace/Open Red Threads.cmd` or `Open Red Threads.command`. Start from the user's question, notes and ordinary documents; name entities and specific sourced relationships, then use the full atlas to inspect what the connection means. The desk saves outside the installation and exports the existing portable case format through Download case. Read `workspace/README.md` for its actual input, persistence and recovery boundaries. Preserve an imported case's unknown fields and advanced investigation features when continuing it through the native tools. + Read [visual investigation](references/visual-investigation.md) when the case would benefit from interactive exploration. The bundled atlas works offline and links the network, timeline, hypotheses, leads, actor games and source ancestry. Open a relationship to inspect its basis. Filter time, relation or status; focus a neighborhood; trace a path; temporarily remove a node to inspect remaining connectivity. Explain visual geometry as an editorial arrangement, and structural scenarios as structural scenarios. Use [case format and commands](references/case-format.md) before creating or changing portable case data. Put case files in the user's chosen workspace, outside this installed skill. Preserve stable IDs and meaningful corrections. Write JSON with a proper serializer, not string concatenation. Read existing data before editing and preserve unknown fields. diff --git a/src/red-threads/workspace/Open Red Threads.cmd b/src/red-threads/workspace/Open Red Threads.cmd new file mode 100644 index 0000000..138c8f7 --- /dev/null +++ b/src/red-threads/workspace/Open Red Threads.cmd @@ -0,0 +1,8 @@ +@echo off +where py >nul 2>nul +if %errorlevel% equ 0 ( + py -3 "%~dp0work-host.py" %* +) else ( + python "%~dp0work-host.py" %* +) +if errorlevel 1 pause diff --git a/src/red-threads/workspace/Open Red Threads.command b/src/red-threads/workspace/Open Red Threads.command new file mode 100644 index 0000000..f0ec79b --- /dev/null +++ b/src/red-threads/workspace/Open Red Threads.command @@ -0,0 +1,3 @@ +#!/bin/sh +cd "$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +exec python3 work-host.py "$@" diff --git a/src/red-threads/workspace/README.md b/src/red-threads/workspace/README.md new file mode 100644 index 0000000..ca9daef --- /dev/null +++ b/src/red-threads/workspace/README.md @@ -0,0 +1,25 @@ +# Start an inquiry in the app + +Run `Open Red Threads.cmd` on Windows or `Open Red Threads.command` on macOS from this folder. Python 3.10+ and a browser are required. Choose **New inquiry**, name it, and write the question you want to understand. A starting hunch is optional and remains attributed to the inquiry author. + +Choose **Add a source or note**. Paste a passage, locate a record, or attach an ordinary document. TXT/Markdown and DOCX text become readable source notes. PDF, PNG, JPEG and WebP originals are retained for you to inspect; there is no OCR or automatic claim extraction. Each attachment is limited to 8 MiB. **Download original** returns the retained bytes. + +Use **Add person, institution or thing** to name the entities the source concerns. Then **Connect two things** with a precise verb and a statement of what the evidence establishes. A reported or documented connection needs a selected source; a hypothesis may remain unsourced. Unknown dates stay unknown. The app does not decide whether a source is true or a relationship proves control. + +The full existing atlas appears below the workbench. Open it in its own tab for more room. Inspect threads, trace directed paths, filter dates and status, compare explanations, and follow source ancestry. These views preserve the native case model, including hypotheses, leads, games and extension fields supplied through an existing case. The simple workbench authors sources, entities and relationships; advanced case operations remain available through the complete skill and CLI. + +**Refine** edits an entity or relationship. **Read / correct** opens a source note and can replace its attached original. Download the current case before replacing an original you need to retain separately. Source corrections identify direct relationships using that source; inspect deeper dependencies through the atlas and native impact command before changing conclusions. The workbench records each saved material change in the case journal, but does not automatically retract or promote related claims. + +## Save, return and take the case elsewhere + +Successful actions report **Saved on this computer**. Cases live outside the installation in the current user's local application data, `Collaborative Dynamics/red-threads/work.json`. The actual folder is shown under Your inquiries. Closing and relaunching through the app returns to that collection even when its local browser port changes. Choose another collection with `--data-home FOLDER` or `RED_THREADS_HOME`; a folder path must be writable. The server binds only to loopback. + +**Download case** exports the selected inquiry in the existing portable case format, including its retained originals and unknown extension fields. **Open case** reads a valid native case JSON into a separate working copy; it never replaces the input file or another inquiry. This is the compatibility route for prepared investigations, not a requirement for starting new work. Save/Load view inside the atlas retains presentation choices separately, as before. + +When another desk has saved a newer collection, a stale save is refused. The dialog keeps your entered values so you can copy them before reloading. Download case exports the last successfully saved state, not an unfinished dialog. A malformed or precision-losing native JSON file is refused; preserve the original and correct it through the native tools. Files up to 32 MiB can be opened and the whole collection is limited to 40 MiB; keep large unrelated media outside the case. + +## Boundaries and recovery + +The app runs local case work and the existing atlas. It does not browse, contact people, purchase information or infer connections from attachments. Use your assistant's authorized research and document tools when that work is needed. Sensitive documents remain part of the case export: choose what is appropriate before sharing it. + +A failed source read leaves the dialog and current saved case intact. Keep the extracted workspace, scripts and atlas assets together if the page cannot start. If the saved store is unreadable, preserve it and launch with an accessible `--data-home`; do not overwrite it with an empty collection. Back up the collection or exported cases separately from the installation. Native macOS launching has not been observed in this Windows verification. diff --git a/src/red-threads/workspace/case-json.js b/src/red-threads/workspace/case-json.js new file mode 100644 index 0000000..7112411 --- /dev/null +++ b/src/red-threads/workspace/case-json.js @@ -0,0 +1,79 @@ +/* Loss-detecting native case input. JSON strings remain data. */ +(()=>{ + function decimalIdentity(raw) { + let [mantissa, exponent = "0"] = raw.toLowerCase().split("e"); + const negative = mantissa.startsWith("-"); + if (negative) mantissa = mantissa.slice(1); + const [whole, fraction = ""] = mantissa.split("."); + let digits = (whole + fraction).replace(/^0+/, ""); + if (!digits) return "0"; + let scale = BigInt(exponent) - BigInt(fraction.length); + const trailing = digits.match(/0+$/)?.[0].length || 0; + if (trailing) { digits = digits.slice(0, -trailing); scale += BigInt(trailing); } + return (negative ? "-" : "") + digits + "e" + scale; + } + function parse(text) { + if (typeof text !== "string") throw Error("Expected JSON text."); + if (new TextEncoder().encode(text).length > 32 * 1024 * 1024) throw Error("JSON exceeds 32 MiB; use a smaller source edition."); + if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1); + let pos = 0; + const fail = message => { throw Error(`${message} at JSON character ${pos + 1}.`); }; + const space = () => { while (/[\x20\t\r\n]/.test(text[pos] || "\0")) pos++; }; + function string() { + const start = pos++; + while (pos < text.length) { + const c = text[pos++]; + if (c === "\\") { pos++; continue; } + if (c === '"') { + try { return JSON.parse(text.slice(start, pos)); } + catch (_) { fail("Invalid JSON string"); } + } + } + fail("Unterminated JSON string"); + } + function value(depth) { + if (depth > 64) fail("JSON nesting exceeds 64 levels"); + space(); + const c = text[pos]; + if (c === '"') return string(); + if (c === "{") { + pos++; space(); + const result = {}, seen = new Set(); + if (text[pos] === "}") { pos++; return result; } + while (true) { + space(); if (text[pos] !== '"') fail("Expected an object key"); + const key = string(); + if (seen.has(key)) fail(`Duplicate JSON key ${JSON.stringify(key)}`); + seen.add(key); space(); if (text[pos++] !== ":") fail("Expected a colon"); + Object.defineProperty(result, key, {value:value(depth + 1), enumerable:true, writable:true, configurable:true}); + space(); const next = text[pos++]; + if (next === "}") return result; + if (next !== ",") fail("Expected a comma or closing brace"); + } + } + if (c === "[") { + pos++; space(); const result = []; + if (text[pos] === "]") { pos++; return result; } + while (true) { + result.push(value(depth + 1)); space(); const next = text[pos++]; + if (next === "]") return result; + if (next !== ",") fail("Expected a comma or closing bracket"); + } + } + for (const [literal, result] of [["true",true],["false",false],["null",null]]) { + if (text.startsWith(literal, pos)) { pos += literal.length; return result; } + } + const token = text.slice(pos).match(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/)?.[0]; + if (!token) fail("Expected a JSON value"); + if (token.length > 512) fail("Numeric token is too long for this browser editor"); + const number = Number(token); + if (!Number.isFinite(number) || (Number.isInteger(number) && !Number.isSafeInteger(number)) || decimalIdentity(token) !== decimalIdentity(String(number))) { + fail("Number cannot round-trip exactly through this browser editor; keep exact identifiers in quoted strings or use the native workflow"); + } + pos += token.length; return number; + } + const result = value(0); space(); if (pos !== text.length) fail("Unexpected trailing JSON content"); + return result; + } +window.CaseJSON={parse}; +})(); diff --git a/src/red-threads/workspace/desk.css b/src/red-threads/workspace/desk.css new file mode 100644 index 0000000..fc9864e --- /dev/null +++ b/src/red-threads/workspace/desk.css @@ -0,0 +1,2 @@ +[hidden]{display:none!important} +*{box-sizing:border-box}body{margin:0;background:#171318;color:#f3e9df;font:16px/1.5 system-ui}button,input,select,textarea{font:inherit}button,.button{background:#44222b;color:#fff;border:1px solid #b96370;border-radius:5px;padding:9px 13px;cursor:pointer;display:inline-block}button:hover,.button:hover{background:#63333f}button:disabled{opacity:.5}a{color:#f5b59d}header{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:20px 28px;border-bottom:1px solid #684751}header strong{font:800 24px Georgia;letter-spacing:3px}header span{flex:1;color:#c5b5b8}#status{position:sticky;top:0;margin:0;padding:9px 28px;background:#2a2027;z-index:5}main{display:grid;grid-template-columns:230px minmax(0,1fr);gap:25px;padding:25px}aside{border-right:1px solid #684751;padding-right:20px}aside button{width:100%;margin-bottom:8px;text-align:left}#location{font-size:12px;overflow-wrap:anywhere;color:#bdadb0}.question{padding:20px 0 28px;border-bottom:1px solid #684751}.question h1{font:42px Georgia;margin:0}.question p{font-size:20px;max-width:900px}.question a{margin-left:18px}.workbench{display:grid;grid-template-columns:1fr 1fr;gap:35px}.workbench h2{font:26px Georgia}article{background:#272027;border:1px solid #60434c;margin:12px 0;padding:16px;border-radius:8px;overflow-wrap:anywhere}article h3{margin:0 0 5px}article small{color:#ccb5ba}article pre{white-space:pre-wrap;font:14px/1.5 system-ui;max-height:220px;overflow:auto}article button,article a{margin:6px 8px 0 0}iframe{width:100%;height:900px;background:#f4f0e8;border:1px solid #684751}.atlas-panel{margin-top:30px}dialog{background:#f8f0e7;color:#271c23;border:2px solid #914451;max-width:min(640px,94vw);max-height:90vh;overflow:auto;padding:25px;border-radius:8px}dialog::backdrop{background:#000a}label{display:block;margin:14px 0}input,textarea,select{display:block;width:100%;padding:10px;border:1px solid #a79298;background:white;color:#281e23}select[multiple]{min-height:110px}#form-error{color:#a4262d}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid #df8e24;outline-offset:3px}@media(max-width:800px){main{display:block;padding:15px}aside{border-right:0}.workbench{display:block}header{padding:16px;gap:9px}header span{flex-basis:100%}.question h1{font-size:32px}iframe{height:720px}} diff --git a/src/red-threads/workspace/desk.js b/src/red-threads/workspace/desk.js new file mode 100644 index 0000000..4eea79e --- /dev/null +++ b/src/red-threads/workspace/desk.js @@ -0,0 +1,25 @@ +'use strict'; +const $=id=>document.getElementById(id),id=()=>crypto.randomUUID(),now=()=>new Date().toISOString(),clone=structuredClone; +let work={app:'red-threads',projects:[]},current=null,token='',revision='new',writing=false; +const el=(tag,text,attrs={})=>{const n=document.createElement(tag);if(text!==undefined)n.textContent=text;for(const[k,v]of Object.entries(attrs))n.setAttribute(k,v);return n;}; +function status(text){$('status').textContent=text;} +function button(text,fn){const b=el('button',text);b.type='button';b.onclick=fn;return b;} +async function api(url,data){const r=await fetch(url,data?{method:'POST',headers:{'Content-Type':'application/json','X-Work-Token':token,'If-Match':revision},body:JSON.stringify(data)}:{});const v=await r.json();if(!r.ok)throw Error(v.error||'Request failed');return v;} +async function commit(candidate,selected){if(writing)throw Error('A save is still running; wait a moment.');writing=true;try{const result=await api('/api/work',candidate);revision=result.revision;work=candidate;current=work.projects.find(p=>p.id===selected)||null;render();status('Saved on this computer.');}finally{writing=false;}} +function journal(data,message){data.case.updated_at=now();data.case.journal??=[];data.case.journal.push({date:new Date().toISOString().slice(0,10),change:message});} +function download(name,obj){const url=URL.createObjectURL(new Blob([JSON.stringify(obj,null,2)+'\n'],{type:'application/json'})),a=el('a',undefined,{href:url,download:name});a.click();setTimeout(()=>URL.revokeObjectURL(url),10000);} +function form(title,specs,submit){$('fields').replaceChildren();$('form-title').textContent=title;$('form-error').textContent='';const fields={};for(const s of specs){if(s.note){$('fields').append(el('p',s.note));continue;}const label=el('label',s.label),input=el(s.options?'select':s.type==='textarea'?'textarea':'input');input.id='f-'+s.key;input.name=s.key;if(s.options){for(const option of s.options){const pair=Array.isArray(option)?option:[option,option];input.append(el('option',pair[1],{value:pair[0]}));}if(s.multiple){input.multiple=true;for(const option of input.options)option.selected=(s.value||[]).includes(option.value);}else input.value=s.value||'';}else{if(s.type==='textarea')input.rows=5;else input.type=s.type||'text';if(s.type==='file')input.accept=s.accept;else input.value=s.value||'';}if(s.required)input.required=true;if(s.placeholder)input.placeholder=s.placeholder;label.append(input);$('fields').append(label);fields[s.key]=input;}$('form').onsubmit=async e=>{e.preventDefault();$('commit').disabled=true;try{const values=Object.fromEntries(Object.entries(fields).map(([k,n])=>[k,n.type==='file'?n.files[0]:n.multiple?[...n.selectedOptions].map(x=>x.value):n.value.trim()]));await submit(values);$('dialog').close();}catch(e){$('form-error').textContent=e.message+' Your entered values are still here.';}finally{$('commit').disabled=false;}};$('dialog').showModal();Object.values(fields)[0]?.focus();} +function create(){form('Start with a question',[{key:'title',label:'Inquiry title',required:true},{key:'question',label:'What are you trying to understand?',type:'textarea',required:true},{key:'prior',label:'Your starting hunch (optional)',type:'textarea'}],async f=>{const p={id:id(),data:{schema_version:1,case:{id:'case-'+id(),title:f.title,question:f.question,updated_at:now(),priors:f.prior?[{attribution:'Inquiry author',statement:f.prior}]:[]},nodes:[],edges:[],sources:[],hypotheses:[],leads:[],games:[]}};const next=clone(work);next.projects.push(p);await commit(next,p.id);});} +function mutate(message,change){const key=current.id;return async f=>{const next=clone(work),p=next.projects.find(p=>p.id===key);if(!p)throw Error('This inquiry is no longer available.');await change(p.data,f);journal(p.data,message);await commit(next,key);};} +function editQuestion(){const c=current.data.case;form('Refine the inquiry',[{key:'title',label:'Title',value:c.title,required:true},{key:'question',label:'Question',value:c.question,type:'textarea',required:true},{key:'notes',label:'Working notes',value:c.notes||'',type:'textarea'}],mutate('Refined the inquiry question',(data,f)=>Object.assign(data.case,f)));} +function base64(file){return new Promise((resolve,reject)=>{const r=new FileReader();r.onload=()=>resolve(r.result.split(',')[1]);r.onerror=()=>reject(Error('Could not read the file.'));r.readAsDataURL(file);});} +async function addSource(source=null){const used=source?current.data.edges.filter(e=>(e.source_ids||[]).includes(source.id)).length:0;form(source?'Read or correct a source':'Add a source or note',[...(used?[{note:`This source supports ${used} relationship(s). A correction may change their interpretation; review those threads in the atlas after saving.`}]:[]),{key:'title',label:'Source title',value:source?.title||'',required:true},{key:'locator',label:'Where can it be found? (URL, page, filename or observation)',value:source?.locator||source?.url||'',placeholder:'Meeting notes, page 3; or a public URL'},{key:'note',label:'Relevant passage or your source note',type:'textarea',value:source?.note||''},{key:'file',label:'Attach a document or image (optional)',type:'file',accept:'.txt,.md,.docx,.pdf,.png,.jpg,.jpeg,.webp'},{note:'Text and DOCX become readable notes. PDF and image originals are retained for you to read; there is no OCR or automatic claim extraction.'}],mutate(source?'Corrected source '+source.title:'Added a source',async(data,f)=>{const next=source?data.sources.find(s=>s.id===source.id):{id:'s-'+id()};Object.assign(next,{title:f.title,locator:f.locator||'Source note in this case',note:f.note});if(f.file){if(f.file.size>8*1024*1024)throw Error('Attachment limit is 8 MiB.');const ext=f.file.name.split('.').pop().toLowerCase();if(!['txt','md','docx','pdf','png','jpg','jpeg','webp'].includes(ext))throw Error('Use text, Markdown, DOCX, PDF or PNG/JPEG/WebP.');const raw=await base64(f.file);let text='';if(['txt','md'].includes(ext))text=new TextDecoder('utf-8',{fatal:true}).decode(await f.file.arrayBuffer());if(ext==='docx')text=(await api('/api/read-docx',{data:raw})).text;next.attachment={name:f.file.name,type:f.file.type,data:raw};if(text)next.note+=(next.note?'\n\n':'')+text;if(!f.locator)next.locator=f.file.name+' — original attached to this case';}if(!source)data.sources.push(next);}));} +function sourceDownload(s){try{const raw=Uint8Array.from(atob(s.attachment.data),c=>c.charCodeAt(0)),url=URL.createObjectURL(new Blob([raw],{type:'application/octet-stream'})),a=el('a',undefined,{href:url,download:s.attachment.name||'source'});a.click();setTimeout(()=>URL.revokeObjectURL(url),10000);}catch(e){status('Could not read the retained original: '+e.message);}} +function addNode(node=null){form(node?'Refine an entity':'Name a person, institution or thing',[{key:'label',label:'Name',value:node?.label||'',required:true},{key:'type',label:'Kind',value:node?.type||'organization',placeholder:'person, institution, event, policy…',required:true},{key:'group',label:'Part of the question / visual group',value:node?.group||'Initial connections'},{key:'summary',label:'What matters about it here?',value:node?.summary||'',type:'textarea'}],mutate(node?'Refined entity '+node.label:'Added an entity',(data,f)=>{if(node)Object.assign(data.nodes.find(n=>n.id===node.id),f);else data.nodes.push({id:'n-'+id(),...f});}));} +function addEdge(edge=null){if(current.data.nodes.length<2){status('Name at least two people, institutions or things before connecting them.');return;}const nodes=current.data.nodes.map(n=>[n.id,n.label]);form(edge?'Refine this thread':'Describe a specific connection',[{key:'source',label:'From',options:nodes,value:edge?.source||nodes[0][0],required:true},{key:'relation',label:'What did it do? Use a precise verb.',value:edge?.relation||'',placeholder:'funded, appointed, supplied, reported…',required:true},{key:'target',label:'To',options:nodes,value:edge?.target||nodes[1][0],required:true},{key:'summary',label:'What does the evidence actually establish?',value:edge?.summary||'',type:'textarea',required:true},{key:'layer',label:'Relationship layer',value:edge?.layer||'governance',required:true},{key:'status',label:'How is this connection known?',options:[['hypothesis','Working hypothesis'],['reported','Reported by a source'],['documented','Documented mechanism'],['inferred','Your inference'],['disputed','Disputed'],['retracted','Retracted']],value:edge?.status||'hypothesis'},{key:'source_ids',label:'Supporting sources (select all that apply)',options:current.data.sources.map(s=>[s.id,s.title]),multiple:true,value:edge?.source_ids||[]},{key:'start',label:'Effective start, if known',type:'date',value:edge?.start||''},{key:'end',label:'Effective end, if known',type:'date',value:edge?.end||''}],mutate(edge?'Revised relationship '+edge.relation:'Added a typed relationship',(data,f)=>{if(f.source===f.target)throw Error('Choose two distinct entities for this connection.');if(['reported','documented'].includes(f.status)&&!f.source_ids.length)throw Error('Choose a supporting source, or retain this as a hypothesis.');if(f.start&&f.end&&f.start>f.end)throw Error('The start must not be later than the end.');f.start=f.start||null;f.end=f.end||null;if(edge)Object.assign(data.edges.find(e=>e.id===edge.id),f);else data.edges.push({id:'e-'+id(),...f});}));} +function render(){const cases=$('cases');cases.replaceChildren();for(const p of work.projects)cases.append(button(p.data.case.title,()=>{if(writing)return;current=p;render();}));$('welcome').hidden=!!current;$('active').hidden=!current;$('backup').disabled=!current;if(!current)return;const data=current.data;$('title').textContent=data.case.title;$('question').textContent=data.case.question;const sourceList=$('sources');sourceList.replaceChildren();for(const s of data.sources){const a=el('article');a.append(el('h3',s.title),el('small',s.locator||s.url||'Source location not supplied'),el('pre',s.note||'Read the original before interpreting this source.'),button('Read / correct',()=>addSource(s)));if(s.attachment)a.append(button('Download original',()=>sourceDownload(s)));sourceList.append(a);}if(!data.sources.length)sourceList.append(el('p','A source can be a passage, an ordinary document, or your own observation.')); +$('nodes').replaceChildren();for(const n of data.nodes){const a=el('article');a.append(el('h3',n.label),el('small',n.type+' · '+(n.group||'Ungrouped')),el('p',n.summary||''),button('Refine',()=>addNode(n)));$('nodes').append(a);}$('edges').replaceChildren();for(const e of data.edges){const name=i=>data.nodes.find(n=>n.id===i)?.label||i,a=el('article');a.append(el('h3',name(e.source)+' → '+e.relation+' → '+name(e.target)),el('small',e.status+' · '+e.layer),el('p',e.summary),button('Refine thread',()=>addEdge(e)));$('edges').append(a);}const url='/api/atlas?id='+encodeURIComponent(current.id)+'&revision='+encodeURIComponent(revision);$('atlas').src=url;$('full-atlas').href=url;} +$('new-case').onclick=$('start-case').onclick=create;$('edit-question').onclick=editQuestion;$('add-source').onclick=()=>addSource();$('add-node').onclick=()=>addNode();$('add-edge').onclick=()=>addEdge();$('cancel').onclick=()=>$('dialog').close();$('backup').onclick=()=>current&&download('case.json',current.data); +$('open-case').onchange=async e=>{const f=e.target.files[0];try{if(!f)return;if(f.size>32*1024*1024)throw Error('Case limit is 32 MiB.');const data=CaseJSON.parse(await f.text()),p={id:id(),data},next=clone(work);next.projects.push(p);await commit(next,p.id);status('Opened a separate working copy. Original case file and unknown fields remain intact.');}catch(e){status('Could not open case: '+e.message);}finally{e.target.value='';}}; +window.addEventListener('beforeunload',e=>{if(writing||$('dialog').open){e.preventDefault();e.returnValue='';}}); +(async()=>{try{const result=await api('/api/work');token=result.token;revision=result.revision;work=result.work||work;$('location').textContent='Saved cases: '+result.location;current=work.projects.at(-1)||null;render();status('Ready. Sources and connections stay on this computer.');}catch(e){status(e.message+' Keep the existing store and restart with a readable --data-home.');for(const b of document.querySelectorAll('button'))b.disabled=true;}})(); diff --git a/src/red-threads/workspace/index.html b/src/red-threads/workspace/index.html new file mode 100644 index 0000000..8c352a2 --- /dev/null +++ b/src/red-threads/workspace/index.html @@ -0,0 +1 @@ +Red Threads · Follow a connection
RED THREADSFollow a connection. Find its mechanism.

Opening your cases…

What connects these things?

Start with a question or hunch. Add a source, identify the people or institutions it mentions, and describe the relationship it actually supports.

\ No newline at end of file diff --git a/src/red-threads/workspace/red-threads-icon.png b/src/red-threads/workspace/red-threads-icon.png new file mode 100644 index 0000000..4fa8a29 Binary files /dev/null and b/src/red-threads/workspace/red-threads-icon.png differ diff --git a/src/red-threads/workspace/work-host.py b/src/red-threads/workspace/work-host.py new file mode 100644 index 0000000..4b7c3cd --- /dev/null +++ b/src/red-threads/workspace/work-host.py @@ -0,0 +1,116 @@ +"""Local product workspace with persistent private work, independent of browser ports.""" +import argparse,json,os,secrets,sys,tempfile,threading,webbrowser,hashlib +from contextlib import contextmanager +from pathlib import Path +from http.server import ThreadingHTTPServer,BaseHTTPRequestHandler +from urllib.parse import urlsplit,unquote,parse_qs +import base64,io,zipfile,xml.etree.ElementTree as ET +sys.path.insert(0,str(Path(__file__).resolve().parents[1]/"scripts")) +import red_threads,render_atlas +ROOT=Path(__file__).resolve().parent +PRODUCT="red-threads" +MAX=40*1024*1024 +@contextmanager +def lock_store(store): + with open(store/'.save-lock','a+b') as f: + if os.name=='nt': + import msvcrt + if f.tell()==0:f.write(b'0');f.flush() + f.seek(0);msvcrt.locking(f.fileno(),msvcrt.LK_LOCK,1) + else: + import fcntl + fcntl.flock(f.fileno(),fcntl.LOCK_EX) + try:yield + finally: + if os.name=='nt':f.seek(0);msvcrt.locking(f.fileno(),msvcrt.LK_UNLCK,1) + else:fcntl.flock(f.fileno(),fcntl.LOCK_UN) +def revision(store): + p=store/'work.json';return hashlib.sha256(p.read_bytes()).hexdigest() if p.exists() else 'new' +class Handler(BaseHTTPRequestHandler): + def log_message(self,*args):pass + def reply(self,data,status=200,kind="application/json; charset=utf-8"): + raw=data if isinstance(data,bytes) else json.dumps(data,ensure_ascii=False).encode() + self.send_response(status);self.send_header("Content-Type",kind);self.send_header("Content-Length",str(len(raw)));self.send_header("Cache-Control","no-store");self.send_header("X-Content-Type-Options","nosniff");self.end_headers() + if self.command!="HEAD":self.wfile.write(raw) + def valid_host(self): + return self.headers.get('Host') in {'127.0.0.1:'+str(self.server.server_port),'localhost:'+str(self.server.server_port)} + def do_HEAD(self):self.do_GET() + def do_GET(self): + if not self.valid_host():return self.reply({"error":"Local workspace only"},403) + path=unquote(urlsplit(self.path).path) + if path=="/api/work": + try: + with self.server.write_lock,lock_store(self.server.store): + p=self.server.store/"work.json";raw=p.read_bytes() if p.exists() else None + work=json.loads(raw) if raw else None;rev=hashlib.sha256(raw).hexdigest() if raw else "new" + return self.reply({"work":work,"token":self.server.token,"location":str(self.server.store),"product":PRODUCT,"revision":rev}) + except (OSError,ValueError) as e:return self.reply({"error":"Your saved work could not be read: "+str(e)},500) + if path=="/api/atlas": + try: + with self.server.write_lock,lock_store(self.server.store):work=json.loads((self.server.store/'work.json').read_bytes()) + key=parse_qs(urlsplit(self.path).query).get('id',[''])[0] + case=next(p['data'] for p in work['projects'] if p['id']==key) + result=red_threads.validate_case(case) + if not result['valid']:raise ValueError('; '.join(x['message'] for x in result['errors'])) + return self.reply(render_atlas.render(case).encode('utf-8'),kind='text/html; charset=utf-8') + except (OSError,ValueError,StopIteration,KeyError) as e:return self.reply({'error':'Could not open this atlas: '+str(e)},400) + if path=="/health":return self.reply({"product":PRODUCT,"source":str(ROOT)}) + name=path.lstrip('/') or 'index.html';p=(ROOT/name).resolve() + if not p.is_relative_to(ROOT) or p.suffix.lower() not in {'.json','.html','.js','.css','.png','.jpg','.jpeg','.svg','.ico','.woff2'} or not p.is_file():return self.reply({"error":"Not found"},404) + try: + import mimetypes + return self.reply(p.read_bytes(),kind=mimetypes.guess_type(p.name)[0] or 'application/octet-stream') + except OSError:return self.reply({"error":"This page asset could not be read"},500) + def do_POST(self): + if not self.valid_host():return self.reply({'error':'Local workspace only'},403) + if self.headers.get('X-Work-Token')!=self.server.token:return self.reply({"error":"Reload this page to reconnect to your workspace."},403) + if urlsplit(self.path).path=='/api/read-docx': + try: + size=int(self.headers.get('Content-Length','0')) + if not 08*1024*1024:raise ValueError('Document limit is 8 MiB.') + with zipfile.ZipFile(io.BytesIO(raw)) as z: + if sum(i.file_size for i in z.infolist())>32*1024*1024:raise ValueError('Expanded document is too large.') + data=z.read('word/document.xml') + if b'{const repo=path.resolve(__dirname,'..'),base=fs.mkdtempSync(path.join(os.tmpdir(),'red-threads-test-')),evidence=path.join(repo,'verification/purpose-rebuild');fs.mkdirSync(evidence,{recursive:true});let server,browser;const checks=[];async function start(){const cmd=path.join(process.env.RED_THREADS_TEST_WORKSPACE||path.join(repo,'src/red-threads/workspace'),'Open Red Threads.cmd');server=cp.spawn('powershell.exe',['-NoProfile','-Command',`& '${cmd}' --no-browser --data-home '${base.replaceAll("'","''")}'`],{windowsHide:true});return await new Promise((resolve,reject)=>{let out='';const timeout=setTimeout(()=>reject(Error('Host startup timeout: '+out)),15000);server.stdout.on('data',d=>{out+=d.toString();for(const line of out.split(/\r?\n/))try{const r=JSON.parse(line);if(r.url){clearTimeout(timeout);resolve(r.url);return;}}catch{}});server.stderr.on('data',d=>out+=d.toString());});}function stop(){if(server){try{cp.execFileSync('taskkill',['/PID',String(server.pid),'/T','/F'],{stdio:'ignore',windowsHide:true});}catch{}server=null;}} +try{let url=await start();browser=await chromium.launch({headless:true,channel:'chrome'});let page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto(url);await page.waitForFunction(()=>document.querySelector('#status').textContent.startsWith('Ready'));await page.locator('#start-case').click();await page.locator('#f-title').fill('Community grant');await page.locator('#f-question').fill('What did the grant let the funder approve?');await page.locator('#commit').click();await page.waitForFunction(()=>!document.querySelector('#dialog').open);assert.equal(await page.locator('#title').textContent(),'Community grant');checks.push('blank inquiry starts from a real question'); +const file=path.join(base,'agreement.txt');fs.writeFileSync(file,'Section 4: The Foundation funds the Library exhibition. Approval concerns the exhibition budget only.');await page.locator('#add-source').click();await page.locator('#f-title').fill('Grant agreement');await page.locator('#f-file').setInputFiles(file);await page.locator('#commit').click();await page.waitForFunction(()=>!document.querySelector('#dialog').open);assert.match(await page.locator('#sources').textContent(),/exhibition budget only/);checks.push('ordinary text document becomes a readable source and original download'); +for(const name of ['Foundation','Library']){await page.locator('#add-node').click();await page.locator('#f-label').fill(name);await page.locator('#f-summary').fill('Named in the supplied agreement');await page.locator('#commit').click();await page.waitForFunction(()=>!document.querySelector('#dialog').open);}await page.locator('#add-edge').click();await page.locator('#f-relation').fill('funded');await page.locator('#f-summary').fill('Funds the exhibition, with budget approval limited to that exhibition.');await page.locator('#f-status').selectOption('documented');await page.locator('#commit').click();assert.match(await page.locator('#form-error').textContent(),/supporting source/);const sourceId=await page.locator('#f-source_ids option').first().getAttribute('value');await page.locator('#f-source_ids').selectOption(sourceId);await page.locator('#commit').click();await page.waitForFunction(()=>!document.querySelector('#dialog').open);checks.push('typed sourced relationship; missing citation correction succeeds in the same dialog'); +const atlas=page.frameLocator('#atlas');await atlas.locator('#case-title').waitFor();assert.match(await atlas.locator('body').textContent(),/Foundation/);assert.match(await atlas.locator('body').textContent(),/Library/);checks.push('authored entities and connection feed the full existing interactive atlas'); +let state=await(await fetch(url+'api/work')).json(),data=state.work.projects[0].data;assert.equal(data.sources[0].attachment.data,fs.readFileSync(file).toString('base64'));assert.equal(data.edges[0].status,'documented');const native=path.join(base,'case.json');data.extra_extension={preserve:'unrecognized values'};data.nodes[0].custom={value:17};fs.writeFileSync(native,JSON.stringify(data));await page.locator('#open-case').setInputFiles(native);await page.waitForFunction(()=>document.querySelector('#cases').children.length===2);state=await(await fetch(url+'api/work')).json();assert.equal(state.work.projects[1].data.extra_extension.preserve,'unrecognized values');assert.equal(state.work.projects[1].data.nodes[0].custom.value,17);checks.push('native import keeps unknown fields as a separate case copy'); +const original=state;await page.locator('#edit-question').click();await page.locator('#f-title').fill('Grant — follow the approval boundary');await page.locator('#commit').click();await page.waitForFunction(()=>!document.querySelector('#dialog').open);let res=await fetch(url+'api/work',{method:'POST',headers:{'Content-Type':'application/json','X-Work-Token':original.token,'If-Match':original.revision},body:JSON.stringify(original.work)});assert.equal(res.status,409);checks.push('stale save rejected without overwriting the newer inquiry'); +await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(evidence,'mobile.png'),fullPage:true});await browser.close();browser=null;stop();url=await start();browser=await chromium.launch({headless:true,channel:'chrome'});page=await browser.newPage();await page.goto(url);await page.waitForFunction(()=>document.querySelector('#title').textContent==='Grant — follow the approval boundary');assert.match(await page.locator('#sources').textContent(),/exhibition budget only/);checks.push('actual shipped launcher close/relaunch restores cases and original source');assert.equal(errors.length,0);fs.writeFileSync(path.join(evidence,'ACCEPTANCE.json'),JSON.stringify({status:'PASS',checks,errors},null,2));console.log(JSON.stringify({product:'red-threads',status:'PASS',checks}));}finally{if(browser)await browser.close();stop();}})().catch(e=>{console.error(e);process.exit(1)}); diff --git a/tests/test_inquiry_host.py b/tests/test_inquiry_host.py new file mode 100644 index 0000000..365144d --- /dev/null +++ b/tests/test_inquiry_host.py @@ -0,0 +1,35 @@ +"""Two independent host processes cannot overwrite a newer Red Threads collection.""" +import concurrent.futures,subprocess,tempfile,json,urllib.request,urllib.error,sys,base64,io,zipfile +from pathlib import Path +root=Path(__file__).resolve().parents[1];results=[];hosts=[] +def call(url,path,data=None,token=None,revision=None): + headers={} + if token:headers['X-Work-Token']=token + if revision:headers['If-Match']=revision + raw=json.dumps(data).encode() if data is not None else None + req=urllib.request.Request(url+path,raw,headers) + try: + with urllib.request.urlopen(req,timeout=10) as r:return r.status,json.load(r) + except urllib.error.HTTPError as e:return e.code,json.load(e) +with tempfile.TemporaryDirectory(prefix='red-host-') as home: + try: + for _ in range(2): + proc=subprocess.Popen([sys.executable,str(root/'src/red-threads/workspace/work-host.py'),'--no-browser','--data-home',home],stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,encoding='utf-8');hosts.append(proc) + urls=[json.loads(p.stdout.readline())['url'] for p in hosts] + states=[call(u,'api/work')[1] for u in urls] + data={'schema_version':1,'case':{'id':'a','title':'Inquiry','question':'Who approved what?','updated_at':'2026-10-15T00:00:00Z'},**{k:[] for k in ['sources','nodes','edges','hypotheses','leads','games']}} + payload={'app':'red-threads','projects':[{'id':'p','data':data}]} + with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool: + futures=[pool.submit(call,u,'api/work',payload,s['token'],s['revision']) for u,s in zip(urls,states)] + statuses=sorted(f.result()[0] for f in futures) + assert statuses==[200,409],statuses;results.append('two independent processes: one accepted same-base write, one conflict') + stored=(Path(home)/'work.json').read_bytes();state=call(urls[0],'api/work')[1] + malformed={'app':'red-threads','projects':[{'id':'bad','data':{}}]};assert call(urls[0],'api/work',malformed,state['token'],state['revision'])[0]==400;assert (Path(home)/'work.json').read_bytes()==stored;results.append('invalid native case leaves saved bytes unchanged') + document=io.BytesIO() + with zipfile.ZipFile(document,'w') as z:z.writestr('word/document.xml','A real supplied agreement passage.') + status,res=call(urls[0],'api/read-docx',{'data':base64.b64encode(document.getvalue()).decode()},state['token']) + assert status==200 and res['text']=='A real supplied agreement passage.';results.append('ordinary DOCX text read locally without altering the saved case') + assert call(urls[0],'api/read-docx',{'data':base64.b64encode(b'not a zip').decode()},state['token'])[0]==400;results.append('malformed DOCX produces recoverable error') + evidence=root/'verification/purpose-rebuild/HOST-ACCEPTANCE.json';evidence.write_text(json.dumps({'status':'PASS','checks':results},indent=2),encoding='utf-8');print(evidence.read_text(encoding='utf-8')) + finally: + for p in hosts:p.terminate();p.wait(timeout=10) diff --git a/verification/github-delivery/documentation-delta.json b/verification/github-delivery/documentation-delta.json new file mode 100644 index 0000000..111b5d1 --- /dev/null +++ b/verification/github-delivery/documentation-delta.json @@ -0,0 +1,16 @@ +{ + "format": "cd-customer-documentation/v1", + "product": "RED THREADS", + "version": "0.2.0", + "customer_docs": [ + "README.md", + "docs/index.html", + "docs/guide.html", + "documentation/README.md", + "documentation/installation.md", + "documentation/source-kit.md", + "documentation/evidence-and-licenses.md", + "CHANGELOG.md" + ], + "scope": "Current release download and package paths, first inquiry installation and recovery, and truthful v0.2.0 capability and evidence guidance." +} diff --git a/verification/github-delivery/documentation-evidence.md b/verification/github-delivery/documentation-evidence.md new file mode 100644 index 0000000..75b80e3 --- /dev/null +++ b/verification/github-delivery/documentation-evidence.md @@ -0,0 +1,3 @@ +# GitHub delivery source evidence + +The accepted v0.2.0 source and shelf ZIP match on all 36 runtime files. The inquiry host, workspace guide, package builder and native acceptance records establish the shipped local workflow and archive paths. The existing regression suite ran 53 tests with no failures and one Windows symlink privilege skip; the inquiry host conflict/DOCX checks also passed. GitHub main and its public download still expose v0.1.1. This revision makes the v0.2.0 download, outer root, Codex folder and nested Claude ZIP agree and explains the local first inquiry and saved-state signal. No host UI instructions or runtime behavior are changed. diff --git a/verification/github-delivery/documentation-execution.md b/verification/github-delivery/documentation-execution.md new file mode 100644 index 0000000..cd07ace --- /dev/null +++ b/verification/github-delivery/documentation-execution.md @@ -0,0 +1,3 @@ +# Hesperos release guidance revision + +In process, read the Hesperos canonical seed, grown practitioner and evidence/change governance reference alongside current installation, workspace, source-kit, evidence and release pages. Revised the installation journey from download and extraction through first inquiry and successful save; made the documentation index disclose that entrypoint; corrected version-specific download and kit paths across public entry pages; retained historical evidence qualifications and separated the 0.1.1 changelog entry. Manually followed the named archive paths against the accepted ZIP and source. Existing host setup guidance is preserved with its original verification date. No fresh-host installation, independent review, or macOS execution is claimed. diff --git a/verification/github-delivery/documentation-response.md b/verification/github-delivery/documentation-response.md new file mode 100644 index 0000000..cde1632 --- /dev/null +++ b/verification/github-delivery/documentation-response.md @@ -0,0 +1,7 @@ +AUTHORING_RUN_ID: red-threads-github-delivery-20261023 +AUTHORING_CAPABILITY: hesperos-documentation +AUTHORSHIP_SCOPE: materially-revised +AUTHORED_FILES_COUNT: 8 +AUTHORED_FILES: README.md|docs/index.html|docs/guide.html|documentation/README.md|documentation/installation.md|documentation/source-kit.md|documentation/evidence-and-licenses.md|CHANGELOG.md +DOCUMENTATION_FINGERPRINT: ffa75b98fe87391c81be1a3de7af884f5b2e6cb025b6076225670def13e8dc4d +HESPEROS_AUTHORING_COMPLETE diff --git a/verification/github-delivery/previous-documentation-authorship.json b/verification/github-delivery/previous-documentation-authorship.json new file mode 100644 index 0000000..d629b6d --- /dev/null +++ b/verification/github-delivery/previous-documentation-authorship.json @@ -0,0 +1,102 @@ +{ + "authored_files": [ + { + "bytes": 6097, + "path": "README.md", + "sha256": "4871c26077e45427d942c610ce912549bfcb2e51079a2ee2600bcbab81072940" + }, + { + "bytes": 5566, + "path": "documentation/first-case.md", + "sha256": "cef3818fe9c7534bfd78a5f294e728904dd83380659513b503d48ce128c61ed2" + }, + { + "bytes": 6669, + "path": "documentation/installation.md", + "sha256": "79ff5d5f40d19feb0c0d4fddb7895eb52beeee4040cb8ce8c51ba0ecf67733c5" + }, + { + "bytes": 1681, + "path": "documentation/native-entries.md", + "sha256": "221943fde0001ed95f6421041eccaad815d18cdef3a12e7cb8d7222ebc6ae8f4" + }, + { + "bytes": 4349, + "path": "src/red-threads/workspace/README.md", + "sha256": "99e6b66f096c258b2518e9d39b874b2d937b762cbf38077f580749e736f3219f" + }, + { + "bytes": 1729, + "path": "CHANGELOG.md", + "sha256": "a9be42612abb8ec8313902a3be15e4608a101b7efac3c41dcd8f3ed960bc131b" + }, + { + "bytes": 5257, + "path": "distribution/red-threads-companion.md", + "sha256": "eecce8938e9bff923a40f68f112c2af65663244d28fc4c7884ef3b574c53456a" + }, + { + "bytes": 2713, + "path": "distribution/install-red-threads.md", + "sha256": "fefb26f42806ce75058a3ed45a27b11f7e630a8d6643addba83c6e165d437b3e" + }, + { + "bytes": 2713, + "path": "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md", + "sha256": "fefb26f42806ce75058a3ed45a27b11f7e630a8d6643addba83c6e165d437b3e" + }, + { + "bytes": 5257, + "path": "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md", + "sha256": "eecce8938e9bff923a40f68f112c2af65663244d28fc4c7884ef3b574c53456a" + } + ], + "authoring": { + "authorship_scope": "materially-revised", + "capability": "hesperos-documentation", + "capability_entrypoint_sha256": "21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f", + "capability_revision": "sha256:21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f", + "capability_snapshot": { + "bytes": 7306, + "path": "verification/hesperos-capabilities/21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f/SKILL.md", + "sha256": "21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f" + }, + "mode": "in-process", + "run_id": "red-threads-purpose-020" + }, + "authoring_response": { + "bytes": 640, + "path": "verification/purpose-rebuild/documentation-response.md", + "sha256": "2ef6e0f469429df4ec75db089f3741d96ec0ccd3cd38e9f94448c167137f85a5" + }, + "claim_boundary": "Binds retained evidence for one Hesperos authoring or material-revision run. It does not prove independent review, host activation, publication, accessibility conformance, or customer outcomes.", + "created_at": "2026-10-15T05:47:04+00:00", + "documentation_fingerprint": "c53387e68b6ff7c765117e11292618fa25c15cd8c45d708bc37c0f2b086c55e1", + "documentation_manifest": { + "bytes": 610, + "path": "verification/purpose-rebuild/documentation-delta.json", + "sha256": "8dc6f55d3a2e07743517a58fc2988a4c4cb7c0d54f5d81d588909a443da49073" + }, + "evidence_packet": { + "bytes": 535, + "path": "verification/purpose-rebuild/documentation-evidence.md", + "sha256": "7e421ee77fabcb285b4deaa68daef9e3442bd18ff80b976edc64271c6b958f69" + }, + "execution_evidence": [ + { + "bytes": 703, + "path": "verification/purpose-rebuild/documentation-execution.md", + "sha256": "ad1cbf5a42eeb9f7270c62cdc63ea62b6be1c3da30bbf7ec899334ff9e3b6b61" + }, + { + "bytes": 640, + "path": "verification/purpose-rebuild/documentation-response.md", + "sha256": "2ef6e0f469429df4ec75db089f3741d96ec0ccd3cd38e9f94448c167137f85a5" + } + ], + "format": "cd-hesperos-documentation-authorship/v1", + "product": { + "slug": "red-threads", + "version": "0.2.0" + } +} diff --git a/verification/github-delivery/release-decision.md b/verification/github-delivery/release-decision.md new file mode 100644 index 0000000..0ab9cb2 --- /dev/null +++ b/verification/github-delivery/release-decision.md @@ -0,0 +1,3 @@ +# v0.2.0 GitHub delivery + +Accepted shelf baseline: v0.2.0, ZIP SHA-256 65333cf34e008275700000eba227657640f7eff052833c1774d04f021b2a0d3b. The inquiry desk capability was accepted as the update from v0.1.1. This delivery preserves that runtime and repairs current-version download links, kit paths, installation guidance and packaging of bound evidence through Git. The shelf transition is an in-place documentation/packaging edit retaining v0.2.0; no capability or compatibility contract changes. Existing shelf routing and customer sidecars remain valid. diff --git a/verification/purpose-rebuild/ACCEPTANCE.json b/verification/purpose-rebuild/ACCEPTANCE.json new file mode 100644 index 0000000..e2f7478 --- /dev/null +++ b/verification/purpose-rebuild/ACCEPTANCE.json @@ -0,0 +1,13 @@ +{ + "status": "PASS", + "checks": [ + "blank inquiry starts from a real question", + "ordinary text document becomes a readable source and original download", + "typed sourced relationship; missing citation correction succeeds in the same dialog", + "authored entities and connection feed the full existing interactive atlas", + "native import keeps unknown fields as a separate case copy", + "stale save rejected without overwriting the newer inquiry", + "actual shipped launcher close/relaunch restores cases and original source" + ], + "errors": [] +} \ No newline at end of file diff --git a/verification/purpose-rebuild/HOST-ACCEPTANCE.json b/verification/purpose-rebuild/HOST-ACCEPTANCE.json new file mode 100644 index 0000000..365989f --- /dev/null +++ b/verification/purpose-rebuild/HOST-ACCEPTANCE.json @@ -0,0 +1,9 @@ +{ + "status": "PASS", + "checks": [ + "two independent processes: one accepted same-base write, one conflict", + "invalid native case leaves saved bytes unchanged", + "ordinary DOCX text read locally without altering the saved case", + "malformed DOCX produces recoverable error" + ] +} \ No newline at end of file diff --git a/verification/purpose-rebuild/documentation-delta.json b/verification/purpose-rebuild/documentation-delta.json new file mode 100644 index 0000000..0a2137f --- /dev/null +++ b/verification/purpose-rebuild/documentation-delta.json @@ -0,0 +1,16 @@ +{ + "format": "cd-customer-documentation/v1", + "customer_docs": [ + "README.md", + "documentation/first-case.md", + "documentation/installation.md", + "documentation/native-entries.md", + "src/red-threads/workspace/README.md", + "CHANGELOG.md", + "distribution/red-threads-companion.md", + "distribution/install-red-threads.md", + "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md", + "distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md" + ], + "scope": "Materially revised app entrance, ordinary-source workflow, persistence and recovery." +} \ No newline at end of file diff --git a/verification/purpose-rebuild/documentation-evidence.md b/verification/purpose-rebuild/documentation-evidence.md new file mode 100644 index 0000000..fdde338 --- /dev/null +++ b/verification/purpose-rebuild/documentation-evidence.md @@ -0,0 +1,3 @@ +# Current app documentation evidence + +Full Red Threads skill and case format inspected, along with inquiry desk source, persistent host and canonical atlas renderer. ACCEPTANCE.json records ordinary question/source/entity/relationship creation, native unknown-field preservation, stale-save refusal and actual shipped Windows launcher close/relaunch. The new guide states exact controls, extraction limits, stored original behavior, correction boundaries and advanced compatibility. Previous receipt preserved beside this evidence. diff --git a/verification/purpose-rebuild/documentation-execution.md b/verification/purpose-rebuild/documentation-execution.md new file mode 100644 index 0000000..c81619c --- /dev/null +++ b/verification/purpose-rebuild/documentation-execution.md @@ -0,0 +1,3 @@ +# Hesperos material revision + +Applied Hesperos canonical/practitioner and procedures/recovery/usability references in process to author the new workflow guide and revise existing customer entrypoints. Applied Promptcraft and its full prompt-design reference to the short skill/install-prompt additions: question and mechanism remain primary; native structure stays backstage. Simulated sparse inquiry, missing source, unavailable host, authority boundary and apparent completion against the actual controls. Documents distinguish saved state, retained dialog edits, unsupported OCR, advanced case editing, and research performed elsewhere. No independent review or native macOS execution is claimed. diff --git a/verification/purpose-rebuild/documentation-response.md b/verification/purpose-rebuild/documentation-response.md new file mode 100644 index 0000000..72dfbb0 --- /dev/null +++ b/verification/purpose-rebuild/documentation-response.md @@ -0,0 +1,7 @@ +AUTHORING_RUN_ID: red-threads-purpose-020 +AUTHORING_CAPABILITY: hesperos-documentation +AUTHORSHIP_SCOPE: materially-revised +AUTHORED_FILES_COUNT: 10 +AUTHORED_FILES: README.md|documentation/first-case.md|documentation/installation.md|documentation/native-entries.md|src/red-threads/workspace/README.md|CHANGELOG.md|distribution/red-threads-companion.md|distribution/install-red-threads.md|distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0.md|distribution/RED THREADS Conspiracy Unraveler T-Free v0.2.0 Extra.md +DOCUMENTATION_FINGERPRINT: c53387e68b6ff7c765117e11292618fa25c15cd8c45d708bc37c0f2b086c55e1 +HESPEROS_AUTHORING_COMPLETE diff --git a/verification/purpose-rebuild/previous-documentation-authorship.json b/verification/purpose-rebuild/previous-documentation-authorship.json new file mode 100644 index 0000000..a1105c4 --- /dev/null +++ b/verification/purpose-rebuild/previous-documentation-authorship.json @@ -0,0 +1,177 @@ +{ + "authored_files": [ + { + "bytes": 5565, + "path": "README.md", + "sha256": "7621f8fa1e85342372346f43afffecefe93caa07de413ad82f8d920989812a06" + }, + { + "bytes": 1457, + "path": "CHANGELOG.md", + "sha256": "f92cc71d2aa2538a995cfb3dfb74c254bf0e04922ba657e894e000ba4e09f26a" + }, + { + "bytes": 17796, + "path": "docs/index.html", + "sha256": "f679778e51912aa315ac8d53233fe11bc354eb4bf210d70e04a3c2d149ec1ad5" + }, + { + "bytes": 13517, + "path": "docs/guide.html", + "sha256": "2fed8414f839cd01a0dca7b698cde0bc4352c436e254748cee0bab0d3c01eb84" + }, + { + "bytes": 6134, + "path": "documentation/installation.md", + "sha256": "eca936ae05c06c9412e5253355756bea51883b0b2e5283e09c8a260abd9d4131" + }, + { + "bytes": 5031, + "path": "documentation/first-case.md", + "sha256": "69f48cdfb21e5cec3fdf7907fb97359c16b56cd5851068fc25134e362ce28995" + }, + { + "bytes": 7715, + "path": "documentation/recovery.md", + "sha256": "e9827c61d20cf7c06c89a8bc73649c13d293fb16a9bca2c649bfbc91835d3e5b" + }, + { + "bytes": 1146, + "path": "documentation/native-entries.md", + "sha256": "0cfc84ae71217106f0b286cdcedfc10d201ebe1f4698c42951a524987c3910ce" + }, + { + "bytes": 1269, + "path": "documentation/source-kit.md", + "sha256": "90e6521729d6f975630240965bc7a71365e7c05cd06a27696ab57628ad42c1a9" + }, + { + "bytes": 4583, + "path": "distribution/red-threads-companion.md", + "sha256": "5f65e0b70a0b76e4ca9c23fa9e2b714fe3dd80033a9d388854e6d88f12ead6ef" + }, + { + "bytes": 10174, + "path": "src/red-threads/references/visual-investigation.md", + "sha256": "d0f48bb1c0b3eaa9ecff487f5dc04134176c1d63e67b402ffcbe6e5675c6c69d" + }, + { + "bytes": 2357, + "path": "distribution/install-red-threads.md", + "sha256": "3c9219a2127f176ea08faa100f502fc2344f472d658cb8f1c5dbc7780c9bcaca" + }, + { + "bytes": 3242, + "path": "CONTRIBUTING.md", + "sha256": "1ac94e39119107630b96eac0fbee86d06d47d70e01be659bb8bbaa95ef011b82" + }, + { + "bytes": 2789, + "path": "documentation/README.md", + "sha256": "0f30269dffbeca2932f35c963c146352bcecebbc1021d9e53a141a2f562bcc75" + }, + { + "bytes": 4707, + "path": "documentation/evidence-and-licenses.md", + "sha256": "27332197e44c3b7bff91e4ac8df326b92034a8114bf116548e4d4be41a217fdd" + }, + { + "bytes": 1484, + "path": "SECURITY.md", + "sha256": "20480e27a9c624968b3d54fd3a8a19cc324633777d35324a68c591779220d443" + } + ], + "authoring": { + "authorship_scope": "materially-revised", + "capability": "hesperos-documentation", + "capability_entrypoint_sha256": "21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f", + "capability_revision": "sha256:21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f", + "capability_snapshot": { + "bytes": 7306, + "path": "verification/hesperos-capabilities/21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f/SKILL.md", + "sha256": "21606903702db1eae375cb8e05dcad821616d399a574488f0f1be738d6dab42f" + }, + "mode": "in-process", + "run_id": "red-threads-v011-sixteen-topics-20261014" + }, + "authoring_response": { + "bytes": 747, + "path": "verification/whole-v0.1.1/response.md", + "sha256": "b8d02f298b07cc6bb6fabeda8fae5ee8fc0937fbf4fafcb4ecfaf09f8c9d84ba" + }, + "claim_boundary": "Binds retained evidence for one Hesperos authoring or material-revision run. It does not prove independent review, host activation, publication, accessibility conformance, or customer outcomes.", + "created_at": "2026-10-14T15:16:15+00:00", + "documentation_fingerprint": "cd5227b41aad626c818e8384d91b4b32a86e1bbcda8e6f19759ad612b7d66b2e", + "documentation_manifest": { + "bytes": 740, + "path": "verification/whole-v0.1.1/documentation-delta.json", + "sha256": "c29e28cb599d1bd5c6ef447e58f31ecd991bbe8dd4d18a948792596f9a66dbd9" + }, + "evidence_packet": { + "bytes": 545, + "path": "verification/whole-v0.1.1/evidence.json", + "sha256": "3cd99878d056b97487ebda7289e274f0ac782d532fe19cd02d356243288e6e35" + }, + "execution_evidence": [ + { + "bytes": 1600, + "path": "verification/whole-v0.1.1/FINAL-REVIEW.md", + "sha256": "49ce4bc933f04f3b896a35ede690ecce2231bb5182b1ae308749d04814b22a68" + }, + { + "bytes": 8138, + "path": "verification/whole-v0.1.1/final-review.json", + "sha256": "abbb4cc814a5c4093d5f21c7d7c0c8ed889423a6390d137e7641e8787bc1612b" + }, + { + "bytes": 8551, + "path": "verification/whole-v0.1.1/independent-review.json", + "sha256": "925203a0345813666e708a545a93379ec3048958c8e30735454ce84084e8ac9b" + }, + { + "bytes": 4218, + "path": "verification/whole-v0.1.1/REVIEW-OBSERVATIONS.md", + "sha256": "fd6e27fe7f76a6bd7b8e56e43d3873d0051b568615a624c24bb891f1f63f3a09" + }, + { + "bytes": 195, + "path": "verification/whole-v0.1.1/THREE-FILE-CONTROLS.log", + "sha256": "876efa2281e287c172207538967cfb6e3c5f963684a6ccd2dcf36b14514e06c9" + }, + { + "bytes": 2763, + "path": "verification/whole-v0.1.1/BROWSER-OBSERVATION.json", + "sha256": "c0d708825de2216a9f43bd1440a926d822eb7d6da7495ed3e131d55f8b0681eb" + }, + { + "bytes": 766, + "path": "verification/whole-v0.1.1/ENTRY-FIXTURE.json", + "sha256": "7ff8a64143ca4e2fd3e07537071b3ad3d7a2d1a0fe338c4311add8999ab799a8" + }, + { + "bytes": 883, + "path": "verification/whole-v0.1.1/RENDERER-CONTROLS.log", + "sha256": "f4e9f98cdbb5c2cf48fb9584e37a1af06ed08ac58be0d609c0c59cc1a53f75bc" + }, + { + "bytes": 230, + "path": "verification/whole-v0.1.1/SITE-CHECK.log", + "sha256": "34a9edf2746946ca7f81f79f1dc88f5552709f31229a37b9d90b16c80d15b828" + }, + { + "bytes": 14530, + "path": "verification/whole-v0.1.1/original-project.json", + "sha256": "7ba11cafd147fe35a4a7334896506f4c899dce56824f22d7234c8a273e36114f" + }, + { + "bytes": 747, + "path": "verification/whole-v0.1.1/response.md", + "sha256": "b8d02f298b07cc6bb6fabeda8fae5ee8fc0937fbf4fafcb4ecfaf09f8c9d84ba" + } + ], + "format": "cd-hesperos-documentation-authorship/v1", + "product": { + "slug": "red-threads", + "version": "0.1.1" + } +}