diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f093519..9e07d64 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,14 +1,22 @@
-## Summary
+## Reviewer brief
-Brief description of the changes.
+What single outcome does this contribution deliver, and why does it matter?
-## Changes
+## Outcome and Factfile
-- ...
+- Keyoku outcome:
+- Contribution id:
+- Factfile:
+- Exact head SHA:
+
+## Human judgment still required
+
+
## Checklist
-- [ ] Tests pass (`npm test`)
-- [ ] Build succeeds (`npm run build`)
-- [ ] New functionality has tests
+- [ ] This is one coherent reviewer outcome; unrelated work is split or stacked
+- [ ] Relevant behavior has evidence (test, screenshot, trace, report, or code tour)
- [ ] Breaking changes are documented
+- [ ] Declared outcome criteria pass for the exact proposed snapshot
+- [ ] I read the Factfile limits and understand what passing does not claim
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a0b4387..01b308a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,13 +8,18 @@ on:
jobs:
test:
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [20, 22]
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: 20
+ node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run eval # gate on muscle-memory retrieval quality, not just correctness
+ - run: npm audit --omit=dev
diff --git a/.github/workflows/keyoku-proof.yml b/.github/workflows/keyoku-proof.yml
new file mode 100644
index 0000000..b398206
--- /dev/null
+++ b/.github/workflows/keyoku-proof.yml
@@ -0,0 +1,45 @@
+name: Keyoku proof
+
+on:
+ pull_request:
+ workflow_dispatch:
+
+# Outcome probes execute the proposed repository revision. Keep this job
+# read-only; GitHub's native PR review owns the human decision.
+permissions:
+ contents: read
+
+concurrency:
+ group: keyoku-proof-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ factfile:
+ name: Keyoku / outcome proof
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 20
+ cache: npm
+ - name: Install project dependencies
+ run: npm ci
+ - name: Build this source revision
+ run: npm run build
+ - name: Prove Keyoku's own GitHub outcome
+ id: proof
+ env:
+ KEYOKU_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before || github.sha }}
+ run: node dist/index.js proof ci github-proof-v1 --base "$KEYOKU_BASE_SHA"
+ - name: Attach the full Factfile
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: keyoku-factfile-${{ github.sha }}
+ path: .keyoku/contributions/${{ steps.proof.outputs.contribution_id }}/factfile.*
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d8d9e7b..358dbc3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,39 +12,45 @@ jobs:
publish:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
# npm >= 11.5 is required for OIDC Trusted Publishing (tokenless + provenance).
- - run: npm install -g npm@latest
+ - run: npm install -g npm@12.0.2
- run: npm ci
- run: npm run typecheck
- run: npm test
- - run: npm run eval # quality gate: muscle-memory retrieval, not just correctness
+ - run: npm run eval
+ - run: npm audit --omit=dev
- run: node scripts/preflight.mjs # release integrity: version single-sourced, changelog present, built artifact matches package.json (npm test already built dist/)
# Publish path, in order of preference:
# 1. npm Trusted Publishing (OIDC, tokenless, provenance) — configure once at
# npmjs.com → package 'keyoku' → Settings → Trusted Publishing → add this repo
# (Keyoku-ai/keyoku) + workflow 'release.yml'. Then this just works.
# 2. NPM_TOKEN secret (Automation type) as a fallback.
- # Idempotent (skips a version already on npm), and degrades to a loud warning
- # instead of a red build when neither auth path is configured (maintainer then
- # publishes via CLI). It never silently claims success.
+ # Idempotent (skips a version already on npm). Prereleases go to `next` so
+ # the v2 `latest` line remains a rollback boundary. Publish failures are
+ # real release failures; a green workflow must never mean "nothing shipped".
- name: Publish to npm
run: |
VERSION=$(node -p "require('./package.json').version")
- if npm view "keyoku@${VERSION}" version >/dev/null 2>&1; then
- echo "keyoku@${VERSION} already on npm — skipping publish."
- exit 0
+ if [[ "${GITHUB_REF_NAME}" != "v${VERSION}" ]]; then
+ echo "::error title=Tag/version mismatch::Tag ${GITHUB_REF_NAME} does not match package v${VERSION}."
+ exit 1
fi
- if npm publish --provenance --access public; then
- echo "Published keyoku@${VERSION} with provenance."
+ if [[ "${VERSION}" == *-* ]]; then
+ DIST_TAG=next
else
- echo "::warning title=npm publish skipped::Could not publish keyoku@${VERSION} — enable Trusted Publishing for Keyoku-ai/keyoku (release.yml) at npmjs.com, or set the NPM_TOKEN secret. Until then publish from the maintainer CLI: npm publish --access public"
+ DIST_TAG=latest
+ fi
+ if npm view "keyoku@${VERSION}" version >/dev/null 2>&1; then
+ echo "keyoku@${VERSION} already on npm — skipping publish."
exit 0
fi
+ npm publish --provenance --access public --tag "${DIST_TAG}"
+ echo "Published keyoku@${VERSION} with provenance on ${DIST_TAG}."
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 7e5b7ed..854349b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,23 @@ dist/
.DS_Store
*.tmp
*.tgz
+.keyoku/runtime/
+.keyoku/contributions/
+.playwright-cli/
+output/
+.vitest-results.json
+preview-factfile.html
+
+# Local product/market working papers. Public documentation belongs in the
+# README, FACTFILE-STANDARD, GITHUB, SECURITY, and contributor guides.
+docs/KEYOKU-PROOF-V1.md
+docs/KEYOKU-CONTRIBUTION-GATE.html
+
+# Retired product explorations are kept locally for reference. Do not publish
+# internal briefs or obsolete UI captures with the generic proof harness.
+archive/experimental-control-plane/docs/
+docs/artifacts/factfile-human-review.png
+docs/artifacts/keyoku-control-plane-desktop.png
+docs/artifacts/keyoku-control-plane-mobile.png
+docs/artifacts/keyoku-intervention-channel-desktop.png
+docs/artifacts/keyoku-intervention-channel-mobile.png
diff --git a/.gitleaksignore b/.gitleaksignore
new file mode 100644
index 0000000..9dbc15b
--- /dev/null
+++ b/.gitleaksignore
@@ -0,0 +1,11 @@
+# Reviewed public-history false positives: synthetic credential-redaction tests
+# and model identifiers. Fingerprint scoping preserves detection everywhere else.
+5d57b56527daf6864c01a1834ce29b05719ec8e8:tests/activity.test.ts:curl-auth-header:154
+5d57b56527daf6864c01a1834ce29b05719ec8e8:tests/mcp-e2e.test.ts:curl-auth-header:92
+8a9db83a91a4df8ab4d2422a22f65f080c79e050:tests/activity.test.ts:generic-api-key:143
+8a9db83a91a4df8ab4d2422a22f65f080c79e050:tests/activity.test.ts:generic-api-key:149
+8a9db83a91a4df8ab4d2422a22f65f080c79e050:tests/activity.test.ts:curl-auth-header:146
+5d57b56527daf6864c01a1834ce29b05719ec8e8:VALIDATION-REPORT-2026-07-02.md:curl-auth-header:43
+66eab48d48f66892cbd2dc4a447f6181f31dd70c:packages/openclaw/src/init.ts:generic-api-key:288
+66eab48d48f66892cbd2dc4a447f6181f31dd70c:packages/openclaw/src/init.ts:generic-api-key:318
+1052ae1dcefda24aeb3dfb3880a17b7939bbb30e:packages/openclaw/src/init.ts:generic-api-key:306
diff --git a/.keyoku/architecture.yaml b/.keyoku/architecture.yaml
new file mode 100644
index 0000000..cabad39
--- /dev/null
+++ b/.keyoku/architecture.yaml
@@ -0,0 +1,94 @@
+schemaVersion: keyoku.dev/architecture/v1alpha1
+projectId: keyoku
+title: Keyoku proof and attention layer
+updatedAt: 2026-08-15T18:35:00Z
+components:
+ - id: contributors
+ label: People + coding agents
+ summary: Any human, harness, model, or CI process can produce a contribution.
+ layer: execution
+ icon: agent
+ view: { x: 40, y: 250 }
+ external: true
+ - id: outcome-contract
+ label: Outcome contract
+ summary: Repository-owned intent, constraints, proof claims, scope, and human criteria.
+ layer: source
+ icon: git
+ view: { x: 280, y: 70 }
+ owns:
+ - .keyoku/outcomes
+ - docs/FACTFILE-STANDARD.md
+ - id: repository-snapshot
+ label: Exact Git snapshot
+ summary: Base, head, committed diff, worktree digest, changed paths, and outcome history.
+ layer: source
+ icon: git
+ view: { x: 280, y: 300 }
+ owns:
+ - src/contribution.ts
+ - id: project-onboarding
+ label: One-command setup
+ summary: Project detection, starter outcome, and safe GitHub workflow generation.
+ layer: experience
+ icon: plug
+ view: { x: 280, y: 500 }
+ owns:
+ - src/project-profile.ts
+ - tests/project-profile.test.ts
+ - id: proof-evaluator
+ label: Proof evaluator
+ summary: Executes repository-defined observations, fails closed, and separates machine facts from judgment.
+ layer: proof
+ icon: proof
+ view: { x: 520, y: 190 }
+ owns:
+ - src/engine.ts
+ - src/probes.ts
+ - src/assert.ts
+ - tests/contribution.test.ts
+ - id: factfile
+ label: Factfile renderers
+ summary: Canonical JSON plus concise GitHub Markdown, detailed Markdown, HTML, and architecture SVG.
+ layer: proof
+ icon: keyoku
+ view: { x: 760, y: 190 }
+ owns:
+ - src/contribution.ts
+ - src/architecture.ts
+ - id: github
+ label: GitHub pull request
+ summary: Read-only Check summary and downloadable exact-revision proof artifact.
+ layer: experience
+ icon: git
+ view: { x: 1000, y: 70 }
+ external: true
+ - id: human-reviewer
+ label: Accountable reviewer
+ summary: Judges coherence, usability, maintainability, risk, and final acceptance.
+ layer: control
+ icon: human
+ view: { x: 1000, y: 330 }
+ external: true
+relations:
+ - from: contributors
+ to: repository-snapshot
+ kind: changes source
+ - from: outcome-contract
+ to: proof-evaluator
+ kind: defines claims
+ - from: repository-snapshot
+ to: proof-evaluator
+ kind: binds exact scope
+ - from: project-onboarding
+ to: proof-evaluator
+ kind: installs workflow
+ - from: proof-evaluator
+ to: factfile
+ kind: emits evidence
+ - from: factfile
+ to: github
+ kind: attaches summary
+ - from: factfile
+ to: human-reviewer
+ kind: requests judgment
diff --git a/.keyoku/outcomes/archive-abandoned-surfaces.yaml b/.keyoku/outcomes/archive-abandoned-surfaces.yaml
new file mode 100644
index 0000000..7a58009
--- /dev/null
+++ b/.keyoku/outcomes/archive-abandoned-surfaces.yaml
@@ -0,0 +1,69 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: archive-abandoned-surfaces
+revision: 1
+title: Abandoned product surfaces leave the active build
+objective: >-
+ Code and positioning that only support the abandoned Omnigent fleet-runner and regulated Outcome
+ Engine directions are recoverably archived, while shared verification, connector, security,
+ learning, and provenance primitives remain active and tested.
+owner:
+ kind: human
+ id: keyoku-owner
+ name: Tye
+ role: accountable product owner
+constraints:
+ - Archive rather than permanently delete historical implementation and tests.
+ - Remove archived code from compilation, packaging, CLI, MCP, and active product promises.
+ - Preserve provider-neutral connectors and autonomy approvals.
+ - Preserve all deterministic goal, probe, assertion, evidence, and workflow-learning behavior.
+criteria:
+ - description: The active TypeScript source and tests contain no Omnigent-specific runtime dependency
+ probe:
+ kind: command
+ run: >-
+ sh -c "! grep -R -i -l omnigent src tests --include='*.ts' | grep -q ."
+ timeoutMs: 30000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ - description: Legacy source, dedicated tests, and positioning are present with recovery documentation
+ probe:
+ kind: command
+ run: >-
+ sh -c "test -f archive/legacy-omnigent/README.md &&
+ test $(find archive/legacy-omnigent/src -name '*.ts' | wc -l) -eq 5 &&
+ test $(find archive/legacy-omnigent/tests -name '*.ts' | wc -l) -eq 5 &&
+ test -f archive/legacy-positioning/OUTCOME-ENGINE.md &&
+ test -f archive/legacy-positioning/README.md"
+ timeoutMs: 30000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ - description: CLI and MCP no longer advertise archived fleet-runner commands or tools
+ probe:
+ kind: command
+ run: >-
+ sh -c "npm run build >/dev/null &&
+ ! node dist/index.js help | grep -E 'omnigent|keyoku (run|converge|guardrails|connect)'"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ - description: The complete active Keyoku suite passes after archival
+ probe:
+ kind: command
+ run: npm test
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+createdAt: 2026-08-09T00:28:00Z
+updatedAt: 2026-08-09T00:28:00Z
diff --git a/.keyoku/outcomes/behavior-iteration-v1.yaml b/.keyoku/outcomes/behavior-iteration-v1.yaml
new file mode 100644
index 0000000..6411a27
--- /dev/null
+++ b/.keyoku/outcomes/behavior-iteration-v1.yaml
@@ -0,0 +1,126 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: behavior-iteration-v1
+revision: 2
+title: Keyoku governs AI product iteration toward observable behavior
+objective: >-
+ A coding harness can use Keyoku to move one repository-owned product outcome through bounded,
+ exact-source proof rounds until evidence passes or a truthful stop condition requires a human.
+owner:
+ kind: human
+ id: keyoku-owner
+ name: Tye
+ role: accountable product owner
+constraints:
+ - Keyoku coordinates evidence and instructions but does not silently run an agent, accept work, push, or deploy.
+ - Every round is bound to the exact Git head and worktree digest.
+ - Human judgment remains separate from automated proof.
+ - Checkpoint retries are idempotent and conflicting replays fail closed.
+ - Usage is explicitly sourced and is never inferred from UI or chat messages.
+scope:
+ include:
+ - src/iteration.ts
+ - src/iteration-cli.ts
+ - src/index.ts
+ - src/server.ts
+ - src/contribution.ts
+ - tests/iteration.test.ts
+ - tests/contribution.test.ts
+ - tests/e2e.test.ts
+ - tests/mcp-e2e.test.ts
+ - docs/ITERATION.md
+ - README.md
+ - CHANGELOG.md
+ - .keyoku/outcomes/behavior-iteration-v1.yaml
+ maxChangedFiles: 13
+criteria:
+ - description: The behavior-iteration state machine, limit rules, idempotency, and tamper detection pass together
+ probe:
+ kind: command
+ run: >-
+ sh -c "npx vitest run tests/iteration.test.ts &&
+ npx vitest run tests/contribution.test.ts -t 'distinguishes a clean committed contribution'"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Deterministic tests exercise convergence, replay, no progress, cost ceilings, human review, and ledger tampering.
+ whyItMatters: The controller must stop truthfully and must not turn repeated or manipulated activity into proof.
+ code:
+ - path: src/iteration.ts
+ purpose: Implements the append-only protocol, exact-source rounds, limits, instructions, and replay rules.
+ - path: tests/iteration.test.ts
+ purpose: Exercises the behavior and adversarial boundaries in real temporary Git repositories.
+ artifacts: []
+ - description: CLI and MCP expose one provider-neutral iteration protocol without an autonomous runner or acceptance tool
+ probe:
+ kind: command
+ run: >-
+ sh -c "npx tsx src/index.ts iterate help | grep -q 'Keyoku behavior iteration' &&
+ npx vitest run tests/mcp-e2e.test.ts -t 'provider-neutral iteration protocol'"
+ timeoutMs: 180000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The public CLI and MCP catalog expose start, status, next, and checkpoint while excluding agent-run and auto-accept capabilities.
+ whyItMatters: Codex, Claude Code, and other harnesses can share the same loop without handing Keyoku undeclared execution authority.
+ code:
+ - path: src/iteration-cli.ts
+ purpose: Provides the human-operable local interface.
+ - path: src/server.ts
+ purpose: Provides the harness-neutral MCP interface.
+ - path: tests/mcp-e2e.test.ts
+ purpose: Asserts the public capability and absence boundaries.
+ artifacts: []
+ - description: The complete active Keyoku suite passes with the iteration layer enabled
+ probe:
+ kind: command
+ run: npm test
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: All active Keyoku behavior passes in one clean-revision suite.
+ whyItMatters: The iteration layer must not regress Factfiles, Pulse, proof sessions, security, or retained compatibility behavior.
+ code:
+ - path: tests/
+ purpose: Covers the complete active product surface.
+ - path: package.json
+ purpose: Defines the reproducible project gate.
+ artifacts: []
+ - description: Strict TypeScript checking accepts the public event, state, CLI, and MCP contracts
+ probe:
+ kind: command
+ run: npm run typecheck
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: TypeScript validates the schemas and every consumer without an error.
+ whyItMatters: Agent adapters must receive one consistent iteration contract across CLI, MCP, and library use.
+ code:
+ - path: src/iteration.ts
+ purpose: Defines the exported schemas and derived state.
+ - path: src/index.ts
+ purpose: Exports the library contract and routes the CLI.
+ artifacts: []
+humanCriteria:
+ - id: useful-agent-direction
+ description: A coding agent receives enough evidence and constraint context to make the next bounded product change without hidden chain-of-thought
+ guidance: Inspect a failed-round instruction and confirm it names the objective, failed claim, reproduction path, source identity, constraints, and checkpoint contract.
+ - id: truthful-product-boundary
+ description: The product reads as an iteration and proof layer rather than an autonomous coding harness or auto-approval system
+ guidance: Inspect README, CLI help, MCP descriptions, and the behavior-iteration guide for conflicting authority claims.
+createdAt: 2026-08-25T17:00:00.000Z
+updatedAt: 2026-08-25T17:30:00.000Z
diff --git a/.keyoku/outcomes/contribution-gate-pivot.yaml b/.keyoku/outcomes/contribution-gate-pivot.yaml
new file mode 100644
index 0000000..9620464
--- /dev/null
+++ b/.keyoku/outcomes/contribution-gate-pivot.yaml
@@ -0,0 +1,189 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: contribution-gate-pivot
+revision: 4
+title: A maintainer can understand and decide an agent contribution
+objective: >-
+ In one short reading, a maintainer can understand what was requested, what changed, who or what
+ did the work, which outcome claims have relevant evidence, what remains uncertain, and which
+ judgments still require an accountable human before the contribution is accepted.
+owner:
+ kind: human
+ id: keyoku-owner
+ name: Tye
+ role: accountable product owner
+constraints:
+ - Core use is free for public and private projects and can run locally without a hosted account.
+ - GitHub is the first distribution surface, but schemas and verification remain provider-neutral.
+ - A human remains accountable; agent identity, harness, and model are supporting provenance.
+ - Verification fails closed and never describes a failed or incomplete probe as proof.
+ - A Factfile is bound to an exact Git head plus worktree digest and states the limits of its claim.
+ - Existing user changes in keyoku-engine are preserved; removal requires a dependency audit.
+criteria:
+ - description: A contribution produces portable JSON, Markdown, and HTML receipts tied to the exact source snapshot
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts -t "binds a passing Factfile to the exact repository snapshot"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The workflow created all three receipt formats and rejected review or publication after the source changed.
+ whyItMatters: A maintainer can share the receipt while knowing exactly which code state it describes.
+ code:
+ - path: src/contribution.ts
+ purpose: Captures the repository digest, creates the receipts, and rejects stale review or publication.
+ - path: tests/contribution.test.ts
+ purpose: Exercises receipt generation, exact-snapshot binding, publication, review, acceptance, and stale-proof rejection.
+ artifacts:
+ - kind: screenshot
+ path: docs/artifacts/factfile-human-review.png
+ label: Human-first Factfile
+ caption: The rendered receipt leads with the requested outcome and pending human decisions.
+ - description: A failed automated observation leaves the claimed outcome visibly unproven
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts -t "reports evidence gaps and never treats a failed probe as proof"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: A deliberately failing probe produced an evidence-gaps state instead of a green contribution.
+ whyItMatters: A broken or incomplete check cannot be presented to a maintainer as successful proof.
+ code:
+ - path: src/contribution.ts
+ purpose: Converts failed automated observations into the evidence_gaps gate state.
+ - path: tests/contribution.test.ts
+ purpose: Creates a failing command and asserts that the Factfile remains unproven.
+ artifacts: []
+ - description: Required human judgments remain pending until an identified person records a decision
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts -t "keeps required human judgments separate from automated proof"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Passing commands left the contribution in human-review-required until a named person judged the declared question.
+ whyItMatters: Software cannot silently convert test output into a human opinion about clarity, usefulness, or readiness.
+ code:
+ - path: src/contribution.ts
+ purpose: Tracks automated state and human verdicts independently.
+ - path: tests/contribution.test.ts
+ purpose: Proves an agent cannot submit the human verdict and acceptance cannot happen early.
+ artifacts: []
+ - description: Agent provenance records the exact harness and model while a human remains accountable
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts -t "keeps required human judgments separate from automated proof"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The contribution records Tye as accountable owner and Codex with the exact gpt-5.6-sol model identity.
+ whyItMatters: Reviewers can distinguish who is responsible from which agent and harness performed the work.
+ code:
+ - path: src/contribution.ts
+ purpose: Defines first-class human and agent actors, including owner, harness, and model fields.
+ - path: tests/contribution.test.ts
+ purpose: Asserts that the exact harness, model, and human owner survive into the contribution.
+ artifacts: []
+ - description: Credential-shaped probe output is removed before any receipt becomes shareable
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts -t "redacts credential-shaped evidence before creating shareable files"
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: A probe containing a credential-shaped value produced only redacted JSON, Markdown, HTML, and publishable data.
+ whyItMatters: Evidence should be safe to review and share without leaking the secrets it encountered.
+ code:
+ - path: src/contribution.ts
+ purpose: Redacts credential-shaped keys and strings before evidence reaches any Factfile format.
+ - path: tests/contribution.test.ts
+ purpose: Injects a fake API key and confirms the original value appears nowhere in the published receipt.
+ artifacts: []
+ - description: Existing Keyoku behavior remains compatible with the contribution-gate change
+ probe:
+ kind: command
+ run: npm test
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: All 320 pre-existing and contribution-gate tests completed successfully after the pivot.
+ whyItMatters: The new contribution workflow does not require sacrificing the Keyoku behavior people already rely on.
+ code:
+ - path: tests/
+ purpose: Covers the existing product plus the new repository-local contribution workflow.
+ - path: package.json
+ purpose: Defines the complete build-and-test command used for this compatibility check.
+ artifacts: []
+ - description: The implementation remains internally consistent under strict type checking
+ probe:
+ kind: command
+ run: npm run typecheck
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: TypeScript accepted the full source tree without type errors under the repository's strict configuration.
+ whyItMatters: The evidence and review data structures agree across parsing, gate execution, rendering, and CLI use.
+ code:
+ - path: src/contribution.ts
+ purpose: Implements the portable outcome, contribution, evidence, review, and Factfile types and workflows.
+ - path: tsconfig.json
+ purpose: Defines the compile-time rules used by the consistency check.
+ artifacts: []
+ - description: Installed production dependencies have no known npm audit vulnerability
+ probe:
+ kind: command
+ run: npm audit --omit=dev --audit-level=high
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: npm reported no known high-severity vulnerability in the installed production dependency graph.
+ whyItMatters: The receipt should disclose dependency risk rather than imply that passing product tests cover it.
+ code:
+ - path: package.json
+ purpose: Declares the production dependency surface assessed by npm audit.
+ - path: package-lock.json
+ purpose: Pins the exact dependency versions covered by this observation.
+ artifacts: []
+humanCriteria:
+ - id: one-minute-comprehension
+ description: A maintainer can explain the request, change, evidence, uncertainty, and next decision after one short reading
+ guidance: Read only the main report first. Raw commands or repository inspection should not be necessary.
+ - id: proof-relevance
+ description: Each claim is taught through relevant artifacts and code context rather than raw assertion values
+ guidance: Expand the evidence cards. The meaning should be understandable without opening the raw audit details.
+ - id: visual-clarity
+ description: The report's hierarchy makes the decision easier without dashboard theater or unexplained metrics
+ guidance: Inspect the desktop report, then narrow the window and confirm the reading order remains clear.
+createdAt: 2026-08-09T00:06:21Z
+updatedAt: 2026-08-09T20:50:00Z
diff --git a/.keyoku/outcomes/github-proof-v1.yaml b/.keyoku/outcomes/github-proof-v1.yaml
new file mode 100644
index 0000000..ce98b60
--- /dev/null
+++ b/.keyoku/outcomes/github-proof-v1.yaml
@@ -0,0 +1,219 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: github-proof-v1
+revision: 10
+title: Keyoku turns agent work into a live, reviewable proof session
+objective: >-
+ A maintainer can add free Keyoku to a Git project, see what connected agents are doing, resolve
+ genuine blockers through durable instructions, review meaningful exact-revision evidence, and
+ share a Factfile that explains the outcome without replacing human acceptance.
+owner:
+ kind: human
+ id: keyoku-owner
+ name: Tye
+ role: accountable product owner
+constraints:
+ - Local and GitHub proof works for public and private repositories without a hosted account.
+ - One contribution represents one coherent reviewer outcome; unrelated work should be split or stacked.
+ - GitHub proof execution remains read-only and never gives untrusted pull-request code a write token.
+ - Outcome revisions and definitions remain repository-owned Git history.
+ - Passing claims are bounded to declared observations and never presented as universal correctness or security.
+ - Archived control-plane research remains recoverable but outside the active build, MCP tools, tests, and launch promise.
+criteria:
+ - description: One command detects representative project types and installs a safe proof workflow
+ probe:
+ kind: command
+ run: npx vitest run tests/project-profile.test.ts -t "cross-project proof setup" --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: >-
+ The initializer recognized Node.js, Python, Rust, Go, and generic Git fixtures and generated
+ repository-owned outcomes plus a read-only GitHub workflow.
+ whyItMatters: Adoption starts with a useful first Factfile in minutes, not a new hosted platform or agent migration.
+ code:
+ - path: src/project-profile.ts
+ purpose: Detects project conventions and generates starter checks and GitHub setup.
+ - path: tests/project-profile.test.ts
+ purpose: Exercises every supported project profile and the one-command installation contract.
+ artifacts: []
+ - description: Pull-request proof covers committed base-to-head changes and fails closed on scope drift
+ probe:
+ kind: command
+ run: npx vitest run tests/project-profile.test.ts -t "Git-native contribution history" --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: >-
+ Tests create real Git histories, bind contributions to the pull-request base, include committed
+ changes, and reject paths outside a declared review boundary.
+ whyItMatters: A clean CI worktree must not produce an empty or misleading change scope.
+ code:
+ - path: src/contribution.ts
+ purpose: Captures committed and uncommitted source, exact digests, scope boundaries, and stale-proof rules.
+ - path: tests/project-profile.test.ts
+ purpose: Verifies real base-to-head Git behavior and scope failure semantics.
+ artifacts: []
+ - description: The Factfile separates blockers from contextual next directions and proof
+ probe:
+ kind: command
+ run: npx vitest run tests/contribution.test.ts --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The reviewer sees genuine blockers separately from evidence-grounded next directions, their outcome effects, deeper context, proof, and ordered review path.
+ whyItMatters: Developers need to orient in seconds and then answer every deeper question without trusting an agent summary or a green badge.
+ code:
+ - path: src/contribution.ts
+ purpose: Renders compact GitHub Markdown and the complete human-readable HTML Factfile from one canonical snapshot.
+ artifacts:
+ - kind: screenshot
+ path: docs/artifacts/keyoku-factfile-current.png
+ label: Live proof session at a glance
+ caption: Desktop capture showing an outcome-complete Factfile with agent-prepared next directions and their evidence-bounded consequences.
+ annotations:
+ - label: Completion becomes a choice, not an empty prompt
+ detail: The agent recommends a bounded next move while preserving alternative and custom directions.
+ x: 24
+ y: 29
+ - label: Consequences are visible before steering
+ detail: Each path states how the outcome changes; evidence basis and tradeoffs remain one disclosure away.
+ x: 63
+ y: 55
+ - kind: video
+ path: docs/artifacts/keyoku-live-decision.webm
+ label: Human decision reaches an agent
+ caption: Short real-browser recording of a recommended choice becoming a durable queued instruction.
+ annotations:
+ - label: Review the decision context
+ detail: The human sees intent, blocker, ownership reason, and no-response consequence.
+ atMs: 0
+ - label: Send the bounded choice
+ detail: The live session appends a decision and an instruction; it does not rewrite prior proof.
+ atMs: 3000
+ - description: The two-way protocol is durable, provider-neutral, and token-scoped
+ probe:
+ kind: command
+ run: npx vitest run tests/proof-session.test.ts --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Real integration tests report agent work, propose context-rich next directions, request and resolve a human decision, deliver and acknowledge instructions, reuse one active contribution, and reject an untokened live-session request.
+ whyItMatters: The interface must change agent behavior through a durable protocol, not merely display a dashboard that drifts from execution.
+ code:
+ - path: src/proof-session.ts
+ purpose: Defines the append-only provider-neutral work, direction, decision, instruction, acknowledgement, and presence protocol.
+ - path: src/session-server.ts
+ purpose: Serves the loopback-only token-scoped interactive Factfile and converts human actions into protocol events.
+ - path: src/server.ts
+ purpose: Exposes the protocol to Codex, Claude Code, OpenHands, custom agents, and other MCP clients.
+ - path: tests/proof-session.test.ts
+ purpose: Proves the end-to-end human choice to agent instruction path and session security boundary.
+ artifacts: []
+ - description: Outcome definitions have inspectable repository-owned version history
+ probe:
+ kind: command
+ run: npx vitest run tests/project-profile.test.ts -t "binds committed PR changes" --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: A committed outcome revision is recovered through Git and shown with its commit, author, time, subject, and revision number.
+ whyItMatters: Teams can audit how definition of done evolved without trusting a second Keyoku database.
+ code:
+ - path: src/contribution.ts
+ purpose: Reads the canonical outcome history directly from Git.
+ - path: src/index.ts
+ purpose: Exposes keyoku outcome history as a human-readable command.
+ artifacts: []
+ - description: Abandoned control-plane surfaces are outside the active product while reusable proof primitives remain
+ probe:
+ kind: command
+ run: >-
+ sh -c "test -f archive/experimental-control-plane/README.md &&
+ test ! -f src/project-state.ts && test ! -f src/presentation.ts &&
+ ! grep -R -E 'project_orient|intervention_create|agent_session_heartbeat|view_publish' src --include='*.ts'"
+ timeoutMs: 30000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The live control-plane prototype is recoverably archived and no longer compiled, registered over MCP, tested, or promised as V1.
+ whyItMatters: A narrow product earns adoption faster and avoids duplicating coding harnesses and GitHub.
+ code:
+ - path: archive/experimental-control-plane/README.md
+ purpose: Records the archived scope, reason, and rule for selective recovery.
+ - path: src/server.ts
+ purpose: Keeps the active MCP surface focused on outcomes, evidence, review, and architecture.
+ artifacts: []
+ - description: Verification runs against a sealed checkout and rejects source mutation
+ probe:
+ kind: command
+ run: npx vitest run tests/source-capsule.test.ts --cache=false --configLoader runner
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Capsule tests bind tracked and nonignored source bytes, reject writes and mutate-restore attacks, detect concurrent original-source changes, and clean every disposable checkout.
+ whyItMatters: A passing repository probe cannot silently rewrite the source it is being asked to prove.
+ code:
+ - path: src/source-capsule.ts
+ purpose: Captures exact source, materializes a sealed checkout, monitors mutations, and verifies freshness.
+ - path: tests/source-capsule.test.ts
+ purpose: Exercises writes, deletes, mode changes, symlink attacks, mutate-restore attempts, timeouts, and concurrent source changes.
+ artifacts: []
+ - description: The active TypeScript implementation remains internally consistent
+ probe:
+ kind: command
+ run: npm run typecheck
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Strict TypeScript checking accepts the project-profile, Git history, scope, renderer, CLI, and gate contracts together.
+ whyItMatters: The generated workflow and canonical Factfile shapes must agree across the public API and CLI.
+ code:
+ - path: tsconfig.json
+ purpose: Defines the strict static consistency rules.
+ artifacts: []
+humanCriteria:
+ - id: five-minute-value
+ description: A developer can reach a useful first Factfile in under five minutes and knows what to customize
+ guidance: Start from the README in a representative repository and note every point requiring prior Keyoku knowledge.
+ - id: github-review-clarity
+ description: The first Factfile screen makes the next decision, supported claims, unknowns, and review path understandable in under ten seconds
+ guidance: Look only at the first viewport, then explain what is established, what is not established, and what action belongs to the reviewer.
+ - id: complete-drill-down
+ description: A developer can drill from any supported claim to useful artifacts, relevant code, a reproduction instruction, and exact verifier details
+ guidance: Open one claim without reading the outcome YAML and reproduce its observation; confirm missing evidence is labeled rather than implied.
+ - id: focused-launch
+ description: The public product reads as a focused proof layer rather than a generic agent control plane, memory system, or certification claim
+ guidance: Inspect the README, CLI help, package description, MCP instructions, and archived surfaces for conflicting promises.
+createdAt: 2026-08-15T18:30:00Z
+updatedAt: 2026-08-27T03:44:20Z
diff --git a/.keyoku/outcomes/review-ready-change.yaml b/.keyoku/outcomes/review-ready-change.yaml
new file mode 100644
index 0000000..24d11df
--- /dev/null
+++ b/.keyoku/outcomes/review-ready-change.yaml
@@ -0,0 +1,69 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: review-ready-change
+revision: 1
+title: A reviewer can confidently decide this change
+objective: The proposed Keyoku change is understandable, bounded, and supported by the repository's
+ own executable checks.
+owner:
+ kind: human
+ id: repository-owner
+ name: Repository owner
+ role: accountable owner
+constraints:
+ - One contribution represents one coherent reviewer outcome; split unrelated work.
+ - Passing checks support only their declared claims and do not replace human review.
+ - Evidence must describe the exact Git revision under review.
+criteria:
+ - description: The project test suite passes
+ probe:
+ kind: command
+ run: npm run test
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The project test suite passes at the exact revision shown in this Factfile.
+ whyItMatters: The behavior covered by the repository's tests still works at this exact revision.
+ code: []
+ artifacts: []
+ - description: Static type checks pass
+ probe:
+ kind: command
+ run: npm run typecheck
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: Static type checks pass at the exact revision shown in this Factfile.
+ whyItMatters: The change remains consistent with the project's declared type contracts.
+ code: []
+ artifacts: []
+ - description: The production build succeeds
+ probe:
+ kind: command
+ run: npm run build
+ timeoutMs: 300000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The production build succeeds at the exact revision shown in this Factfile.
+ whyItMatters: The repository can produce its declared build artifact from this revision.
+ code: []
+ artifacts: []
+humanCriteria:
+ - id: coherent-review-unit
+ description: This contribution is one coherent outcome and the implementation is understandable
+ enough to own
+ guidance: Read the reviewer brief, inspect the changed areas and evidence, then review the diff
+ where judgment is still required.
+createdAt: 2026-08-19T02:22:31.503Z
+updatedAt: 2026-08-19T02:22:31.503Z
diff --git a/.keyoku/policy.yaml b/.keyoku/policy.yaml
new file mode 100644
index 0000000..06e18af
--- /dev/null
+++ b/.keyoku/policy.yaml
@@ -0,0 +1,16 @@
+schemaVersion: keyoku.dev/policy/v1alpha1
+projectId: keyoku
+distribution:
+ license: MIT
+ publicProjects: free
+ privateProjects: free
+ selfHosted: true
+proof:
+ failClosed: true
+ exactSnapshotBinding: true
+ humanAcceptanceRequired: true
+ generatedEvidenceCommittedByDefault: false
+privacy:
+ publishRawAgentTranscripts: false
+ publishSecrets: false
+ publishEvidenceSummary: true
diff --git a/.keyoku/project.yaml b/.keyoku/project.yaml
new file mode 100644
index 0000000..247d95b
--- /dev/null
+++ b/.keyoku/project.yaml
@@ -0,0 +1,8 @@
+schemaVersion: keyoku.dev/project/v1alpha1
+id: keyoku
+name: Keyoku
+summary: Free, provider-neutral continuous proof for human-owned software contributions.
+repository: https://github.com/Keyoku-ai/keyoku.git
+defaultBranch: main
+createdAt: 2026-08-09T00:06:21Z
+updatedAt: 2026-08-09T00:06:21Z
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 402d193..9ed9890 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,159 @@
# Changelog
+## 3.0.0-alpha.1 — 2026-08-25
+
+Unreleased candidate for Keyoku's narrow, local, Git-native proof and attention
+layer around coding agents. This entry does not claim that the package has been
+published or that npm `latest` has moved from v2.
+
+### Added
+
+- A durable provider-neutral session protocol for agent work, structured human decisions, queued instructions, acknowledgements, and leased presence.
+- A token-scoped loopback UI with **Agent work**, **Needs you**, **Review first**, and claim-by-claim **Proof** surfaces.
+- Active contribution reuse per branch and outcome, plus content binding for the exact outcome contract.
+- Annotated screenshot and timestamped video evidence in portable Factfiles.
+- `keyoku proof serve`, five bounded MCP proof-session tools, seven bounded Pulse tools, one side-effect-free assurance evaluator, and a Marketplace-compatible composite GitHub Action.
+- Exact-digest Factfile verification plus `keyoku factfile inspect|verify|assess|publish`.
+- Harness-neutral Pulse lifecycle events, verified checkpoints, deterministic dispatch planning, and stakeholder/developer/timeline/email/text/JSON projections.
+- An optional neutral `EvidenceProvider` and WorkEvent bridge; callers retain runtime orchestration and assurance-profile policy.
+- A SHA-256 source capsule for command-backed Factfile criteria, materialized into one fresh disposable Git checkout per criterion with dirty/untracked bytes, paths, modes, symlinks, mutation detection, original-source race rejection, and validated cleanup.
+
+### Changed
+
+- Factfiles now present claim → observation → meaning → limits → reproduction → relevant code and artifacts; raw assertions remain audit detail.
+- The launch promise is intentionally narrow: Keyoku coordinates proof and human attention without replacing GitHub, coding harnesses, or project-management systems.
+- The v3 package entrypoint now exposes only `proof`, `factfile`, `pulse`, `serve`, `doctor`, `version`, and help. Goals, workflows, connectors, activity recording, memory, execution, and behavior-iteration commands remain compatibility source and are excluded from the v3 archive.
+- Synthetic and adapter-attested checkpoints remain visibly attested and nondispatchable. Local promotion rechecks Factfile project/outcome/source identity, rejects symlinked or signature-mismatched media, and public adapter ingestion cannot self-claim local verification.
+- Repository identity uses fail-closed Git calls and NUL-delimited paths; gates bracket probes, evidence resolution, and persistence with source captures so checkout mutation fails closed.
+- Repository commands are now explicitly read-only proof observations. Writes, additions, deletions, mode changes, and mutate-restore behavior reject proof; this evidence isolation does not claim OS sandboxing of arbitrary repository code.
+
## Unreleased
+> The entries below record work on the v2 compatibility lineage. They are not
+> v3 public commands or MCP tools unless a later release explicitly promotes
+> them into the checked `docs/PUBLIC-SURFACE.md` inventory.
+
+### Added
+- **Compatibility behavior iteration: `keyoku iterate` plus four MCP tools.** A provider-neutral,
+ bounded prove → repair → re-prove protocol now turns failed repository-owned
+ claims into deterministic agent instructions and re-evaluates only at an
+ idempotent checkpoint. Each round records the exact Git/worktree identity,
+ Factfile digest, passing/failing/regressed claim indexes, declared human-review
+ state, explicitly sourced token/cost usage, and stop reason in a hash-chained
+ append-only ledger. The controller stops on success, human judgment, failed
+ human review, no source progress, or configured round/time/token/cost limits.
+ It deliberately does not run an agent, infer billing, fill human decisions,
+ accept a contribution, push, or deploy. See `docs/ITERATION.md`.
+
+- **Demo evidence: `keyoku demo `.** A generic, project-agnostic
+ "record -> watch -> gate" workflow that makes a recorded product demo
+ first-class Keyoku evidence (the existing `EvidencePresentationSchema`
+ `artifacts` already supported `kind: "screenshot"/"video"`; this adds the
+ workflow that actually produces and validates them). `keyoku demo init`
+ writes a commented `.keyoku/demo.yaml` template (won't overwrite an
+ existing one) plus a ready-to-paste outcome criterion snippet. `keyoku demo
+ record` reads/validates that config, launches Chromium via Playwright
+ resolved from the *target* project (not a keyoku dependency — clear error
+ if `playwright` isn't installed there), walks each declared "stop"
+ (optional auth once, then goto -> actions -> settle -> screenshot), and
+ writes `.keyoku/demo/frames/*.jpeg` + `.keyoku/demo/manifest.json`. `keyoku
+ demo watch [--assert]` composes a prompt from the manifest's frames and
+ per-stop `expect` assertions, spawns `claude -p ... --permission-mode
+ acceptEdits` to review the frames and run a UI/UX audit, validates the
+ resulting `.keyoku/demo/verdict.json` against a zod contract, and with
+ `--assert` exits 0 only when `overall.verdict === "pass"` AND the verdict
+ is newer than the manifest — usable directly as an outcome criterion probe
+ (`keyoku demo record && keyoku demo watch --assert`) in any project. See
+ `docs/demo-evidence.md`.
+
+- **ADR-35: `Goal.project`/`Goal.cwd` — the keyoku side of belay's cross-project
+ scoping fix.** belay's loop portfolio/proposals now scope by project to stop
+ goals bleeding across unrelated repos sharing one `~/.keyoku`; that read a
+ `project`/`cwd` off the goal row, but the goal record had no such field —
+ this adds it.
+ - **What cwd context is actually available to an MCP tool handler** (the
+ key finding): MCP does not hand a tool call the client's cwd — there is
+ no protocol-level "caller's cwd" param. The only two reliable signals are
+ (a) an explicit `cwd` argument the calling agent chooses to pass, and (b)
+ `process.cwd()` of the long-lived stdio server process itself, fixed at
+ the moment Claude Code spawned it (typically the project dir the session
+ started in). `goal_focus` already leaned on exactly this — `cwd` optional,
+ defaulting to `process.cwd()` — so that's the established, trusted
+ convention this change follows for `goal_create` too, rather than
+ inventing a new mechanism.
+ - **`goal_create` gains an optional `cwd` param**, defaulting to the
+ server's `process.cwd()` when omitted — so every newly created goal is
+ stamped going forward, not just focused ones. Stored as two fields:
+ `Goal.cwd` (the raw dir) and `Goal.project` (the git repo root of that
+ dir, or the dir itself outside a repo — `never-throw`, via
+ `projectForCwd()` in `engine.ts`) — repo-root normalization means a goal
+ created from any subdir of a monorepo checkout lands on the same
+ `project` value.
+ - **`goal_focus` backfills `project`/`cwd`** on a goal that doesn't have
+ them yet, from the focus `cwd` (itself already optional-with-a-
+ `process.cwd()`-default on that tool). **First stamp wins** — focusing an
+ already-stamped goal from a different directory never reassigns it, so a
+ shared/portfolio goal can't get bounced between projects by whoever
+ focuses it next.
+ - **Surfaced** in `goal_get` (full goal object) and `goal_list`/`goal_create`
+ responses (`goalSummary`, `project` only, when set) — as well as directly
+ in `goals.json`, which is how belay itself reads it.
+ - **Backward compat, stated plainly:** the ~97 goals that existed before
+ this field shipped have neither `project` nor `cwd` and are **NOT**
+ retroactively scoped — there is no backfill migration, because inferring
+ a project from a goal's free-text objective/activity would produce false
+ positives that are worse than "unscoped." An old goal becomes scopeable
+ only once it is re-focused (`goal_focus`) from a real cwd, or recreated.
+ belay-side scoping logic must treat an absent `project`/`cwd` as
+ "unknown," not "global."
+- **B2: edit a goal's criteria in place.** `goal_update` gains `addCriteria` /
+ `removeCriteriaIds` / `editCriteria`, so a wrong or incomplete criterion no
+ longer forces creating a whole new goal (which was fragmenting the loop
+ portfolio into duplicate `-v2`-style goals with their own, disconnected
+ learned workflow). `addCriteria` appends new criteria (ids continue the
+ goal's `c` sequence, never colliding with survivors after a removal);
+ `removeCriteriaIds` drops criteria by id; `editCriteria` patches an
+ existing criterion's `description`/`probe`/`assert` by id — fields left
+ out of the patch are preserved. Criteria not referenced by any of the three
+ pass through completely unchanged (verified by identity-equality in tests,
+ not just value equality). Backward compatible: a `goal_update` call with
+ none of the three new params behaves byte-for-byte as before (same patch
+ application, same response shape — the redacted `criteria` array is only
+ added to the response when a criteria edit actually happened).
+ - **Re-validated on every edit**, through the same gate `goal_create` uses:
+ at least one criterion must remain, and any `mcp` criterion (added or
+ edited-in) must reference a connector that's actually registered. A
+ rejected edit never partially applies — the goal's criteria are left
+ exactly as they were.
+ - **Converged-goal guard, safe default (no force flag):** editing criteria
+ on a `converged` goal reopens it — to `active`, or to `blocked` if its
+ iteration budget is already exhausted — mirroring the existing
+ drift-detection auto-reactivation in `assess()`. Rationale: a
+ `converged` status is a proof that criteria held; changing the criteria
+ invalidates that proof, so leaving the status untouched would be a
+ second silent-false-convergence hole right next to the one closed in
+ 2.18.0. No flag is needed to opt into the safe behavior — the harness
+ never leaves a goal claiming a convergence it hasn't re-verified.
+ - **The edit lands in the goal's trace/history** (visible via `goal_get`)
+ as a new `source:"system"` `ActionRecord` — distinct from `"recorded"`
+ (an explicit `goal_record` corrective action) and `"activity"` (live
+ capture). It does NOT spend the corrective-action iteration budget, and
+ it is excluded from workflow-step promotion and the `totalActions` stat
+ — a criteria edit is bookkeeping about the goal's definition of done, not
+ a reusable action toward it, so it must not pollute a learned workflow's
+ steps.
+ - Server instructions (`PROTOCOL`) and `goal_update`'s tool description
+ updated — the old "criteria are IMMUTABLE after goal_create... make a
+ NEW goal to change them" guidance is now actively wrong and has been
+ replaced with guidance to refine in place instead.
+
+### Changed
+- **Command probe `timeoutMs` cap raised from 5 minutes to 15 minutes
+ (300,000ms -> 900,000ms)** in `CommandProbeSchema`/`HttpProbeSchema`
+ (`src/types.ts`) — real frontend production builds (and the new demo
+ record/watch pipeline) routinely exceed 5 minutes, and the old cap made
+ those outcome checks structurally unable to declare an honest timeout.
+
## 2.18.0 — 2026-07-02
Security + correctness hardening. A full-codebase adversarial validation
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c980c24..04559b8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,8 @@
# Contributing to Keyoku
-Thanks for helping make Keyoku better.
+Thanks for helping make agent-assisted software easier to review. Keyoku is a
+generic, agent-neutral proof harness: contributors should not need a particular
+model, coding agent, hosted account, or memory service.
## Development setup
@@ -8,29 +10,58 @@ Thanks for helping make Keyoku better.
git clone https://github.com/Keyoku-ai/keyoku.git
cd keyoku
npm install
-npm run build # tsup → dist/
-npm test # builds, then runs the full vitest suite (incl. MCP e2e)
-npm run typecheck # tsc --noEmit
+npm run typecheck
+npm test
```
-Node 20+ required. State during manual testing goes to `$KEYOKU_HOME` — set it
-to a temp dir (`KEYOKU_HOME=/tmp/keyoku-dev node dist/index.js serve`) so you
-don't pollute your real `~/.keyoku`.
+Node 20+ is required. Use `npm run preflight` before opening a pull request.
+
+## Prove the outcome you changed
+
+The repository dogfoods Keyoku. Pick the smallest outcome that describes your
+change, customize it when the definition of done changes, and generate a fresh
+Factfile for the exact revision under review:
+
+```bash
+npm run build
+node dist/index.js outcome list
+node dist/index.js proof run
+```
+
+If no existing outcome fits, create a repository-owned contract with
+`node dist/index.js proof init`, then edit the generated YAML. A strong outcome:
+
+- describes one reviewer-sized result rather than an agent task;
+- pairs every automated claim with why the evidence matters;
+- asks a human only for decisions that cannot be reduced to a command;
+- declares a path scope when the intended review boundary is known; and
+- never treats an agent's confidence or a zero exit code as sufficient explanation.
+
+Screenshots, traces, reports, videos, and logs may support a claim. Keep them
+small, redact private data, and attach only evidence that teaches a reviewer
+something about the result.
## Project layout
-- `src/server.ts` — MCP tool surface (the API)
-- `src/activity.ts` — pattern detection over the activity stream
-- `src/refine.ts` — optional SLM refinement of suggestions
-- `src/executor.ts` — bash / mcp_call step execution
-- `src/store.ts` — JSON-file persistence under `~/.keyoku`
-- `src/index.ts` — CLI (`serve`, `init`, `record`, …)
-- `tests/` — unit + end-to-end tests (e2e drives a real MCP stdio session)
+- `src/contribution.ts` — outcome evaluation, revision binding, and Factfile renderers
+- `src/architecture.ts` — deterministic architecture projection
+- `src/project-profile.ts` — one-command project and GitHub workflow setup
+- `src/index.ts` — CLI surface
+- `src/server.ts` — agent-neutral MCP tools
+- `.keyoku/` — this repository's versioned outcomes and project policy
+- `docs/FACTFILE-STANDARD.md` — portable receipt contract
+- `archive/` — retired implementations, excluded from the launch surface
+- `tests/` — unit and end-to-end verification
## Pull requests
-- Every behavior change needs a test. CI (typecheck + full suite) must pass.
-- Keep PRs focused; explain *why* in the description, not just what.
-- New MCP tools must be added to the tool-surface snapshot in `tests/e2e.test.ts`.
+- Keep one coherent outcome per PR; use stacked PRs for independent outcomes.
+- Add tests for behavior changes and keep typecheck plus the full suite green.
+- Include the generated GitHub summary or Factfile artifact for reviewer context.
+- Use native GitHub review for the accountable decision: approve the exact
+ revision, or request changes with a concrete next instruction.
+- Update MCP tool-surface assertions in `tests/e2e.test.ts` when tools change.
+- Never commit credentials, private prompts, customer data, local runtime state,
+ or internal product/market working papers.
-By contributing you agree your contributions are licensed under the MIT license.
+By contributing, you agree your contributions are licensed under the MIT license.
diff --git a/README.md b/README.md
index 09c1dcf..71eaa4d 100644
--- a/README.md
+++ b/README.md
@@ -3,180 +3,282 @@
-
+
-
- The harness with muscle memory.
- Keyoku watches what you do in Claude Code, Cursor, or Codex, learns your patterns, and turns them into one-command workflows — automatically.
-
+
Proof your coding agent's work—not its confidence.
+ One repository-owned outcome. Exact-revision evidence. A clear human decision.
-
- [](https://www.npmjs.com/package/keyoku)
+ [](https://www.npmjs.com/package/keyoku)
[](https://github.com/Keyoku-ai/keyoku/actions/workflows/ci.yml)
- [](https://www.typescriptlang.org/)
- [](LICENSE)
+ [](LICENSE)
-
+Keyoku is a free, local-first proof session between humans and coding agents. It turns a repository-owned definition of done into a live working view and a shareable **Factfile**: meaningful evidence, agent provenance, explicit limits, and an exact Git scope.
+
+**Factfile proves one checkpoint. Keyoku Pulse carries trusted progress across checkpoints.** Command claims run from one exact, content-addressed source capsule in fresh disposable checkouts; any source write rejects the proof. Pulse accepts typed events from any agent harness, reports only exact-source verified checkpoints, and renders founder, developer, timeline, email-safe, text, and JSON views from one digest. It never silently sends a message.
+
+**Keyoku is an optional assurance adapter, not an agent protocol or control plane.** A caller may submit a neutral, content-digested evidence envelope and receive a deterministic accepted, rejected, stale, or human-review-required result. The caller chooses whether to use no assurance, basic assurance, or Keyoku high assurance; the neutral work contract does not require Keyoku. See the [adapter contract](docs/ASSURANCE-ADAPTER.md).
+
+It works with Codex, Claude Code, Copilot, Cursor, OpenHands, custom agents, CI, or no agent at all. Keyoku does not run your agent and does not ask you to move source code off GitHub.
+
+> GitHub shows the diff. Keyoku shows whether the intended outcome is supported—and where a human still has to decide.
+
+## Try the v3 source alpha
+
+```bash
+git clone https://github.com/Keyoku-ai/keyoku.git
+cd keyoku
+npm ci
+npm link
+
+# Run the complete evidence-gap → human-review → stale-proof demo
+keyoku proof demo --open
-## Get Started
+cd /path/to/your-project
+keyoku proof init
+```
+
+`keyoku proof demo` creates a disposable Git repository and uses the real Keyoku
+pipeline. It first records a failing evidence state, fixes the sample defect,
+produces an exact-revision Factfile, and proves that review is rejected after
+the source changes. It needs no account, model key, hosted service, or prepared
+video. Pass `--dir ` if you want a predictable location to
+inspect afterward.
+
+The npm `latest` tag still points to the v2 muscle-memory product during this
+alpha cutover. The generated GitHub workflow pins `keyoku@3.0.0-alpha.1` and is
+staged—not runnable from npm—until that exact candidate is separately approved
+and published to the `next` dist-tag. Local source evaluation works now.
-Install it once, then wire it up:
+Customize the outcome without learning the full YAML schema:
```bash
-npm install -g keyoku
-keyoku init
+keyoku proof customize review-ready-change \
+ --objective "A user can complete checkout without losing their cart"
+
+keyoku proof customize review-ready-change \
+ --check "npm run test:checkout" \
+ --claim "Checkout completes end to end" \
+ --why "This is the behavior being shipped"
```
-> A global install keeps keyoku on a durable path. Running `npx keyoku init`
-> from the throwaway npx cache is refused — npm can evict that directory and
-> break the hooks — so install globally first.
+Run `keyoku proof customize review-ready-change` with no edit flags to see the current claims, human decisions, and copyable customization recipes. Outcome YAML remains portable and Git-owned; each meaningful customization increments its revision.
-The init command wires everything automatically:
+Keyoku detects Node.js, Python, Rust, Go, or a generic Git repository and creates:
-1. **Registers the MCP server** — via `claude mcp add --scope user`, so Claude Code connects on next launch
-2. **Installs the hooks** — activity recording (every Bash/Edit/Write/Read/MCP call), a session-start brief, and prompt-time practice injection
-3. **Wires Codex too** — when `~/.codex` exists, the MCP server lands in `config.toml` automatically (same tools, same workflows)
-4. **Stays local** — no cloud, no telemetry; state lives in `~/.keyoku` with the same file permissions as `~/.aws`. `keyoku pause` stops everything instantly.
+```text
+.keyoku/
+├── project.yaml
+└── outcomes/
+ └── review-ready-change.yaml # repository-owned definition of done
+.github/workflows/
+└── keyoku-proof.yml # read-only PR proof check
+```
-Restart Claude Code and keyoku is live. Then skip the cold start entirely:
+Review the generated outcome contract, replace starter checks with behavior that matters to your project, and run it locally:
```bash
-keyoku import # backfill months of history from your Claude Code transcripts
+keyoku proof run review-ready-change
```
-Now ask your agent to run `workflow_suggest` — keyoku mines your real history immediately instead of waiting days for new activity. Approved workflows appear as native slash commands (MCP prompts), and `keyoku export ` bakes one into your repo as a Claude Code skill your whole team inherits.
+The command prints a contribution id. Open its live session while an agent works:
-## How It Works
+```bash
+keyoku proof serve
+```
-**Without Keyoku:** you describe the same multi-step process to your agent every session.
+The token-scoped loopback link opens automatically. It keeps four surfaces deliberately separate:
-**With Keyoku:** you approve a workflow once, then run it with one command. The agent never has to rediscover it.
+- **Agent work** — reported task status; useful for coordination, never treated as proof.
+- **Needs you** — only decisions that genuinely block safe progress, with options, recommendation, and the cost of no response.
+- **Direct** — optional, context-aware next directions with their expected outcome effect, deeper context, tradeoffs, and a custom path.
+- **Review first** — deterministic risk and attention signals, not another model verdict.
+- **Proof** — claim → observation → meaning → limits → reproduction → relevant code and content-bound artifacts.
-### 1. Activity tracing — automatic
+A choice in **Needs you** or **Direct** writes a durable instruction. Any MCP-connected agent can receive and acknowledge it; if no agent is online, it stays queued. “Copy instruction” remains the universal fallback for any harness. The portable artifact is dark-first with a local light/dark toggle; the chosen appearance never changes canonical proof.
-Every tool call your agent makes is recorded as a lightweight `ActivityEvent` — tool name, summary, extracted entities. Purely local.
+On a pull request, GitHub gets a reviewer-first Check summary and a downloadable Factfile artifact. The job executes with `contents: read`; untrusted PR code never receives a write token merely so Keyoku can post a comment.
-### 2. Pattern detection — heuristics for recall, a model for precision
+The repository also contains a Marketplace-compatible composite action for the
+future stable `v3` tag. During alpha, use `proof init`; its generated workflow
+pins the source alpha and detects each project's dependencies safely. No `v3`
+action tag is claimed until that release exists.
-`workflow_suggest` mines recurring sequences from your recent activity (non-overlapping counting, noise suppression, longest-chain collapsing — no model required). If an SLM key is configured (`GEMINI_API_KEY` or `ANTHROPIC_API_KEY`), the model then refines the drafts: filters coincidences, names workflows properly, and parameterizes run-specific values with `{{placeholders}}`.
+## What a reviewer sees
-### 2b. Muscle memory — converged goals become reusable workflows
+The Factfile answers these questions in order:
-A goal that converges (`goal_assess` reports all criteria met) promotes its action trace into a learned workflow. Next time you start a *similar* goal, keyoku surfaces what worked before — so the agent never rediscovers it.
+1. What are agents doing, and which are currently connected?
+2. Does anything genuinely need my decision?
+3. Where should I review first?
+4. Which declared claims are supported by evidence?
+5. Which files and code areas changed?
+6. Which person, agent, harness, and model contributed?
+7. Which exact base, head, worktree, and Factfile digests does this cover?
-- **Capture happens three ways:** explicit `goal_record`, live `goal_focus` (real actions stream into the goal's trace as you work), or **activity backfill** (if you just did the work without recording, keyoku lifts the steps from the activity log). Already have hollow workflows from older runs? `keyoku backfill` repopulates them.
-- **Reuse needs no API key.** keyoku is driven by a frontier coding agent, so *the agent is the judge of relevance.* `goal_assess` returns `candidateWorkflows` and the agent picks the ones that genuinely apply — which matches verbose, differently-worded goals that token-overlap never could. A lite model is an optional accelerator for headless runs (`keyoku watch`/cron), not a requirement.
-- **It self-prunes.** Suggestions rank by `similarity × precision`, where precision is learned from whether a workflow's steps actually recur — so word-matching-but-never-useful workflows sink.
-- **Negative memory too.** Approaches that *failed* on the way to convergence are captured as pitfalls and surfaced as "avoid (failed before): …" on similar goals.
-- **Refine raw into clean.** `keyoku refine ` turns noisy captured steps into a tight, `{{parameterized}}` template ready to run.
+Raw observations are collapsed audit detail. An exit code is never presented as the explanation. Visible behavior can attach screenshots; runtime claims can attach tests or traces; security claims can attach scanner output; architecture claims can attach code tours and the generated SVG projection.
-### 3. Approval — you are the trust boundary
+“Review this first” is deterministic—not another model verdict. Failed claims, declared scope violations, security/data/workflow/dependency-sensitive paths, broad changes, and pending human decisions are ordered with their reasons and source paths.
-```
-workflow_approve { slug: "deploy-staging", name: "Deploy staging", steps: [...] }
-```
+## One outcome is one review unit
-Review the draft like you'd review a shell script, then approve. Templates live in `~/.keyoku/templates.json`.
+Keyoku does not encourage one enormous PR. A contribution may contain several commits, but it should deliver one coherent reviewer outcome. Unrelated outcomes should become separate or stacked PRs.
-### 4. Execution — bash runs, judgment pauses
+An optional path boundary can fail closed when a contribution strays outside its declared scope:
-```
-workflow_execute { slug: "deploy-staging" }
+```yaml
+scope:
+ include:
+ - src/auth/**
+ - tests/auth/**
+ exclude:
+ - docs/**
+ maxChangedFiles: 30
```
-- **bash** steps run directly (per-step `cwd`, timeouts, output captured)
-- **agent_prompt** steps pause and hand the step to your coding agent, which resumes with `execution_complete`
-- **human_review** steps wait for your explicit sign-off
+Path checks cannot prove semantic coherence, so the generated contract also keeps that question as an explicit human judgment.
-Every execution persists step-by-step — crash-safe, fully inspectable via `execution_list`.
+Graphite and GitHub can own PR stacking. Keyoku owns the outcome and its proof.
-## MCP Tools
+## Outcome history belongs in Git
-| Tool | What it does |
-|---|---|
-| `activity_record` / `activity_list` | Log and browse the observation stream |
-| `workflow_suggest` | Mine patterns → model-refined draft workflows |
-| `workflow_capture` | "Save what I just did" — last N session actions become a draft |
-| `workflow_approve` / `workflow_update` | Save or edit templates (slash commands stay current) |
-| `workflow_template_list` / `workflow_template_delete` | Manage the catalog |
-| `workflow_execute` | Run a template (`params` fill `{{placeholders}}`) |
-| `execution_complete` / `execution_cancel` / `execution_list` | Resume, stop, browse runs |
-| `knowledge_submit` / `knowledge_query` | The context layer — research, conventions, practice |
-| `goal_create` / `goal_assess` / … | Goals with machine-checkable success criteria |
-| `goal_focus` / `goal_unfocus` | Live capture — record real actions into a goal's trace as you work |
-| `connector_add` / `connector_call` / … | Plug in external MCP servers (GitHub, GCP, …) with autonomy gating |
-
-## CLI
+Outcome contracts are normal versioned repository files. Change the meaning or acceptance criteria, increment `revision`, and commit the file. Anyone can inspect its canonical history without a Keyoku account:
+```bash
+git log -- .keyoku/outcomes/review-ready-change.yaml
```
-keyoku [serve] Start the MCP server on stdio (Claude Code does this automatically)
-keyoku init Wire up the hook + MCP registration
-keyoku import Backfill activity from Claude Code + Codex transcripts (kills the cold start)
-keyoku export Bake a workflow into ./.claude/skills — or AGENTS.md with --agents-md
-keyoku pause | resume Privacy switch: stop/start all recording and injection
-keyoku doctor Verify hooks, MCP registrations, engine, and activity health
-keyoku inspect Show exactly what's stored in ~/.keyoku (--secrets scans for leaks)
-keyoku status Show goals, templates, connectors
-keyoku learn Mine patterns from the activity log
-keyoku backfill Repopulate hollow learned workflows from the activity log (--dry-run)
-keyoku refine Turn a workflow's raw steps into a clean, parameterized template (--apply)
-keyoku focus Live-capture actions into a goal's trace (--clear to stop; no arg to show)
-keyoku assess One-shot convergence check
-keyoku watch Re-assess on an interval
-keyoku approvals Approve/deny gated connector calls
-keyoku audit [n] Show the audit trail
+
+Each contribution also keeps append-only coordination events and Factfile snapshots:
+
+```text
+.keyoku/contributions//
+├── manifest.yaml
+├── events.jsonl # work, decisions, instructions, acknowledgements, presence
+├── reviews.jsonl # human judgments and exact-snapshot acceptance
+├── snapshots/.json
+├── factfile.json # canonical machine record
+├── factfile.github.md # concise GitHub reviewer surface
+├── factfile.md # portable detailed Markdown
+└── factfile.html # human-readable evidence and code tour
```
-## Architecture
+Projects can keep snapshots local, upload them as CI artifacts, or commit accepted receipts. Generating a receipt does not change its own source digest.
+
+## The state model tells the truth
+| State | Meaning |
+|---|---|
+| `evidence_gaps` | A declared machine claim failed, timed out, or could not be observed |
+| `human_review_required` | Machine evidence passed; a named human question remains |
+| `review_blocked` | A required human judgment failed |
+| `ready_for_review` | Declared automated and required human criteria passed |
+| `accepted` | An identified human accepted that exact snapshot |
+
+“Passing” means only that the repository's declared checks passed for the shown revision. It never means universally secure, correct, maintainable, or fit for purpose. Any source change makes the Factfile stale and requires re-evaluation.
+
+## Example outcome
+
+```yaml
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: working-release
+revision: 1
+title: The release can be reviewed and shipped
+objective: A maintainer can build the release and confirm its visible behavior.
+owner:
+ kind: human
+ id: maintainer@example.com
+ name: Project maintainer
+constraints:
+ - One contribution represents one coherent outcome.
+criteria:
+ - description: The release build succeeds
+ probe:
+ kind: command
+ run: npm run build
+ timeoutMs: 120000
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The production build completed for this exact revision.
+ whyItMatters: A broken build cannot produce a releasable artifact.
+ code:
+ - path: src/build.ts
+ purpose: Produces the release bundle.
+ artifacts: []
+humanCriteria:
+ - id: visible-behavior
+ description: The maintainer confirms the user-facing result matches the request
+ guidance: Open the preview and inspect the attached screenshots before accepting.
+createdAt: 2026-08-15T00:00:00Z
+updatedAt: 2026-08-15T00:00:00Z
```
-Your machine
-├── Claude Code (or Cursor, Codex)
-│ ├── PostToolUse hook → keyoku record (activity logging)
-│ └── MCP connection → keyoku serve (tool calls)
-│
-└── ~/.keyoku/
- ├── activity.jsonl (event stream, capped)
- ├── templates.json (approved workflows)
- ├── executions.json (run history)
- ├── goals.json (convergence targets)
- └── connectors.json (external MCP services)
+
+## CLI
+
+```text
+keyoku proof demo Run the real fail → repair → stale-proof scenario
+keyoku proof init Detect the project and install a proof workflow
+keyoku proof customize Edit common proof fields without schema knowledge
+keyoku proof run Evaluate the outcome and generate a local Factfile
+keyoku proof serve Open the token-scoped human ↔ agent session
+keyoku proof review Record an identified human criterion decision
+keyoku proof accept Accept one exact passing snapshot as a human
+keyoku factfile inspect Validate and explain a content-bound Factfile
+keyoku factfile verify Also require the current source to match it
+keyoku factfile publish Explicitly publish to an optional Engine
+keyoku pulse help Inspect the event, checkpoint, planner, and renderer path
+keyoku pulse fixture generic Emit a harness-neutral JSONL integration fixture
+keyoku pulse ingest --file F Append strict, idempotent lifecycle events
+keyoku pulse plan --json Decide send/defer/dedupe/suppress/coalesce/stale_no_send
+keyoku pulse render --audience A Render one content-bound audience projection
+keyoku serve Serve the bounded MCP surface over stdio
+keyoku doctor --json Inspect install, project, and authority boundaries
```
-The division of labor: **heuristics** generate candidates for free, the **small model** refines them cheaply, and your **coding agent** does the heavy lifting on the subscription you already pay for. Keyoku orchestrates; it never burns frontier tokens.
+`proof run` reuses the active contribution for the current branch and outcome,
+so fail, repair, and re-proof checkpoints remain one inspectable history. Pass
+`--new` only for a genuinely separate attempt. The v2 goals, workflows,
+connectors, activity recorder, memory, and execution commands are not v3 public
+entrypoints. See the checked [v3 public surface](docs/PUBLIC-SURFACE.md).
-## Configuration
+## Different tools, different jobs
-| Env var | Default | Purpose |
+| Product | Primary job | Keyoku's boundary |
|---|---|---|
-| `KEYOKU_HOME` | `~/.keyoku` | State directory |
-| `GEMINI_API_KEY` / `ANTHROPIC_API_KEY` | — | Enable model-refined suggestions |
-| `KEYOKU_SLM_PROVIDER` | auto | `gemini`, `anthropic`, `openai-compat`, or `none` |
-| `KEYOKU_SLM_BASE_URL` / `KEYOKU_SLM_MODEL` | — | Any OpenAI-compatible endpoint (Ollama, LM Studio, LiteLLM, Groq, …) |
-| `KEYOKU_ENGINE_URL` | — | Connect a running [keyoku-engine](https://github.com/Keyoku-ai/keyoku-engine): knowledge mirrors into it and queries upgrade to semantic search |
-| `KEYOKU_WF_MIN_SIMILARITY` | `0.2` | Jaccard floor for suggesting a learned workflow on a new goal |
-| `KEYOKU_WF_SUGGEST_LIMIT` | `2` | Max learned workflows surfaced per assessment |
-| `KEYOKU_BACKFILL_LOOKBACK_MIN` | `45` | Minutes before a goal's creation to scan for build-then-verify work |
-| `KEYOKU_BACKFILL_HEAD_STEPS` | `8` | Setup steps kept from the front when a backfilled workflow is capped |
-| `KEYOKU_DEBUG` | — | Full error stacks |
+| GitHub Copilot agents | Run and track GitHub agent sessions | Keyoku remains harness-neutral and evaluates a repository-owned outcome |
+| Entire | Capture prompts, transcripts, and session checkpoints in Git | Keyoku records bounded result evidence; raw transcripts are optional |
+| Graphite | Split and navigate stacked pull requests | Keyoku evaluates each coherent outcome in the stack |
+| CodeRabbit | AI review and defect suggestions | Keyoku reports the project's own deterministic proof and human decisions |
+| CI/test tools | Execute specialized checks | Keyoku explains their relevance and binds results into one portable receipt |
+
+Keyoku should complement these tools, not recreate them.
+
+## Two repositories, one product
+
+| Repository | Free responsibility |
+|---|---|
+| [`keyoku`](https://github.com/Keyoku-ai/keyoku) | CLI, open Factfile and Pulse schemas, local verifier/ledger/planner/renderers, GitHub workflow, harness adapters |
+| [`keyoku-engine`](https://github.com/Keyoku-ai/keyoku-engine) | Optional durable multi-run Factfile/Pulse registry and dispatcher service plus the retained embedded-memory library |
+
+The CLI repository is the product wedge and source of truth. The engine is an optional registry—not a required memory backend and not a duplicate control plane. Managed team views, retention policy, RBAC, and cross-repository search are possible hosted extensions; they are not presented as finished open-source features. Both repositories remain usable for public or private repositories.
+
+## Trust and privacy
-## Security
+- Project proof lives in `.keyoku/`; ephemeral evaluator state lives in `.keyoku/runtime/`.
+- Credential-shaped observations are redacted before JSON, Markdown, HTML, or publication.
+- Factfile publication is explicit and accepts HTTPS or loopback HTTP only.
+- GitHub proof execution is read-only and does not post privileged PR comments from untrusted code.
+- Agent identity is provenance. A human or organization remains accountable.
-Approved templates execute shell commands with your privileges — the approval step is the trust boundary. Read [SECURITY.md](SECURITY.md) before installing.
+Read the [Factfile standard](docs/FACTFILE-STANDARD.md), [v3 public surface](docs/PUBLIC-SURFACE.md), [Pulse contract](docs/PULSE.md), [GitHub integration guide](docs/GITHUB.md), and [security review](docs/SECURITY-REVIEW.md).
-## keyoku-engine
+## Status
-The Go backend for teams: knowledge graph, semantic search, memory decay, and cross-device sync. Available at [github.com/Keyoku-ai/keyoku-engine](https://github.com/Keyoku-ai/keyoku-engine).
+The Factfile and Pulse schemas are `v1alpha1`. The local evaluator, exact Git binding, repeated proof history, durable two-way instruction protocol, token-scoped live session, JSON/Markdown/HTML renderers, annotated visual evidence, scope boundary, outcome history, GitHub Check workflow, deterministic Pulse planner, and audience renderers are implemented and tested. Keyoku does not run an agent or deliver a Pulse update. Schema meaning may still evolve during alpha; incompatible changes receive a new schema version.
## License
diff --git a/SECURITY.md b/SECURITY.md
index b1d88a6..9bd986b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,32 +1,48 @@
-# Security Policy
+# Security policy
## Reporting a vulnerability
-Use GitHub's private vulnerability reporting on this repository, or email
-**support@keyoku.ai**. Please do not open public issues for security reports.
-We aim to acknowledge reports within 72 hours.
+Do not include secrets, customer evidence, or exploit material in a public
+issue.
+
+This integration candidate does not yet have a verified private vulnerability
+intake. Public release is blocked until the owner enables GitHub private
+vulnerability reporting or designates and verifies a monitored security
+contact. Final reporting instructions and response expectations must describe
+that exact intake rather than an unverified mailbox.
## Supported versions
-Only the latest published 0.x release receives security fixes.
-
-## Execution model — read this before installing
-
-Keyoku is a local automation tool. Be aware of what it does by design:
-
-- **Approved workflow templates execute shell commands** on your machine with
- your privileges via `workflow_execute`. The approval step
- (`workflow_approve`) is the trust boundary: review every step of a template
- before approving it, exactly as you would review a shell script before
- running it. Steps time out (30s, SIGTERM→SIGKILL) and output is captured.
-- **The activity log** (`~/.keyoku/activity.jsonl`) records summaries of your
- tool usage (commands, file paths). It stays on your machine. There is no
- telemetry and no network calls unless you configure an SLM key
- (GEMINI_API_KEY / ANTHROPIC_API_KEY), in which case pattern summaries are
- sent to that provider for refinement.
-- **State files** under `~/.keyoku` are written with mode 0600 (dir 0700),
- the same posture as `~/.aws`. Connector configs may contain credentials —
- treat the directory accordingly.
-- **Connector autonomy**: external MCP connectors default to gated execution;
- write-capable calls can be routed through an approvals queue
- (`keyoku approvals`).
+The public repository and npm `latest` tag still contain v2. The v3 candidate
+is unreleased and receives fixes only on its reviewed integration line until a
+replacement release is approved.
+
+## v3 execution model — read this before evaluating
+
+Keyoku v3 is a local assurance tool. Be aware of what it does by design:
+
+- **Repository outcome probes are trusted code.** Review an unfamiliar outcome
+ contract exactly as you would review project test scripts from an untrusted
+ fork. The release candidate executes command probes in disposable source
+ snapshots, but does not claim hostile-command containment from an OS sandbox.
+- **Human authority is out of band.** The narrow v3 agent-facing CLI and MCP
+ surfaces cannot fabricate review acceptance, connector approval, or shell
+ workflows. Acceptance binds an identified human to one exact Factfile
+ digest and source identity; it is not yet externally signed.
+- **Factfiles can contain sensitive data.** Credential-shaped evidence values
+ are redacted and portable artifacts are path- and signature-checked, but
+ this is not artifact-wide DLP. Inspect every export before sharing it.
+- **Pulse never silently sends.** Fixture and adapter-attested checkpoints are
+ nondispatchable. A local checkpoint is planned only after current Factfile,
+ source, and artifact bytes are reverified. Delivery requires separately
+ authorized channel code and a recorded successful receipt.
+- **No telemetry is enabled by default.** The local v3 surface does not require
+ Engine, an account, an LLM key, or a hosted control plane.
+
+## v2 compatibility
+
+The v2 activity, connector, memory, workflow, and shell-execution surfaces are
+not registered or shipped by the narrow v3 entrypoint. Existing v2 users must
+continue to treat approved workflows and connector calls as local code running
+with their user privileges. See the v2 release documentation for that retained
+compatibility line.
diff --git a/VALIDATION-REPORT-2026-07-02.md b/VALIDATION-REPORT-2026-07-02.md
index 19018e1..f24489b 100644
--- a/VALIDATION-REPORT-2026-07-02.md
+++ b/VALIDATION-REPORT-2026-07-02.md
@@ -40,7 +40,7 @@ Source: 47-agent read-only refute (6 lenses, independent skeptic verification) +
- Scenario: Disk fills while appendAudit writes an entry (or the hook process is killed mid-append). From then on, every audit_list MCP call (server.ts:924) and `keyoku audit` CLI invocation (index.ts:1273) throws 'Unexpected end of JSON input' / 'Unterminated string in JSON' whenever the bad line is within the last `limit` lines
- Fix: In listAudit (src/store.ts:318-326), match the sibling readers: parse with `.flatMap((l) => { try { return [JSON.parse(l) as AuditEntry]; } catch { return []; } })` over all non-empty lines, then `.slice(-limit)` on the parsed array (as listActivity does at :368-377) so a torn line is skipped instea
- 🔧 **src/activity.ts:159** — redactSecrets leaks Basic-auth credentials (mis-redacts the wrong token) and DB connection-string passwords
- - Scenario: User (or agent) runs `curl -H "Authorization: Basic dXNlcjpwYXNzd29yZA==" https://api.example.com` or `psql postgres://admin:s3cr3tPass@db:5432/prod`. The PostToolUse hook records the Bash command; redactSecrets leaves the base64 credential / DB password in ~/.keyoku/activity.jsonl. That command later becomes a workflo
+ - Scenario: User (or agent) runs `curl -H "Authorization: Basic dXNlcjpwYXNzd29yZA==" https://api.example.com` or `psql postgres://admin:s3cr3tPass@db:5432/prod`. The PostToolUse hook records the Bash command; redactSecrets leaves the base64 credential / DB password in ~/.keyoku/activity.jsonl. That command later becomes a workflo
- Fix: In src/activity.ts add a BASIC_RE analogous to BEARER_RE (/\b(basic\s+)[A-Za-z0-9+\/=]{8,}/gi → "$1«redacted»", applied before SECRET_ASSIGNMENT_RE and also matching inside quotes) and a URL-userinfo rule (/(\/\/[^\s\/:@"']+:)[^\s@"']+@/g → "$1«redacted»@") covering postgres://user:pass@host-style s
- 🔧 **src/engine.ts:589** — Re-promotion of a workflow silently wipes the self-pruning stats (suggested/helped), defeating precision ranking
- Scenario: Verified by repro: workflow with stats {convergences:1, suggested:5, helped:0} (a proven word-match-only workflow that would rank at the 0.25 precision floor) drifts and re-converges once → stats become {convergences:2, totalActions:1} with suggested/helped gone → precision multiplier returns to 1.0 and the noisy workf
diff --git a/action.yml b/action.yml
new file mode 100644
index 0000000..1a2b9b4
--- /dev/null
+++ b/action.yml
@@ -0,0 +1,54 @@
+name: Keyoku Factfile
+description: Turn a repository-owned outcome into exact-revision evidence and a human-readable Factfile.
+author: Keyoku
+branding:
+ icon: check-circle
+ color: gray-dark
+inputs:
+ outcome:
+ description: Outcome id under .keyoku/outcomes.
+ required: true
+ base:
+ description: Base Git SHA or ref for the contribution boundary.
+ required: false
+ default: HEAD^
+ keyoku-version:
+ description: Exact published Keyoku npm version used by this action revision.
+ required: false
+ default: "3.0.0-alpha.1"
+ retention-days:
+ description: Number of days GitHub retains the full Factfile artifact.
+ required: false
+ default: "14"
+outputs:
+ contribution-id:
+ description: The generated contribution id.
+ value: ${{ steps.proof.outputs.contribution_id }}
+ state:
+ description: The exact-snapshot Keyoku state.
+ value: ${{ steps.proof.outputs.state }}
+ factfile:
+ description: Path to the generated HTML Factfile.
+ value: ${{ steps.proof.outputs.factfile }}
+runs:
+ using: composite
+ steps:
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 20
+ - name: Generate exact-revision proof
+ id: proof
+ shell: bash
+ env:
+ KEYOKU_OUTCOME: ${{ inputs.outcome }}
+ KEYOKU_BASE: ${{ inputs.base }}
+ KEYOKU_VERSION: ${{ inputs.keyoku-version }}
+ run: npx --yes "keyoku@${KEYOKU_VERSION}" proof ci "${KEYOKU_OUTCOME}" --base "${KEYOKU_BASE}"
+ - name: Attach the full Factfile
+ if: always() && steps.proof.outputs.contribution_id != ''
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: keyoku-factfile-${{ github.sha }}
+ path: .keyoku/contributions/${{ steps.proof.outputs.contribution_id }}/factfile.*
+ if-no-files-found: error
+ retention-days: ${{ inputs.retention-days }}
diff --git a/archive/README.md b/archive/README.md
new file mode 100644
index 0000000..e093b67
--- /dev/null
+++ b/archive/README.md
@@ -0,0 +1,17 @@
+# Keyoku archive
+
+This directory preserves code and product documents removed from the active build during the continuous-contribution-gate pivot. Archived files are retained for history and selective reuse; they are excluded from TypeScript compilation, npm packaging, tests, CLI help, and MCP registration.
+
+Archiving rule:
+
+1. Prove the code belongs only to an abandoned product surface.
+2. Move implementation and dedicated tests together.
+3. Remove all active imports, commands, tools, presets, and promises.
+4. Keep a recovery note and run the complete active test suite.
+5. Never archive a shared primitive merely because one old integration used it.
+
+See each subdirectory for scope and recovery instructions.
+
+- `legacy-omnigent/` — abandoned fleet-runner runtime and dedicated tests.
+- `legacy-positioning/` — abandoned Outcome Engine positioning.
+- `experimental-control-plane/` — recoverable live briefing, steering, presence, and generative-view prototype removed from the proof-first V1 launch path.
diff --git a/archive/experimental-control-plane/.keyoku/harnesses.yaml b/archive/experimental-control-plane/.keyoku/harnesses.yaml
new file mode 100644
index 0000000..b764954
--- /dev/null
+++ b/archive/experimental-control-plane/.keyoku/harnesses.yaml
@@ -0,0 +1,9 @@
+schemaVersion: keyoku.dev/harnesses/v1alpha1
+adapters:
+ - id: codex-headless
+ label: Codex headless
+ kind: codex-exec
+ enabled: true
+ model: gpt-5.6-sol
+ sandbox: workspace-write
+ description: Dispatch one scoped goal to a non-interactive Codex worker.
diff --git a/archive/experimental-control-plane/.keyoku/outcomes/project-intelligence-v1.yaml b/archive/experimental-control-plane/.keyoku/outcomes/project-intelligence-v1.yaml
new file mode 100644
index 0000000..636286a
--- /dev/null
+++ b/archive/experimental-control-plane/.keyoku/outcomes/project-intelligence-v1.yaml
@@ -0,0 +1,128 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: project-intelligence-v1
+revision: 1
+title: A Project Steward keeps code, agents, and proof synchronized
+objective: >-
+ A developer can open Keyoku and understand the current architecture, active goals, worker
+ agents, important changes, evidence, and decisions from a live project model that updates
+ without manually rewriting dashboard copy.
+owner:
+ kind: human
+ id: keyoku-owner
+ name: Tye
+ role: accountable product owner
+constraints:
+ - The Steward maintains project intelligence and context but does not replace worker-agent harnesses.
+ - Deterministic observations remain distinct from agent-inferred semantic structure.
+ - The UI is template-driven and accepts typed, attributed data patches rather than arbitrary agent-authored scripts.
+ - Multiple goals may be active; one may be focused per project view without deleting or hiding the others.
+criteria:
+ - description: The project intelligence architecture defines the Steward, context graph ontology, worker plane, evidence artifacts, deployment modes, and adoption wedge
+ probe:
+ kind: command
+ run: >-
+ node -e "const fs=require('fs');const s=fs.readFileSync('docs/KEYOKU-PROJECT-INTELLIGENCE.md','utf8');process.exit(['Project Steward','Core ontology','Context compiler','Worker plane','Projection and artifact plane','Deployment modes','Adoption wedge'].every(x=>s.includes(x))?0:1)"
+ timeoutMs: 30000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The system design assigns deterministic observation, semantic stewardship, worker execution, and human accountability to separate bounded planes.
+ whyItMatters: Contributors can implement intelligence without turning Keyoku into another opaque autonomous coding harness.
+ code:
+ - path: docs/KEYOKU-PROJECT-INTELLIGENCE.md
+ purpose: Canonical Project Steward, context graph, synchronization, artifact, and deployment architecture.
+ artifacts: []
+ - description: The architecture contract produces a current projection and portable SVG export
+ probe:
+ kind: command
+ run: >-
+ npx tsx -e "import {scanArchitecture,renderArchitectureSvg} from './src/architecture.ts';const a=scanArchitecture(process.cwd());const s=renderArchitectureSvg(a);process.exit(a.components.length>=8&&a.relations.length>=8&&s.includes('
";
+ return `Keyoku project status
Keyoku · shareable project status
${htmlEscape(focused?.title || "No focused goal")}
${state.agents.active.length ? htmlEscape(state.agents.active.map((item) => `${item.actor.name}: ${item.currentWork?.summary || item.status}`).join(" · ")) : "No agent has a current heartbeat lease."}
${state.snapshot.changedFiles} working-tree files · ${htmlEscape(state.snapshot.branch)} at ${htmlEscape(state.snapshot.head)}
Generated ${htmlEscape(state.generatedAt)} · Portable status snapshot, not an accepted Factfile unless human review says so.
`;
+}
+
+const server = createServer(async (req, res) => {
+ try {
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
+
+ if (url.searchParams.get("token") === token) {
+ res.writeHead(302, {
+ Location: url.pathname,
+ "Set-Cookie": `${sessionCookie}; HttpOnly; SameSite=Strict; Path=/; Max-Age=28800`,
+ "Cache-Control": "no-store",
+ });
+ res.end();
+ return;
+ }
+
+ if (!authenticated(req, url)) {
+ res.writeHead(401, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
+ res.end("This Keyoku briefing link is missing or has an expired session token.");
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/state") {
+ json(res, 200, projectState());
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/context") {
+ json(res, 200, contextPacket(url.searchParams.get("question") || ""));
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/architecture") {
+ const architecture = architectureState();
+ if (!architecture) return json(res, 404, { error: "No architecture contract exists" });
+ json(res, 200, architecture);
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/view") {
+ json(res, 200, projectView());
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/record") {
+ json(res, 200, projectRecord(safeText(url.searchParams.get("goalId"), 200), safeText(url.searchParams.get("snapshotId"), 200)));
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/snapshots/current") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const goalId = safeText(input.goalId, 200);
+ const snapshotId = safeText(input.snapshotId, 200);
+ const record = projectRecord(goalId, snapshotId);
+ if (!goalId || record.goal?.id !== goalId || record.selectedId !== snapshotId) return json(res, 404, { error: "Goal snapshot not found" });
+ const event = { eventType: "snapshot.current", eventId: protocolId("evt"), goalId, snapshotId, previousSnapshotId: record.currentId, actor: { kind: "human", id: "owner", name: "Tye" }, reason: safeText(input.reason, 1_000) || "Selected as the current Keyoku review baseline. Git was not changed.", createdAt: new Date().toISOString() };
+ appendEvent(currentSnapshotsPath, event);
+ broadcast("state", { kind: "snapshot.current", event });
+ json(res, 201, { event, record: projectRecord(goalId, snapshotId) });
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/goals/focus") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const goalId = safeText(input.goalId, 200);
+ const goals = projectGoals();
+ if (!goals.active.some((goal) => goal.id === goalId)) return json(res, 404, { error: "Project goal not found" });
+ const event = {
+ eventType: "goal.focused",
+ eventId: protocolId("evt"),
+ goalId,
+ actor: { kind: "human", id: "owner", name: "Tye" },
+ reason: safeText(input.reason, 1_000) || "Focused from the Keyoku project interface.",
+ createdAt: new Date().toISOString(),
+ };
+ appendEvent(goalFocusPath, event);
+ broadcast("state", { kind: "goal.focused", event });
+ json(res, 201, { event, goals: projectGoals() });
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/dispatch") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const goals = projectGoals();
+ const goalId = safeText(input.goalId, 200) || goals.focusedId;
+ const adapterId = safeText(input.adapterId, 200);
+ const goal = goals.active.find((item) => item.id === goalId);
+ const adapter = projectHarnesses().find((item) => item.id === adapterId);
+ if (!goal) return json(res, 404, { error: "Project goal not found" });
+ if (!adapter) return json(res, 404, { error: "Enabled harness adapter not found" });
+ const dispatch = startHeadlessDispatch({ adapter, goal });
+ broadcast("state", { kind: "dispatch.started", dispatch });
+ json(res, 201, { dispatch, message: `${adapter.label} started for ${goal.title}` });
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/api/events") {
+ res.writeHead(200, {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache, no-transform",
+ Connection: "keep-alive",
+ "X-Accel-Buffering": "no",
+ });
+ res.write(`event: connected\ndata: ${JSON.stringify(projectState())}\n\n`);
+ clients.add(res);
+ req.on("close", () => clients.delete(res));
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/steer") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const message = safeText(input.message, 2_000);
+ const kind = safeText(input.kind, 40) || "direction";
+ if (!message) return json(res, 400, { error: "A steering message is required" });
+ const event = { id: `steer_${Date.now().toString(36)}`, kind, message, actor: { kind: "human", name: "Tye" }, createdAt: new Date().toISOString(), status: "queued" };
+ appendEvent(steeringPath, event);
+ broadcast("state", { kind: "steering", event });
+ json(res, 201, event);
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/ask") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const question = safeText(input.question, 2_000);
+ if (!question) return json(res, 400, { error: "A question is required" });
+ const packet = contextPacket(question);
+ const event = {
+ id: `ask_${Date.now().toString(36)}`,
+ kind: "question",
+ message: question,
+ actor: { kind: "human", name: "Tye" },
+ createdAt: new Date().toISOString(),
+ status: "queued",
+ };
+ appendEvent(steeringPath, event);
+ broadcast("state", { kind: "question", event });
+ json(res, 201, {
+ mode: "copy",
+ delivery: "queued_for_connected_agents",
+ event,
+ prompt: promptForAgent(packet),
+ note: "Keyoku recorded this in the project inbox. Copy the prompt into an active agent session unless a live harness adapter is connected.",
+ });
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/interventions") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const message = safeText(input.message, 4_000);
+ const allowedKinds = new Set(["query", "direction", "decision_response", "control", "proof_challenge"]);
+ const allowedPolicies = new Set(["when_available", "next_checkpoint", "interrupt_now"]);
+ const kind = allowedKinds.has(input.kind) ? input.kind : "query";
+ const policy = allowedPolicies.has(input.deliveryPolicy) ? input.deliveryPolicy : "next_checkpoint";
+ const targetMode = ["current", "session", "all"].includes(input.targetMode) ? input.targetMode : "current";
+ const targetSessionId = safeText(input.targetSessionId, 200);
+ if (!message) return json(res, 400, { error: "An intervention message is required" });
+ if (targetMode === "session" && !targetSessionId) return json(res, 400, { error: "targetSessionId is required for a session target" });
+ const id = protocolId("int");
+ const repository = repositoryState();
+ const focusedGoalId = projectGoals().focusedId;
+ const event = {
+ schemaVersion: "keyoku.dev/intervention/v1alpha1",
+ eventType: "intervention.created",
+ eventId: protocolId("evt"),
+ id,
+ projectId: "keyoku",
+ threadId: "project",
+ correlationId: id,
+ kind,
+ message,
+ actor: { kind: "human", id: "owner", name: "Tye" },
+ target: { mode: targetMode, ...(targetSessionId ? { sessionId: targetSessionId } : {}) },
+ scope: { ...(focusedGoalId ? { outcomeId: focusedGoalId } : {}), snapshotRef: repository.head },
+ delivery: {
+ policy,
+ require: Array.isArray(input.require) && input.require.length ? input.require.filter((value) => ["understood", "applied", "verified"].includes(value)) : ["understood", "applied"],
+ },
+ createdAt: new Date().toISOString(),
+ idempotencyKey: safeText(input.idempotencyKey, 300) || protocolId("idem"),
+ phase: "committed",
+ receipts: [],
+ };
+ appendEvent(protocolPath, event);
+ broadcast("state", { kind: "intervention", event });
+ const activeAgents = foldedSessions().filter((session) => session.active && (!focusedGoalId || session.currentWork?.outcomeId === focusedGoalId));
+ const packet = contextPacket(message);
+ json(res, 201, {
+ event,
+ delivery: {
+ state: "committed",
+ activeTargets: activeAgents.length,
+ mode: activeAgents.length ? "adapter_or_poll" : "durable_inbox",
+ note: activeAgents.length
+ ? "Committed to the project thread. Target agents must publish semantic receipts as they understand and apply it."
+ : "Committed to the durable inbox. No agent heartbeat is active, so this has not been delivered yet.",
+ },
+ fallbackPrompt: promptForAgent(packet),
+ });
+ return;
+ }
+
+ const cancelMatch = url.pathname.match(/^\/api\/interventions\/([^/]+)\/cancel$/);
+ if (req.method === "POST" && cancelMatch) {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const interventionId = decodeURIComponent(cancelMatch[1]);
+ const intervention = foldedInterventions(500).find((item) => item.id === interventionId);
+ if (!intervention) return json(res, 404, { error: "Intervention not found" });
+ if (["applied", "verified", "declined", "expired", "superseded", "could_not_apply", "cancelled"].includes(intervention.phase)) {
+ return json(res, 409, { error: `A ${intervention.phase} intervention cannot be cancelled` });
+ }
+ const receipt = {
+ eventType: "intervention.receipt",
+ eventId: protocolId("evt"),
+ interventionId,
+ phase: "cancelled",
+ summary: "Cancelled by the accountable human before application.",
+ actor: { kind: "human", id: "owner", name: "Tye" },
+ createdAt: new Date().toISOString(),
+ };
+ appendEvent(protocolPath, receipt);
+ broadcast("state", { kind: "intervention.receipt", event: receipt });
+ json(res, 201, { intervention: foldedInterventions(500).find((item) => item.id === interventionId), receipt });
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/agent-heartbeat") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const sessionId = safeText(input.sessionId, 200);
+ const harness = safeText(input.harness, 200);
+ const actorName = safeText(input.actorName, 200) || "Coding agent";
+ const transport = safeText(input.transport, 100);
+ const allowedStatuses = new Set(["idle", "working", "waiting", "blocked", "disconnected"]);
+ const status = allowedStatuses.has(input.status) ? input.status : "working";
+ if (!sessionId || !harness || !transport) return json(res, 400, { error: "sessionId, harness, and transport are required" });
+ const leaseSeconds = Math.max(15, Math.min(Number(input.leaseSeconds) || 45, 300));
+ const now = new Date();
+ const event = {
+ schemaVersion: "keyoku.dev/agent-session/v1alpha1",
+ eventType: "agent.heartbeat",
+ eventId: protocolId("evt"),
+ sessionId,
+ actor: { kind: "agent", id: safeText(input.actorId, 200) || sessionId, name: actorName, harness, ...(safeText(input.model, 200) ? { model: safeText(input.model, 200) } : {}) },
+ status,
+ ...(safeText(input.workSummary, 1_000) ? { currentWork: {
+ summary: safeText(input.workSummary, 1_000),
+ ...(safeText(input.outcomeId, 200) ? { outcomeId: safeText(input.outcomeId, 200) } : {}),
+ ...(safeText(input.contributionId, 200) ? { contributionId: safeText(input.contributionId, 200) } : {}),
+ ...(Array.isArray(input.capabilityIds) ? { capabilityIds: input.capabilityIds.map((value) => safeText(value, 200)).filter(Boolean).slice(0, 30) } : {}),
+ ...(Array.isArray(input.paths) ? { paths: input.paths.map((value) => safeText(value, 500)).filter(Boolean).slice(0, 100) } : {}),
+ baseSnapshot: repositoryState().head,
+ } } : {}),
+ capabilities: Array.isArray(input.capabilities) ? input.capabilities.map((value) => safeText(value, 100)).filter(Boolean).slice(0, 30) : [],
+ transport,
+ createdAt: now.toISOString(),
+ leaseUntil: new Date(now.getTime() + leaseSeconds * 1_000).toISOString(),
+ active: status !== "disconnected",
+ };
+ appendEvent(sessionsPath, event);
+ broadcast("state", { kind: "agent.heartbeat", event });
+ json(res, 201, event);
+ return;
+ }
+
+ if (req.method === "POST" && url.pathname === "/api/decision") {
+ if (!assertLocalOrigin(req)) return json(res, 403, { error: "Origin does not match this Keyoku session" });
+ const input = await body(req);
+ const decisionId = safeText(input.decisionId, 100);
+ const choice = safeText(input.choice, 100);
+ if (!decisionId || !choice) return json(res, 400, { error: "decisionId and choice are required" });
+ const event = { id: `decision_${Date.now().toString(36)}`, decisionId, choice, actor: { kind: "human", name: "Tye" }, createdAt: new Date().toISOString() };
+ appendEvent(decisionsPath, event);
+ broadcast("state", { kind: "decision", event });
+ json(res, 201, event);
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/export/project-brief.json") {
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Content-Disposition": "attachment; filename=keyoku-project-brief.json", "Cache-Control": "no-store" });
+ res.end(JSON.stringify(exportJson(), null, 2));
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/export/project-update.md") {
+ res.writeHead(200, { "Content-Type": "text/markdown; charset=utf-8", "Content-Disposition": "attachment; filename=keyoku-project-update.md", "Cache-Control": "no-store" });
+ res.end(exportMarkdown());
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/export/project-update.html") {
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Disposition": "attachment; filename=keyoku-project-update.html", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
+ res.end(exportHtml());
+ return;
+ }
+
+ if (req.method === "GET" && url.pathname === "/export/architecture.svg") {
+ const architecture = architectureState();
+ if (!architecture) return json(res, 404, { error: "No architecture contract exists" });
+ res.writeHead(200, { "Content-Type": "image/svg+xml; charset=utf-8", "Content-Disposition": "attachment; filename=keyoku-architecture.svg", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
+ res.end(architectureSvg(architecture));
+ return;
+ }
+
+ const factfileMatch = url.pathname.match(/^\/artifacts\/factfiles\/([^/]+)$/);
+ if (req.method === "GET" && factfileMatch) {
+ const contributionId = decodeURIComponent(factfileMatch[1]);
+ if (!/^[a-zA-Z0-9._-]+$/.test(contributionId)) return json(res, 400, { error: "Invalid contribution id" });
+ const artifact = resolve(contributionsRoot, contributionId, "factfile.html");
+ if (!artifact.startsWith(`${resolve(contributionsRoot)}/`) || !existsSync(artifact)) return json(res, 404, { error: "Factfile not found" });
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": "default-src 'none'; img-src data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", "X-Content-Type-Options": "nosniff" });
+ createReadStream(artifact).pipe(res);
+ return;
+ }
+
+ const snapshotMatch = url.pathname.match(/^\/artifacts\/snapshots\/([^/]+)\/([^/]+)$/);
+ if (req.method === "GET" && snapshotMatch) {
+ const contributionId = decodeURIComponent(snapshotMatch[1]);
+ const snapshotId = decodeURIComponent(snapshotMatch[2]);
+ if (!/^[a-zA-Z0-9._-]+$/.test(contributionId) || !/^[a-zA-Z0-9._-]+$/.test(snapshotId)) return json(res, 400, { error: "Invalid snapshot reference" });
+ const artifact = resolve(contributionsRoot, contributionId, "snapshots", `${snapshotId}.html`);
+ if (!artifact.startsWith(`${resolve(contributionsRoot)}/`) || !existsSync(artifact)) return json(res, 404, { error: "Snapshot artifact not found" });
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", "Content-Security-Policy": "default-src 'none'; img-src data:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", "X-Content-Type-Options": "nosniff" });
+ createReadStream(artifact).pipe(res);
+ return;
+ }
+
+ if (req.method !== "GET") return json(res, 405, { error: "Method not allowed" });
+
+ const requested = url.pathname === "/" ? pagePath : join(docsRoot, normalize(url.pathname).replace(/^[/\\]+/, ""));
+ const absolute = resolve(requested);
+ if (!absolute.startsWith(`${docsRoot}/`) && absolute !== pagePath) return json(res, 404, { error: "Not found" });
+ if (!existsSync(absolute) || !statSync(absolute).isFile()) return json(res, 404, { error: "Not found" });
+ res.writeHead(200, {
+ "Content-Type": mime(absolute),
+ "Cache-Control": "no-store",
+ "Content-Security-Policy": "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'",
+ "Referrer-Policy": "no-referrer",
+ "X-Content-Type-Options": "nosniff",
+ "X-Frame-Options": "DENY",
+ });
+ createReadStream(absolute).pipe(res);
+ } catch (error) {
+ json(res, 500, { error: error instanceof Error ? error.message : String(error) });
+ }
+});
+
+const watched = [pagePath, join(projectRoot, ".keyoku")];
+for (const target of watched) {
+ if (!existsSync(target)) continue;
+ watch(target, { recursive: statSync(target).isDirectory() }, (_event, filename) => {
+ broadcast(target === pagePath ? "page" : "state", { changed: filename || target, at: new Date().toISOString() });
+ });
+}
+
+let lastRepositorySignature = JSON.stringify(repositoryState());
+setInterval(() => {
+ const repository = repositoryState();
+ const signature = JSON.stringify(repository);
+ if (signature !== lastRepositorySignature) {
+ lastRepositorySignature = signature;
+ broadcast("state", { kind: "repository", repository, at: new Date().toISOString() });
+ }
+}, 2_000).unref();
+
+function addresses() {
+ if (!lan) return ["127.0.0.1"];
+ const values = [];
+ for (const records of Object.values(networkInterfaces())) {
+ for (const record of records || []) {
+ if (record.family === "IPv4" && !record.internal) values.push(record.address);
+ }
+ }
+ return values.length ? values : ["127.0.0.1"];
+}
+
+server.listen(port, host, () => {
+ console.log("Keyoku project brief is live.");
+ console.log("");
+ for (const address of addresses()) console.log(` http://${address}:${port}/?token=${token}`);
+ console.log("");
+ console.log(lan ? "Anyone on this local network with the temporary link can view and steer this session." : "This session is available only on this device. Add --lan to create a phone-accessible local-network link.");
+ console.log("Press Ctrl+C to stop sharing.");
+});
+
+function shutdown() {
+ broadcast("closed", { at: new Date().toISOString() });
+ for (const client of clients) client.end();
+ server.close(() => process.exit(0));
+}
+process.on("SIGINT", shutdown);
+process.on("SIGTERM", shutdown);
diff --git a/archive/experimental-control-plane/src/presentation.ts b/archive/experimental-control-plane/src/presentation.ts
new file mode 100644
index 0000000..824b255
--- /dev/null
+++ b/archive/experimental-control-plane/src/presentation.ts
@@ -0,0 +1,109 @@
+import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { randomBytes } from "node:crypto";
+import { parse as parseYaml } from "yaml";
+
+export type ViewField = {
+ value: string;
+ description: string;
+ source: "manifest" | "agent";
+ updatedAt?: string;
+ actor?: { id: string; harness?: string; model?: string };
+ confidence?: number;
+};
+
+export type ProjectView = {
+ schemaVersion: "keyoku.dev/project-view/v1alpha1";
+ template: string;
+ fields: Record;
+};
+
+type ViewManifest = {
+ schemaVersion?: string;
+ template?: string;
+ fields?: Record;
+};
+
+type ViewPublication = {
+ eventType: "view.fields.published";
+ eventId: string;
+ fields: Record;
+ actor: { id: string; harness?: string; model?: string };
+ confidence: number;
+ reason: string;
+ createdAt: string;
+};
+
+const FIELD_NAME = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/;
+
+function manifestPath(root: string): string {
+ return join(root, ".keyoku", "view.yaml");
+}
+
+function eventsPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "view-events.jsonl");
+}
+
+function publications(root: string): ViewPublication[] {
+ const path = eventsPath(root);
+ if (!existsSync(path)) return [];
+ return readFileSync(path, "utf8").split("\n").filter(Boolean).flatMap((line) => {
+ try {
+ const event = JSON.parse(line) as ViewPublication;
+ return event.eventType === "view.fields.published" ? [event] : [];
+ } catch {
+ return [];
+ }
+ });
+}
+
+export function readProjectView(root: string): ProjectView {
+ const path = manifestPath(root);
+ if (!existsSync(path)) throw new Error("No .keyoku/view.yaml presentation manifest exists");
+ const manifest = parseYaml(readFileSync(path, "utf8")) as ViewManifest;
+ const fields: Record = {};
+ for (const [name, field] of Object.entries(manifest.fields ?? {})) {
+ if (!FIELD_NAME.test(name) || typeof field.value !== "string") continue;
+ fields[name] = {
+ value: field.value,
+ description: typeof field.description === "string" ? field.description : "Agent-editable presentation field.",
+ source: "manifest",
+ };
+ }
+ for (const event of publications(root)) {
+ for (const [name, value] of Object.entries(event.fields)) {
+ if (!fields[name]) continue;
+ fields[name] = { ...fields[name], value, source: "agent", updatedAt: event.createdAt, actor: event.actor, confidence: event.confidence };
+ }
+ }
+ return { schemaVersion: "keyoku.dev/project-view/v1alpha1", template: manifest.template || "convergence-thread", fields };
+}
+
+export function publishProjectView(
+ root: string,
+ input: { fields: Record; actor: { id: string; harness?: string; model?: string }; confidence?: number; reason: string },
+): ViewPublication {
+ const current = readProjectView(root);
+ const fields: Record = {};
+ for (const [name, raw] of Object.entries(input.fields)) {
+ if (!FIELD_NAME.test(name) || !current.fields[name]) throw new Error(`Unknown or protected view field '${name}'`);
+ const value = raw.trim();
+ if (!value) throw new Error(`View field '${name}' cannot be empty`);
+ if (value.length > 2_000) throw new Error(`View field '${name}' exceeds 2,000 characters`);
+ fields[name] = value;
+ }
+ if (!Object.keys(fields).length) throw new Error("At least one view field is required");
+ const event: ViewPublication = {
+ eventType: "view.fields.published",
+ eventId: `view_${Date.now().toString(36)}_${randomBytes(5).toString("base64url")}`,
+ fields,
+ actor: input.actor,
+ confidence: Math.max(0, Math.min(1, input.confidence ?? 0.8)),
+ reason: input.reason.trim().slice(0, 1_000),
+ createdAt: new Date().toISOString(),
+ };
+ const path = eventsPath(root);
+ mkdirSync(dirname(path), { recursive: true });
+ appendFileSync(path, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600 });
+ return event;
+}
diff --git a/archive/experimental-control-plane/src/project-state.ts b/archive/experimental-control-plane/src/project-state.ts
new file mode 100644
index 0000000..a26d038
--- /dev/null
+++ b/archive/experimental-control-plane/src/project-state.ts
@@ -0,0 +1,484 @@
+import { execFileSync } from "node:child_process";
+import { randomBytes } from "node:crypto";
+import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+
+import { listOutcomes, loadProject } from "./contribution.js";
+
+export type SteeringStatus = "queued" | "acknowledged" | "applied" | "superseded" | "could_not_apply";
+
+export type InterventionKind = "query" | "direction" | "decision_response" | "control" | "proof_challenge";
+export type InterventionPhase =
+ | "committed"
+ | "delivered"
+ | "understood"
+ | "planned"
+ | "applied"
+ | "verified"
+ | "declined"
+ | "expired"
+ | "superseded"
+ | "could_not_apply"
+ | "cancelled";
+export type DeliveryPolicy = "when_available" | "next_checkpoint" | "interrupt_now";
+export type AgentSessionStatus = "idle" | "working" | "waiting" | "blocked" | "disconnected";
+
+export interface ProtocolActor {
+ kind: "human" | "agent" | "system";
+ id: string;
+ name: string;
+ harness?: string;
+ model?: string;
+}
+
+export interface InterventionReceipt {
+ eventType: "intervention.receipt";
+ eventId: string;
+ interventionId: string;
+ phase: Exclude;
+ summary: string;
+ actor: ProtocolActor;
+ createdAt: string;
+ evidenceRefs?: string[];
+}
+
+export interface Intervention {
+ schemaVersion: "keyoku.dev/intervention/v1alpha1";
+ eventType: "intervention.created";
+ eventId: string;
+ id: string;
+ projectId: string;
+ threadId: string;
+ correlationId: string;
+ causationId?: string;
+ kind: InterventionKind;
+ message: string;
+ actor: ProtocolActor;
+ target: { mode: "current" | "session" | "all"; sessionId?: string };
+ scope: { outcomeId?: string; contributionId?: string; snapshotRef: string };
+ delivery: { policy: DeliveryPolicy; require: Array<"understood" | "applied" | "verified"> };
+ createdAt: string;
+ expiresAt?: string;
+ idempotencyKey: string;
+ phase: InterventionPhase;
+ receipts: InterventionReceipt[];
+}
+
+export interface AgentSession {
+ schemaVersion: "keyoku.dev/agent-session/v1alpha1";
+ eventType: "agent.heartbeat";
+ eventId: string;
+ sessionId: string;
+ actor: ProtocolActor;
+ status: AgentSessionStatus;
+ currentWork?: {
+ outcomeId?: string;
+ contributionId?: string;
+ summary: string;
+ capabilityIds?: string[];
+ paths?: string[];
+ baseSnapshot?: string;
+ };
+ capabilities: string[];
+ transport: string;
+ createdAt: string;
+ leaseUntil: string;
+ active: boolean;
+}
+
+export interface AgentCoordinationConflict {
+ sessions: [string, string];
+ reason: "same_contribution" | "overlapping_path";
+ scope: string;
+}
+
+export interface SteeringRequest {
+ id: string;
+ kind: string;
+ message: string;
+ actor?: { kind?: string; name?: string };
+ createdAt: string;
+ status: SteeringStatus;
+ acknowledgement?: {
+ summary: string;
+ actor: string;
+ createdAt: string;
+ };
+}
+
+interface SteeringAcknowledgement {
+ eventType: "acknowledgement";
+ steeringId: string;
+ status: Exclude;
+ summary: string;
+ actor: string;
+ createdAt: string;
+}
+
+function jsonLines(path: string): unknown[] {
+ if (!existsSync(path)) return [];
+ return readFileSync(path, "utf8")
+ .split("\n")
+ .filter(Boolean)
+ .flatMap((line) => {
+ try { return [JSON.parse(line) as unknown]; } catch { return []; }
+ });
+}
+
+function isRecord(value: unknown): value is Record {
+ return Boolean(value) && typeof value === "object";
+}
+
+function steeringPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "human-steering.jsonl");
+}
+
+function protocolPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "thread-events.jsonl");
+}
+
+function sessionPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "agent-sessions.jsonl");
+}
+
+function eventId(prefix: string): string {
+ return `${prefix}_${Date.now().toString(36)}_${randomBytes(6).toString("base64url")}`;
+}
+
+function appendJsonLine(path: string, value: unknown): void {
+ mkdirSync(dirname(path), { recursive: true });
+ appendFileSync(path, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600 });
+}
+
+function decisionsPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "human-decisions.jsonl");
+}
+
+function goalFocusPath(root: string): string {
+ return join(root, ".keyoku", "runtime", "goal-focus.jsonl");
+}
+
+export function focusedProjectGoalId(root: string): string | undefined {
+ const events = jsonLines(goalFocusPath(root)).filter(isRecord);
+ for (let index = events.length - 1; index >= 0; index -= 1) {
+ const event = events[index];
+ if (event && event.eventType === "goal.focused" && typeof event.goalId === "string") return event.goalId;
+ }
+ return undefined;
+}
+
+function git(root: string, args: string[], fallback = "unknown"): string {
+ try {
+ return execFileSync("git", args, {
+ cwd: root,
+ encoding: "utf8",
+ stdio: ["ignore", "pipe", "ignore"],
+ }).trim() || fallback;
+ } catch {
+ return fallback;
+ }
+}
+
+export function listSteering(root: string): SteeringRequest[] {
+ const requests = new Map();
+ const acknowledgements: SteeringAcknowledgement[] = [];
+
+ for (const value of jsonLines(steeringPath(root))) {
+ if (!isRecord(value)) continue;
+ if (value.eventType === "acknowledgement") {
+ if (
+ typeof value.steeringId === "string" &&
+ typeof value.status === "string" &&
+ typeof value.summary === "string" &&
+ typeof value.actor === "string" &&
+ typeof value.createdAt === "string"
+ ) acknowledgements.push(value as unknown as SteeringAcknowledgement);
+ continue;
+ }
+ if (typeof value.id !== "string" || typeof value.message !== "string" || typeof value.createdAt !== "string") continue;
+ requests.set(value.id, {
+ id: value.id,
+ kind: typeof value.kind === "string" ? value.kind : "direction",
+ message: value.message,
+ actor: isRecord(value.actor) ? {
+ kind: typeof value.actor.kind === "string" ? value.actor.kind : undefined,
+ name: typeof value.actor.name === "string" ? value.actor.name : undefined,
+ } : undefined,
+ createdAt: value.createdAt,
+ status: value.status === "acknowledged" || value.status === "applied" || value.status === "superseded" || value.status === "could_not_apply"
+ ? value.status
+ : "queued",
+ });
+ }
+
+ for (const acknowledgement of acknowledgements) {
+ const request = requests.get(acknowledgement.steeringId);
+ if (!request) continue;
+ request.status = acknowledgement.status;
+ request.acknowledgement = {
+ summary: acknowledgement.summary,
+ actor: acknowledgement.actor,
+ createdAt: acknowledgement.createdAt,
+ };
+ }
+
+ return [...requests.values()].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
+}
+
+export function acknowledgeSteering(input: {
+ root: string;
+ steeringId: string;
+ status: Exclude;
+ summary: string;
+ actor: string;
+}): SteeringRequest {
+ const request = listSteering(input.root).find((item) => item.id === input.steeringId);
+ if (!request) throw new Error(`Unknown steering request '${input.steeringId}'.`);
+ const event: SteeringAcknowledgement = {
+ eventType: "acknowledgement",
+ steeringId: input.steeringId,
+ status: input.status,
+ summary: input.summary.trim(),
+ actor: input.actor.trim(),
+ createdAt: new Date().toISOString(),
+ };
+ if (!event.summary || !event.actor) throw new Error("summary and actor are required");
+ const path = steeringPath(input.root);
+ mkdirSync(dirname(path), { recursive: true });
+ appendFileSync(path, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600 });
+ return listSteering(input.root).find((item) => item.id === input.steeringId)!;
+}
+
+export function createIntervention(input: {
+ root: string;
+ kind: InterventionKind;
+ message: string;
+ actor: ProtocolActor;
+ target?: Intervention["target"];
+ outcomeId?: string;
+ contributionId?: string;
+ deliveryPolicy?: DeliveryPolicy;
+ require?: Intervention["delivery"]["require"];
+ threadId?: string;
+ causationId?: string;
+ idempotencyKey?: string;
+ expiresAt?: string;
+}): Intervention {
+ const message = input.message.trim();
+ if (!message) throw new Error("message is required");
+ const existing = input.idempotencyKey
+ ? listInterventions(input.root).find((item) => item.idempotencyKey === input.idempotencyKey)
+ : undefined;
+ if (existing) return existing;
+ const id = eventId("int");
+ const intervention: Intervention = {
+ schemaVersion: "keyoku.dev/intervention/v1alpha1",
+ eventType: "intervention.created",
+ eventId: eventId("evt"),
+ id,
+ projectId: loadProject(input.root).id,
+ threadId: input.threadId || "project",
+ correlationId: id,
+ ...(input.causationId ? { causationId: input.causationId } : {}),
+ kind: input.kind,
+ message,
+ actor: input.actor,
+ target: input.target || { mode: "current" },
+ scope: {
+ ...(input.outcomeId ? { outcomeId: input.outcomeId } : {}),
+ ...(input.contributionId ? { contributionId: input.contributionId } : {}),
+ snapshotRef: git(input.root, ["rev-parse", "HEAD"]),
+ },
+ delivery: {
+ policy: input.deliveryPolicy || "next_checkpoint",
+ require: input.require || ["understood", "applied"],
+ },
+ createdAt: new Date().toISOString(),
+ ...(input.expiresAt ? { expiresAt: input.expiresAt } : {}),
+ idempotencyKey: input.idempotencyKey || eventId("idem"),
+ phase: "committed",
+ receipts: [],
+ };
+ appendJsonLine(protocolPath(input.root), intervention);
+ return intervention;
+}
+
+export function recordInterventionReceipt(input: {
+ root: string;
+ interventionId: string;
+ phase: Exclude;
+ summary: string;
+ actor: ProtocolActor;
+ evidenceRefs?: string[];
+}): Intervention {
+ const intervention = listInterventions(input.root).find((item) => item.id === input.interventionId);
+ if (!intervention) throw new Error(`Unknown intervention '${input.interventionId}'.`);
+ const receipt: InterventionReceipt = {
+ eventType: "intervention.receipt",
+ eventId: eventId("evt"),
+ interventionId: input.interventionId,
+ phase: input.phase,
+ summary: input.summary.trim(),
+ actor: input.actor,
+ createdAt: new Date().toISOString(),
+ ...(input.evidenceRefs?.length ? { evidenceRefs: input.evidenceRefs } : {}),
+ };
+ if (!receipt.summary) throw new Error("summary is required");
+ appendJsonLine(protocolPath(input.root), receipt);
+ return listInterventions(input.root).find((item) => item.id === input.interventionId)!;
+}
+
+export function listInterventions(root: string): Intervention[] {
+ const interventions = new Map();
+ const receipts: InterventionReceipt[] = [];
+ for (const value of jsonLines(protocolPath(root))) {
+ if (!isRecord(value)) continue;
+ if (value.eventType === "intervention.receipt" && typeof value.interventionId === "string") {
+ receipts.push(value as unknown as InterventionReceipt);
+ } else if (value.eventType === "intervention.created" && typeof value.id === "string") {
+ interventions.set(value.id, { ...(value as unknown as Intervention), phase: "committed", receipts: [] });
+ }
+ }
+ for (const receipt of receipts) {
+ const intervention = interventions.get(receipt.interventionId);
+ if (!intervention) continue;
+ intervention.receipts.push(receipt);
+ intervention.phase = receipt.phase;
+ }
+ return [...interventions.values()].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
+}
+
+export function heartbeatAgentSession(input: {
+ root: string;
+ sessionId: string;
+ actor: ProtocolActor;
+ status: AgentSessionStatus;
+ currentWork?: AgentSession["currentWork"];
+ capabilities?: string[];
+ transport: string;
+ leaseSeconds?: number;
+}): AgentSession {
+ const leaseSeconds = Math.max(15, Math.min(input.leaseSeconds || 45, 300));
+ const now = new Date();
+ const session: AgentSession = {
+ schemaVersion: "keyoku.dev/agent-session/v1alpha1",
+ eventType: "agent.heartbeat",
+ eventId: eventId("evt"),
+ sessionId: input.sessionId.trim(),
+ actor: input.actor,
+ status: input.status,
+ ...(input.currentWork ? { currentWork: input.currentWork } : {}),
+ capabilities: [...new Set(input.capabilities || [])],
+ transport: input.transport.trim(),
+ createdAt: now.toISOString(),
+ leaseUntil: new Date(now.getTime() + leaseSeconds * 1_000).toISOString(),
+ active: input.status !== "disconnected",
+ };
+ if (!session.sessionId || !session.transport) throw new Error("sessionId and transport are required");
+ appendJsonLine(sessionPath(input.root), session);
+ return session;
+}
+
+export function listAgentSessions(root: string, now = new Date()): AgentSession[] {
+ const sessions = new Map();
+ for (const value of jsonLines(sessionPath(root))) {
+ if (!isRecord(value) || value.eventType !== "agent.heartbeat" || typeof value.sessionId !== "string") continue;
+ sessions.set(value.sessionId, value as unknown as AgentSession);
+ }
+ return [...sessions.values()]
+ .map((session) => ({
+ ...session,
+ active: session.status !== "disconnected" && new Date(session.leaseUntil).getTime() > now.getTime(),
+ }))
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt));
+}
+
+export function findAgentCoordinationConflicts(sessions: AgentSession[]): AgentCoordinationConflict[] {
+ const active = sessions.filter((session) => session.active && session.currentWork);
+ const conflicts: AgentCoordinationConflict[] = [];
+ const pathOverlaps = (left: string, right: string) => {
+ const a = left.replace(/^\.\//, "").replace(/\/$/, "");
+ const b = right.replace(/^\.\//, "").replace(/\/$/, "");
+ return a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
+ };
+ for (let left = 0; left < active.length; left += 1) {
+ for (let right = left + 1; right < active.length; right += 1) {
+ const a = active[left]!;
+ const b = active[right]!;
+ if (a.currentWork?.contributionId && a.currentWork.contributionId === b.currentWork?.contributionId) {
+ conflicts.push({ sessions: [a.sessionId, b.sessionId], reason: "same_contribution", scope: a.currentWork.contributionId });
+ }
+ for (const aPath of a.currentWork?.paths || []) {
+ for (const bPath of b.currentWork?.paths || []) {
+ if (pathOverlaps(aPath, bPath)) conflicts.push({ sessions: [a.sessionId, b.sessionId], reason: "overlapping_path", scope: aPath.length <= bPath.length ? aPath : bPath });
+ }
+ }
+ }
+ }
+ return conflicts;
+}
+
+export function buildProjectOrientation(root: string) {
+ const project = loadProject(root);
+ const outcomes = listOutcomes(root);
+ const decisions = jsonLines(decisionsPath(root)).filter(isRecord);
+ const latestDecisions = new Map>();
+ for (const decision of decisions) {
+ if (typeof decision.decisionId === "string") latestDecisions.set(decision.decisionId, decision);
+ }
+ const steering = listSteering(root);
+ const pendingSteering = steering.filter((item) => item.status === "queued");
+ const interventions = listInterventions(root);
+ const pendingInterventions = interventions.filter((item) => !["applied", "verified", "declined", "expired", "superseded", "could_not_apply", "cancelled"].includes(item.phase));
+ const agentSessions = listAgentSessions(root);
+ const agentConflicts = findAgentCoordinationConflicts(agentSessions);
+ const status = git(root, ["status", "--porcelain=v1"], "");
+ const changedFiles = status ? status.split("\n").filter(Boolean) : [];
+ const focusedGoalId = focusedProjectGoalId(root);
+ const currentOutcome = outcomes.find((outcome) => outcome.id === focusedGoalId) || [...outcomes].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
+
+ return {
+ schemaVersion: "keyoku.dev/project-orientation/v1alpha1",
+ project: {
+ id: project.id,
+ name: project.name,
+ summary: project.summary,
+ },
+ snapshot: {
+ branch: git(root, ["branch", "--show-current"], "detached"),
+ head: git(root, ["rev-parse", "--short=12", "HEAD"]),
+ changedFiles: changedFiles.length,
+ exact: changedFiles.length === 0,
+ },
+ currentGoal: currentOutcome ? {
+ id: currentOutcome.id,
+ title: currentOutcome.title,
+ objective: currentOutcome.objective,
+ owner: currentOutcome.owner,
+ } : null,
+ goals: {
+ focusedId: currentOutcome?.id || null,
+ active: outcomes.map((outcome) => ({ id: outcome.id, title: outcome.title, objective: outcome.objective, owner: outcome.owner, updatedAt: outcome.updatedAt })),
+ count: outcomes.length,
+ },
+ humanAttention: {
+ pendingSteering,
+ pendingInterventions,
+ count: pendingSteering.length + pendingInterventions.length,
+ rule: "Interrupt a person only for a consequential, non-inferable, time-sensitive choice. Otherwise continue safely or include it in the next checkpoint.",
+ },
+ decisions: [...latestDecisions.values()],
+ agents: {
+ active: agentSessions.filter((session) => session.active),
+ recent: agentSessions,
+ coordinationConflicts: agentConflicts,
+ rule: "A session is active only while its signed heartbeat lease is valid; repository activity alone is not presence.",
+ },
+ instructions: [
+ "Use this compact orientation before substantial work; retrieve detailed outcomes or contributions only when needed.",
+ "Treat human decisions as constraints and distinguish observed evidence from agent proposals.",
+ "If steering is queued, acknowledge it before claiming it changed the work.",
+ "Checkpoint at a meaningful outcome boundary; do not dump raw transcripts into project state.",
+ ],
+ };
+}
diff --git a/archive/experimental-control-plane/tests/presentation.test.ts b/archive/experimental-control-plane/tests/presentation.test.ts
new file mode 100644
index 0000000..26477d2
--- /dev/null
+++ b/archive/experimental-control-plane/tests/presentation.test.ts
@@ -0,0 +1,30 @@
+import { cpSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, describe, expect, it } from "vitest";
+
+import { publishProjectView, readProjectView } from "../src/presentation.js";
+
+const roots: string[] = [];
+afterEach(() => roots.splice(0).forEach((root) => rmSync(root, { recursive: true, force: true })));
+
+function fixture(): string {
+ const root = mkdtempSync(join(tmpdir(), "keyoku-view-")); roots.push(root);
+ mkdirSync(join(root, ".keyoku"), { recursive: true });
+ writeFileSync(join(root, ".keyoku", "view.yaml"), "schemaVersion: keyoku.dev/project-view/v1alpha1\ntemplate: thread\nfields:\n header.summary:\n value: Original\n description: Header summary\n");
+ return root;
+}
+
+describe("agent-editable project view", () => {
+ it("publishes an attributed update to an allowlisted field", () => {
+ const root = fixture();
+ publishProjectView(root, { fields: { "header.summary": "Current and useful" }, actor: { id: "ui-agent", harness: "Codex", model: "gpt-5.6-sol" }, confidence: 0.92, reason: "Project state changed" });
+ const view = readProjectView(root);
+ expect(view.fields["header.summary"]).toMatchObject({ value: "Current and useful", source: "agent", confidence: 0.92 });
+ });
+
+ it("protects unknown facts and arbitrary DOM targets", () => {
+ const root = fixture();
+ expect(() => publishProjectView(root, { fields: { "header.innerHTML": "" }, actor: { id: "ui-agent" }, reason: "no" })).toThrow("Unknown or protected");
+ });
+});
diff --git a/archive/experimental-control-plane/tests/project-state.test.ts b/archive/experimental-control-plane/tests/project-state.test.ts
new file mode 100644
index 0000000..4913b56
--- /dev/null
+++ b/archive/experimental-control-plane/tests/project-state.test.ts
@@ -0,0 +1,198 @@
+import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+import { afterEach, describe, expect, it } from "vitest";
+
+import {
+ acknowledgeSteering,
+ buildProjectOrientation,
+ createIntervention,
+ findAgentCoordinationConflicts,
+ heartbeatAgentSession,
+ listAgentSessions,
+ listInterventions,
+ listSteering,
+ recordInterventionReceipt,
+} from "../src/project-state.js";
+
+const roots: string[] = [];
+
+function projectRoot(): string {
+ const root = mkdtempSync(join(tmpdir(), "keyoku-project-state-"));
+ roots.push(root);
+ mkdirSync(join(root, ".keyoku", "outcomes"), { recursive: true });
+ mkdirSync(join(root, ".keyoku", "runtime"), { recursive: true });
+ writeFileSync(join(root, ".keyoku", "project.yaml"), `schemaVersion: keyoku.dev/project/v1alpha1
+id: demo
+name: Demo
+summary: A test project
+createdAt: 2026-08-11T00:00:00.000Z
+updatedAt: 2026-08-11T00:00:00.000Z
+`);
+ writeFileSync(join(root, ".keyoku", "outcomes", "current.yaml"), `schemaVersion: keyoku.dev/outcome/v1alpha1
+id: current
+revision: 1
+title: Make the project understandable
+objective: A person can understand the current work
+owner:
+ kind: human
+ id: owner
+ name: Owner
+constraints: []
+criteria:
+ - description: A file exists
+ probe:
+ kind: command
+ run: test -f README.md
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+humanCriteria: []
+createdAt: 2026-08-11T00:00:00.000Z
+updatedAt: 2026-08-11T00:00:00.000Z
+`);
+ writeFileSync(join(root, ".keyoku", "runtime", "human-steering.jsonl"), `${JSON.stringify({
+ id: "steer_1",
+ kind: "direction",
+ message: "Prioritize the mobile view",
+ actor: { kind: "human", name: "Owner" },
+ createdAt: "2026-08-11T01:00:00.000Z",
+ status: "queued",
+ })}\n`);
+ return root;
+}
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
+});
+
+describe("project state", () => {
+ it("presents compact orientation with queued human steering", () => {
+ const root = projectRoot();
+ const orientation = buildProjectOrientation(root);
+ expect(orientation.project.name).toBe("Demo");
+ expect(orientation.currentGoal?.title).toBe("Make the project understandable");
+ expect(orientation.humanAttention.count).toBe(1);
+ expect(orientation.humanAttention.pendingSteering[0]?.message).toBe("Prioritize the mobile view");
+ });
+
+ it("keeps multiple goals active while focusing one independently of agent assignment", () => {
+ const root = projectRoot();
+ writeFileSync(join(root, ".keyoku", "outcomes", "parallel.yaml"), `schemaVersion: keyoku.dev/outcome/v1alpha1
+id: parallel
+revision: 1
+title: Ship a parallel outcome
+objective: Complete work without replacing the other goal
+owner: { kind: human, id: owner, name: Owner }
+constraints: []
+criteria:
+ - description: A parallel result exists
+ probe: { kind: command, run: "true" }
+ assert: { path: exitCode, op: eq, value: 0 }
+humanCriteria: []
+createdAt: 2026-08-11T00:00:00.000Z
+updatedAt: 2026-08-12T00:00:00.000Z
+`);
+ writeFileSync(join(root, ".keyoku", "runtime", "goal-focus.jsonl"), `${JSON.stringify({ eventType: "goal.focused", goalId: "current", createdAt: "2026-08-12T01:00:00.000Z" })}\n`);
+ heartbeatAgentSession({
+ root,
+ sessionId: "parallel-agent",
+ actor: { kind: "agent", id: "parallel-agent", name: "Parallel agent", harness: "test" },
+ status: "working",
+ currentWork: { outcomeId: "parallel", summary: "Working elsewhere" },
+ transport: "test",
+ });
+ const orientation = buildProjectOrientation(root);
+ expect(orientation.goals.count).toBe(2);
+ expect(orientation.currentGoal?.id).toBe("current");
+ expect(orientation.agents.active[0]?.currentWork?.outcomeId).toBe("parallel");
+ });
+
+ it("records an agent acknowledgement without rewriting the human request", () => {
+ const root = projectRoot();
+ const updated = acknowledgeSteering({
+ root,
+ steeringId: "steer_1",
+ status: "applied",
+ summary: "Mobile is now the first responsive breakpoint tested.",
+ actor: "test-agent",
+ });
+ expect(updated.status).toBe("applied");
+ expect(updated.acknowledgement?.actor).toBe("test-agent");
+ expect(listSteering(root)).toHaveLength(1);
+ expect(listSteering(root)[0]?.message).toBe("Prioritize the mobile view");
+ });
+
+ it("separates a committed intervention from understood, applied, and verified receipts", () => {
+ const root = projectRoot();
+ const actor = { kind: "human" as const, id: "owner", name: "Owner" };
+ const created = createIntervention({
+ root,
+ kind: "direction",
+ message: "Make the agent channel fully bidirectional",
+ actor,
+ deliveryPolicy: "next_checkpoint",
+ idempotencyKey: "owner-message-1",
+ });
+ expect(created.phase).toBe("committed");
+ expect(createIntervention({ root, kind: "direction", message: "duplicate", actor, idempotencyKey: "owner-message-1" }).id).toBe(created.id);
+
+ const understood = recordInterventionReceipt({
+ root,
+ interventionId: created.id,
+ phase: "understood",
+ summary: "I will add presence, durable delivery, and semantic receipts.",
+ actor: { kind: "agent", id: "agent-1", name: "Test agent", harness: "test" },
+ });
+ expect(understood.phase).toBe("understood");
+ expect(understood.receipts).toHaveLength(1);
+
+ const applied = recordInterventionReceipt({
+ root,
+ interventionId: created.id,
+ phase: "applied",
+ summary: "The protocol types and relay endpoints changed.",
+ actor: { kind: "agent", id: "agent-1", name: "Test agent", harness: "test" },
+ evidenceRefs: ["tests/project-state.test.ts"],
+ });
+ expect(applied.phase).toBe("applied");
+ expect(listInterventions(root)[0]?.receipts[1]?.evidenceRefs).toEqual(["tests/project-state.test.ts"]);
+ });
+
+ it("treats agent presence as a renewable lease instead of inferring it", () => {
+ const root = projectRoot();
+ const heartbeat = heartbeatAgentSession({
+ root,
+ sessionId: "session-1",
+ actor: { kind: "agent", id: "agent-1", name: "Test agent", harness: "test", model: "test-model" },
+ status: "working",
+ currentWork: { outcomeId: "current", summary: "Testing presence" },
+ capabilities: ["stream", "intervene"],
+ transport: "mcp-poll",
+ leaseSeconds: 30,
+ });
+ expect(heartbeat.active).toBe(true);
+ expect(listAgentSessions(root, new Date(heartbeat.createdAt))[0]?.active).toBe(true);
+ expect(listAgentSessions(root, new Date(Date.parse(heartbeat.leaseUntil) + 1))[0]?.active).toBe(false);
+ });
+
+ it("surfaces overlapping multi-agent work without pretending to lock Git", () => {
+ const root = projectRoot();
+ for (const [sessionId, path] of [["agent-a", "src"], ["agent-b", "src/server.ts"]]) {
+ heartbeatAgentSession({
+ root,
+ sessionId,
+ actor: { kind: "agent", id: sessionId, name: sessionId, harness: "test" },
+ status: "working",
+ currentWork: { summary: "Parallel work", paths: [path], baseSnapshot: "abc123" },
+ transport: "test",
+ });
+ }
+ const conflicts = findAgentCoordinationConflicts(listAgentSessions(root));
+ expect(conflicts).toHaveLength(1);
+ expect(conflicts[0]).toMatchObject({ reason: "overlapping_path", scope: "src" });
+ expect(new Set(conflicts[0]?.sessions)).toEqual(new Set(["agent-a", "agent-b"]));
+ });
+});
diff --git a/archive/legacy-omnigent/README.md b/archive/legacy-omnigent/README.md
new file mode 100644
index 0000000..c54eab4
--- /dev/null
+++ b/archive/legacy-omnigent/README.md
@@ -0,0 +1,25 @@
+# Legacy Omnigent fleet runner
+
+Archived: 2026-08-09
+Reason: Keyoku’s primary product is now a provider-neutral repository outcome and contribution gate. Owning a special-purpose Omnigent fleet/session/policy runtime made Keyoku look like another agent orchestrator and duplicated the responsibility of full agent-workplace products such as QM.
+
+## Contents
+
+- `src/run.ts` — create and drive Omnigent sessions
+- `src/dispatch.ts` — select an Omnigent agent
+- `src/omnigent-guardrails.ts` — install/remove Omnigent runtime policies
+- `src/policy-compiler.ts` — compile constraints into Omnigent policy handlers
+- `src/presets.ts` — Omnigent-only connector preset
+- `tests/` — the dedicated regression suite for those modules
+
+## What remains active
+
+- Provider-neutral MCP/OpenAPI connectors and their autonomy/approval controls
+- Machine-checkable command, HTTP, and MCP outcome probes
+- Constraints as human-readable contribution boundaries
+- Agent identity, harness, and model provenance
+- Deterministic goal assessment and workflow learning
+
+## Recovery
+
+The move preserves Git history. To revive this integration, copy the files back to `src/` and `tests/`, restore the exports/imports plus `run`, `converge`, `guardrails`, `connect` CLI commands and `goal_run`, `goal_converge`, `goal_guardrails` MCP tools, then update the active product contract and tests. Do not revive it as a hidden dependency of the provider-neutral gate.
diff --git a/src/dispatch.ts b/archive/legacy-omnigent/src/dispatch.ts
similarity index 100%
rename from src/dispatch.ts
rename to archive/legacy-omnigent/src/dispatch.ts
diff --git a/src/omnigent-guardrails.ts b/archive/legacy-omnigent/src/omnigent-guardrails.ts
similarity index 100%
rename from src/omnigent-guardrails.ts
rename to archive/legacy-omnigent/src/omnigent-guardrails.ts
diff --git a/src/policy-compiler.ts b/archive/legacy-omnigent/src/policy-compiler.ts
similarity index 100%
rename from src/policy-compiler.ts
rename to archive/legacy-omnigent/src/policy-compiler.ts
diff --git a/src/presets.ts b/archive/legacy-omnigent/src/presets.ts
similarity index 100%
rename from src/presets.ts
rename to archive/legacy-omnigent/src/presets.ts
diff --git a/src/run.ts b/archive/legacy-omnigent/src/run.ts
similarity index 100%
rename from src/run.ts
rename to archive/legacy-omnigent/src/run.ts
diff --git a/tests/dispatch.test.ts b/archive/legacy-omnigent/tests/dispatch.test.ts
similarity index 100%
rename from tests/dispatch.test.ts
rename to archive/legacy-omnigent/tests/dispatch.test.ts
diff --git a/tests/omnigent-guardrails.test.ts b/archive/legacy-omnigent/tests/omnigent-guardrails.test.ts
similarity index 100%
rename from tests/omnigent-guardrails.test.ts
rename to archive/legacy-omnigent/tests/omnigent-guardrails.test.ts
diff --git a/tests/policy-compiler.test.ts b/archive/legacy-omnigent/tests/policy-compiler.test.ts
similarity index 100%
rename from tests/policy-compiler.test.ts
rename to archive/legacy-omnigent/tests/policy-compiler.test.ts
diff --git a/tests/presets.test.ts b/archive/legacy-omnigent/tests/presets.test.ts
similarity index 100%
rename from tests/presets.test.ts
rename to archive/legacy-omnigent/tests/presets.test.ts
diff --git a/tests/run.test.ts b/archive/legacy-omnigent/tests/run.test.ts
similarity index 100%
rename from tests/run.test.ts
rename to archive/legacy-omnigent/tests/run.test.ts
diff --git a/docs/OUTCOME-ENGINE.md b/archive/legacy-positioning/OUTCOME-ENGINE.md
similarity index 100%
rename from docs/OUTCOME-ENGINE.md
rename to archive/legacy-positioning/OUTCOME-ENGINE.md
diff --git a/archive/legacy-positioning/README.md b/archive/legacy-positioning/README.md
new file mode 100644
index 0000000..bd38df2
--- /dev/null
+++ b/archive/legacy-positioning/README.md
@@ -0,0 +1,5 @@
+# Legacy positioning
+
+`OUTCOME-ENGINE.md` proposed a broad regulated-enterprise decision engine. It is preserved as strategy history but is no longer an active product promise.
+
+The current wedge is narrower and distributable: free repository-native outcomes, continuous contribution review, exact-snapshot evidence, human accountability, and portable Factfiles. Enterprise decisioning or security packs can later use the standard; they do not define Keyoku’s category.
diff --git a/docs/ASSURANCE-ADAPTER.md b/docs/ASSURANCE-ADAPTER.md
new file mode 100644
index 0000000..2a6d598
--- /dev/null
+++ b/docs/ASSURANCE-ADAPTER.md
@@ -0,0 +1,51 @@
+# Optional assurance adapter
+
+Keyoku can act as an optional evidence provider around a caller's work. It is not an
+agent runner, runtime-neutral protocol, scheduler, or control plane. A caller can use
+no assurance, its own basic checks, or Keyoku high assurance; that profile is caller
+policy and is deliberately absent from the neutral evidence envelope.
+
+## EvidenceProvider
+
+`evidence-provider/v1` accepts a neutral work identity and objective, claims, source
+or deployment snapshots, command **results**, artifact digests, limitations,
+authority, and a canonical content digest. Keyoku does not execute commands through
+this adapter. It deterministically returns `accepted`, `rejected`, `stale`, or
+`human_review_required`, with exact reason codes and a canonical result digest.
+Evaluation is side-effect free and does not mutate the caller's object.
+
+```sh
+keyoku factfile assess --file evidence.json --json
+```
+
+MCP exposes the same evaluator as `evidence_evaluate`. It has no shell-execution or
+human-acceptance capability.
+
+## Neutral WorkEvent bridge
+
+`work-event/v1` carries only neutral `dispatch`, `checkpoint`, `milestone`,
+`decision`, `regression`, `recovery`, `stale`, or `terminal` outcomes. The local sink
+stores validated, content-digested events in `.keyoku/pulse/work-events.jsonl` with
+idempotent IDs and conflict rejection. The local sink rejects symlinked storage
+paths, serializes writers with an exclusive fail-closed lock, appends through
+identity-checked descriptors, and fsyncs the ledger and parent directory. A crash
+may leave a `.lock` file; inspect it and confirm no writer is alive before removing
+it and retrying. This is cooperative same-user process safety, not containment
+against a malicious process running as that OS user:
+
+```sh
+keyoku pulse work-event ingest --file event.json --root /project --json
+keyoku pulse work-event list --root /project --json
+```
+
+MCP exposes the same functions as `pulse_work_event_ingest` and
+`pulse_work_event_list`. WorkEvents are coordination input: they never promote
+themselves into a verified Factfile or dispatchable Pulse snapshot.
+
+An HTTP/webhook integration may wrap these pure functions in caller-owned transport,
+authentication, replay protection, and authorization. Keyoku core does not start a
+webhook listener, authenticate arbitrary runtimes, send delivery, or prescribe a
+neutral agent protocol.
+
+See `fixtures/assurance/v1` for generic synthetic data. The fixture names no agent
+product or harness and is not live proof.
diff --git a/docs/FACTFILE-STANDARD.md b/docs/FACTFILE-STANDARD.md
new file mode 100644
index 0000000..746b23b
--- /dev/null
+++ b/docs/FACTFILE-STANDARD.md
@@ -0,0 +1,217 @@
+# Keyoku Factfile Standard
+
+Status: `v1alpha1`
+License: MIT
+Scope: any Git repository, public or private
+
+A Factfile is a portable, human-readable receipt for a software contribution. It connects a versioned intended outcome to accountable actors, relevant artifacts, deterministic observations, review history, and the exact repository snapshot those observations cover.
+
+A Factfile proves one bounded checkpoint. [Keyoku Pulse](PULSE.md) is the separate temporal layer that carries trusted progress across multiple Factfile-bound checkpoints and agent harnesses. Pulse activity never changes what a Factfile establishes.
+
+For command-backed claims, Keyoku captures the complete Git-visible source tree
+(tracked plus non-ignored untracked bytes, paths, symlinks, and executable
+modes) into one SHA-256 content-addressed capsule. Each command runs
+sequentially in a fresh disposable checkout. A write, add, delete, mode change,
+mutate-restore, original-tree race, or unsupported source entry rejects the
+proof. The capsule isolates evidence bytes; it does not sandbox arbitrary code
+from the caller's operating-system, network, or external-file authority. A probe
+that deliberately daemonizes or escapes its process group is outside the trusted
+repository-command support boundary.
+
+It is not an AI-generated claim that a project is “good.” The canonical JSON records bounded facts. HTML and Markdown explain those facts at the level of detail a recipient needs.
+
+## The standard method
+
+1. **Declare the outcome.** Write one human-owned objective, its constraints, automated criteria, and any required human judgment criteria under `.keyoku/outcomes/`.
+2. **Open a contribution.** Bind work to an outcome revision and base Git SHA. Record the responsible human and any contributing agents, harnesses, or models.
+3. **Work in any harness.** Keyoku does not prescribe Claude Code, Codex, Cursor, CI, a custom agent, or human-only development.
+4. **Coordinate without pretending activity is proof.** Agents report work, request only material human decisions, and poll for durable instructions. These events remain separate from evidence.
+5. **Evaluate continuously.** Reuse one active contribution per branch and outcome; run the gate after meaningful iterations. Failed and incomplete probes fail closed.
+6. **Render the receipt.** Store canonical JSON and generate Markdown and HTML views from the same record.
+7. **Review as a human.** Automated proof can move work only to `human_review_required` when judgments remain. Named people record those verdicts; only after all required gates pass can the snapshot be accepted.
+8. **Re-evaluate after change.** A later Git head or worktree digest is a different proof scope. Old evidence remains history, never silently applies to new code.
+
+## Canonical hierarchy
+
+```text
+Project
+└── Outcome (versioned definition of done)
+ └── Contribution (bounded attempt)
+ ├── Actors (human, agent, organization)
+ ├── Session events (work, decisions, instructions, presence)
+ ├── Repository snapshot (base, head, worktree digest)
+ ├── Automated evidence (claim + explanation + artifact + audit trail)
+ ├── Human criteria (named judgment + guidance + verdict)
+ ├── Reviews (human decisions and comments)
+ └── Factfile snapshots (append-only history)
+```
+
+## Repository layout
+
+```text
+.keyoku/
+├── project.yaml
+├── policy.yaml
+├── outcomes/
+│ └── .yaml
+├── contributions/
+│ └── /
+│ ├── manifest.yaml
+│ ├── events.jsonl
+│ ├── reviews.jsonl
+│ ├── snapshots/.json
+│ ├── factfile.json
+│ ├── factfile.github.md
+│ ├── factfile.md
+│ └── factfile.html
+├── pulse/
+│ └── events.jsonl # optional harness-neutral progress ledger
+└── runtime/ # local evaluator state; never canonical proof
+```
+
+Projects normally commit the project, policy, and outcome files. A project decides whether to commit contribution receipts or attach them to pull requests/releases. The local runtime is implementation state and should not be published.
+
+## Required records
+
+### Project
+
+- Stable `id`, display `name`, and plain-language `summary`
+- Optional repository URL and default branch
+- Creation and update timestamps
+
+### Outcome
+
+- Stable `id` and explicit positive `revision`
+- Human-readable `title` and `objective`
+- Accountable `owner`
+- Constraints that bound acceptable work
+- One or more automated criteria
+- Zero or more required human judgment criteria with stable ids and review guidance
+
+Editing meaning, constraints, or criteria requires a new revision. A contribution never silently moves to a newer revision.
+
+The outcome file is repository-owned. Its canonical revision history is the Git history of `.keyoku/outcomes/.yaml`; inspect it directly with `git log -- .keyoku/outcomes/.yaml` without creating a second source of truth.
+
+An outcome may declare a deterministic path boundary with `scope.include`, `scope.exclude`, and `scope.maxChangedFiles`. Paths outside that boundary fail the gate. This catches mechanical scope drift but does not claim that a change is semantically coherent; projects should keep coherence as a human criterion.
+
+### Actor
+
+- `kind`: `human`, `agent`, or `organization`
+- Stable `id` and display `name`
+- Optional role
+- Agent provenance may include `harness` and `model`
+- An agent should identify a human or organization `ownerId`
+
+Agent identity is provenance, not personhood or legal accountability.
+
+### Evidence contract
+
+Every machine-evaluated claim uses the same reading order:
+
+1. **Claim** — the bounded behavior or property being evaluated
+2. **What this shows** — the result in language a maintainer can explain
+3. **Why it matters** — its relevance to the requested outcome
+4. **Artifacts** — screenshots, short recordings, traces, reports, logs, or other inspectable output when appropriate
+5. **Code context** — the paths that deliver the behavior and what each one is responsible for
+6. **Audit details** — the probe, observed value, assertion rule, duration, and error
+
+The Factfile stores a safe reproduction description for each observation. Repository commands are shown directly; HTTP and MCP probes are represented without publishing request credentials. Referenced artifacts must exist inside the project and are SHA-256 content-bound before they are presented as available evidence. A screenshot or short MP4/WebM recording may additionally be embedded in the portable HTML view within the documented size limit. Screenshots may carry percentage-based callouts; recordings may carry timestamped callouts. An annotation explains what a reviewer should notice but remains a demonstration, not an independent verifier.
+
+The artifact type follows the claim. Visible behavior normally needs a screenshot or rendered capture. Runtime behavior needs a test or trace. Architecture needs a code tour or diff. Security needs the relevant scanner report and scope. A raw exit code by itself is audit data, not a useful human explanation.
+
+Every criterion evaluation therefore records:
+
+- Plain-language description
+- Human-facing result summary and relevance
+- Zero or more evidence artifacts with labels, captions, paths, and digests where available
+- Zero or more code references with an explanation of responsibility
+- Observed value
+- Expected assertion path, operator, and value
+- Pass/fail verdict
+- Runtime duration
+- Probe or evaluation error, when present
+
+Raw logs may remain private. Published evidence must be enough to understand the verdict without opening the audit details and without exposing credentials, transcripts, customer data, or unrelated source.
+
+### Two-way session protocol
+
+The mutable live session and immutable Factfile snapshot have different jobs. The live session coordinates the next iteration; each gate captures its then-current state into a content-addressed Factfile.
+
+- A work item has a stable id, actor, status (`queued`, `working`, `blocked`, or `done`), detail, and update time. It is agent-reported activity, never completion evidence.
+- A decision request states what the agent wants, what blocks it, why a human must decide, bounded options, a recommendation when available, and the consequence of no response.
+- A human resolution creates a queued instruction. The choice is not considered delivered until an agent receives and acknowledges that instruction.
+- A free-form steering instruction uses the same durable queue. It may target one agent or be available to the next connected agent.
+- A heartbeat describes presence only. Keyoku considers it connected for a short lease; absence never discards queued work or instructions.
+- Optional steering is separate from **Needs you**. A renderer may derive suggested next directions from attention signals, evidence gaps, architecture, and pending acceptance criteria. Each suggestion explains its expected outcome effect, deep context, and tradeoffs before it becomes an instruction. Custom direction remains available without presenting an empty prompt box as a blocker.
+
+The MCP surface is provider-neutral: `contribution_report_work`, `contribution_request_decision`, `contribution_propose_directions`, `contribution_next_instruction`, and `contribution_ack_instruction`. The working agent proposes contextual next moves before the final gate; the Factfile records the concise evidence-grounded rationale and references, never private chain-of-thought. Hooks may improve immediacy for a particular harness, but the protocol does not require them.
+
+### Human judgment
+
+Not every meaningful property reduces to an exit code. Product fit, visual quality, maintainability, risk acceptance, and contextual correctness can be declared as human criteria. Each verdict records the criterion id, identified human reviewer, pass/fail judgment, reason, time, Factfile digest, and repository snapshot it reviewed. Agents cannot satisfy these criteria.
+
+The portable HTML receipt can copy an instruction but cannot mutate its historical snapshot. A token-scoped local live session may present decision controls. Those controls append decision and instruction events; they never rewrite an earlier snapshot. Exact-snapshot acceptance still rejects a stale Factfile digest.
+
+### Repository snapshot
+
+The proof scope includes:
+
+- Contribution base SHA
+- Current Git head SHA
+- Whether source is dirty
+- Changed paths
+- SHA-256 worktree digest over the complete Git-visible source capsule:
+ sorted tracked and non-ignored untracked paths, exact bytes, executable modes,
+ and internal relative symlink targets
+
+Generated Factfiles, Pulse ledgers, and evaluator runtime are excluded from the
+worktree digest so proof bookkeeping cannot invalidate its own source identity.
+Project, policy, outcome, and architecture contracts remain included.
+
+## States
+
+| State | Meaning |
+|---|---|
+| `draft` | Work is open; no current evaluation is claimed |
+| `evaluating` | Probes are running |
+| `evidence_gaps` | One or more declared criteria did not pass |
+| `human_review_required` | Automated evidence passed; one or more required human judgments are pending |
+| `review_blocked` | Automated evidence passed; a required human judgment failed |
+| `ready_for_review` | Automated and required human criteria passed; the snapshot is ready for acceptance |
+| `accepted` | An accountable human or organization accepted that snapshot |
+
+Review events append to `reviews.jsonl` and every rendered Factfile includes the resulting timeline. `keyoku proof review` records an identified human note; `keyoku proof accept` records acceptance only when the current source still exactly matches a passing Factfile. A source change after `ready_for_review` or `accepted` requires re-evaluation.
+
+## Claim language
+
+Allowed:
+
+> 8 of 8 automated checks passed; 1 of 2 required human judgments passed for Git head `abc123` plus worktree digest `def456`. Human review remains required.
+
+Not allowed:
+
+> The AI proved this project is secure and correct.
+
+A security scan, test suite, or sandbox evaluation supports only the property it actually checked. Severe findings may block readiness even when other checks pass, but an absence of findings is never universal safety proof.
+
+## Human views
+
+All views derive from canonical records and answer these maintainer questions first:
+
+- What are agents doing now, and is that status merely reported or actually proven?
+- Which decision is truly blocked on me, why, and what happens if I do nothing?
+- What was requested?
+- What changed?
+- What is actually supported, and by which artifacts?
+- Who or what did the work, and which human is accountable?
+- What does the reviewer need to decide?
+
+Raw assertions, hashes, changed paths, and verifier output remain available as collapsed audit detail. They must not displace the human explanation.
+
+## Interoperability
+
+Keyoku is Git-provider and harness neutral. A GitHub Action may attach the compact `factfile.github.md` summary and full HTML artifact to a pull request. GitLab, Forgejo, CI systems, or an agent runtime can consume the same canonical JSON and exit semantics. `keyoku-engine` may mirror snapshots for shared live views, but the local canonical record must remain usable without it.
+
+## Versioning
+
+Schemas use identifiers such as `keyoku.dev/outcome/v1alpha1`. Additive fields may appear during alpha. Incompatible meaning or required-field changes receive a new schema version. A verifier must reject unsupported versions rather than guessing.
diff --git a/docs/GITHUB.md b/docs/GITHUB.md
new file mode 100644
index 0000000..8701609
--- /dev/null
+++ b/docs/GITHUB.md
@@ -0,0 +1,71 @@
+# GitHub integration
+
+Keyoku’s first distribution surface is a normal GitHub Check, reviewer-first job summary, and downloadable Factfile artifact. It does not require a GitHub App or hosted Keyoku account.
+
+Install it from any Git repository in one command:
+
+```bash
+keyoku proof init
+```
+
+The workflow generated by the unreleased source candidate pins
+`keyoku@3.0.0-alpha.1`; it becomes runnable only if that exact package is
+separately approved and published to npm `next`. It never falls through to
+`latest`.
+
+After a stable `v3` tag exists, the Marketplace-compatible composite action can
+be used directly after installing project dependencies:
+
+```yaml
+- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+- uses: Keyoku-ai/keyoku@v3 # available only after the stable v3 release
+ with:
+ outcome: review-ready-change
+ base: ${{ github.event.pull_request.base.sha }}
+```
+
+The action writes the native job summary, exposes `contribution-id`, `state`, and `factfile` outputs, and uploads the portable Factfile bundle. It requests no repository write permission.
+
+Keyoku detects Node.js, Python, Rust, Go, or a generic Git project, creates a starter outcome contract, and writes `.github/workflows/keyoku-proof.yml`. Review the generated contract before treating it as definition of done. The workflow:
+
+1. Checks out the proposed revision.
+2. Installs the detected project dependencies.
+3. Opens an ephemeral contribution attributed to GitHub Actions and binds it to the pull request base SHA.
+4. Runs the repository-owned outcome contract.
+5. Adds compact `factfile.github.md` to the native GitHub job summary.
+6. Uploads JSON, GitHub Markdown, detailed Markdown, and HTML Factfiles as one artifact.
+7. Fails the required check when machine evidence gaps or an explicit human block remain.
+
+`human_review_required` does not fail the machine Check: it means the declared observations passed and normal GitHub human review still owns the decision. The summary makes that boundary visible.
+
+Use GitHub's existing review controls for PR acceptance. For local agent steering, open the contribution id printed by `proof run`:
+
+```bash
+keyoku proof serve
+```
+
+The local session keeps agent work, genuine blockers, attention signals, and proof separate. Human choices become durable MCP instructions; GitHub remains the source of truth for code review and merge.
+
+In GitHub:
+
+- **Approve** when the requested outcome is satisfied by the current exact revision.
+- **Request changes** with the next concrete instruction when it is not.
+- The agent or developer pushes another iteration; Keyoku automatically produces a new SHA-bound Factfile.
+
+This keeps the first release GitHub-native without requiring a privileged Keyoku App or a second conversation UI.
+
+## Security boundary
+
+Outcome probes execute commands from the checked-out repository. Treat them like test scripts. The example workflow intentionally grants only `contents: read`; it does not give untrusted pull-request code a token that can comment, merge, publish, or modify the repository.
+
+An automatic pull-request comment would require a separate privileged workflow that only reads a previously generated artifact and validates its digest. Do not combine untrusted probe execution with `pull-requests: write` merely for a nicer comment.
+
+## Branch protection
+
+After the workflow has run once, make `Keyoku proof / Keyoku / outcome proof` a required status check in the repository’s branch rules. This makes the outcome contract the contribution gate while preserving GitHub as the source-code host.
+
+## Public credit
+
+The artifact records actors, harness/model provenance, exact source scope, and evidence. A project may also commit accepted Factfiles or link the artifact from a release. Avoid publishing raw transcripts, secrets, customer data, or unrelated source output.
diff --git a/docs/ITERATION.md b/docs/ITERATION.md
new file mode 100644
index 0000000..39ab622
--- /dev/null
+++ b/docs/ITERATION.md
@@ -0,0 +1,61 @@
+# Keyoku behavior-iteration compatibility contract
+
+> This controller remains implemented and regression-tested as compatibility
+> source, but its CLI and MCP verbs are not part of the bounded v3 public
+> surface. v3 expresses incremental work through repeated `keyoku proof run`
+> checkpoints plus Pulse. See [PUBLIC-SURFACE.md](PUBLIC-SURFACE.md).
+
+Status: `v1alpha1`
+
+Behavior iteration is Keyoku's provider-neutral loop for getting a software product from an observed evidence gap to a bounded, reviewable outcome. Keyoku owns the outcome contract, evidence rounds, stop policy, and receipt. A coding harness owns execution.
+
+## Protocol
+
+1. A human or harness starts a session for one repository-owned outcome.
+2. Keyoku opens one contribution and runs every declared probe against the exact source state.
+3. If evidence is missing, Keyoku emits a deterministic instruction containing the objective, constraints, failed claims, reproduction commands, regressions, and exact Git/worktree identity.
+4. Any MCP-capable harness can fetch that instruction, perform product work, and report one idempotent checkpoint.
+5. Keyoku records explicitly sourced usage, reruns the proof, and either emits the next instruction or stops.
+
+The CLI and MCP surfaces use the same implementation:
+
+| CLI | MCP |
+|---|---|
+| `keyoku iterate start ` | `iteration_start` |
+| `keyoku iterate status ` | `iteration_status` |
+| `keyoku iterate next ` | `iteration_next` |
+| `keyoku iterate checkpoint ...` | `iteration_checkpoint` |
+
+## Ledger and idempotency
+
+Events live under `.keyoku/runtime/iterations//events.jsonl`. This location is intentionally local evaluator state and is excluded from source proof. Every event has a monotonic sequence, the previous event digest, and its own canonical SHA-256 digest. Replay verifies the full chain before returning state.
+
+Checkpoint ids are idempotency keys. Replaying an identical checkpoint returns the existing state without another proof round or another usage charge. Reusing the id with a different summary, usage, or provenance fails closed.
+
+Each proof round records:
+
+- contribution and portable Factfile identity;
+- exact Git head, worktree digest, dirty flag, and changed paths;
+- automated pass/fail totals;
+- pending, passing, and failed human judgments;
+- indexes of passing, failing, and newly regressed claims;
+- consecutive checkpoints that produced no source change.
+
+## Stop semantics
+
+The controller is bounded by default to five rounds, one hour, and two consecutive no-progress checkpoints. Callers may lower or raise those limits within schema bounds and may add reported-token or reported-cost ceilings.
+
+Terminal states are intentionally specific:
+
+- `ready_for_review`: declared automated and human criteria pass for the exact snapshot; accountable acceptance is still separate.
+- `human_review_required`: machine evidence passes, but one or more declared human judgments are pending.
+- `review_blocked`: machine evidence passes, but an accountable human judgment failed.
+- `stopped_round_limit`, `stopped_time_limit`, `stopped_no_progress`, `stopped_token_limit`, or `stopped_cost_limit`: the named bound ended the loop while evidence remained incomplete.
+
+## Usage boundary
+
+An agent checkpoint may report input, output, and cached-input tokens, tool calls, and cost. It must label the source as `provider_receipt`, `agent_reported`, or `unknown`. These values are operational bounds, not settled billing. Keyoku does not derive them from rendered chat messages or claim a provider receipt when none exists.
+
+## Authority boundary
+
+Keyoku never executes arbitrary agent commands in this protocol. It does not write product source, decide human criteria, accept a Factfile, push Git commits, deploy, or bypass application authorization. Those actions remain with the connected harness and accountable human. This separation lets Keyoku become the iteration layer without turning proof output into an autonomous actor or confusing agent activity with evidence.
diff --git a/docs/PRODUCTION-READINESS.md b/docs/PRODUCTION-READINESS.md
index ff4e626..cbf0b5b 100644
--- a/docs/PRODUCTION-READINESS.md
+++ b/docs/PRODUCTION-READINESS.md
@@ -1,89 +1,88 @@
-# Production readiness
-
-Honest status of keyoku as a product other people depend on. The convergence
-engine is production-grade; this tracks the **operational shell** around it.
-Grouped by who can close each item: ✅ done · 🟡 needs maintainer (one-time,
-out of code) · 🔵 next in code.
-
-## Tier 1 — table stakes
-
-### ✅ Release integrity (done — 2.8.0)
-- `npm run preflight` + a CI step in `release.yml` verify, before any tag ships:
- valid semver, CHANGELOG entry present, `VERSION` single-sourced from
- package.json (the 2.7.1 "0.1.0" drift can't recur), the **built artifact
- reports the right version**, and `dist` is in the tarball.
-- The release workflow already runs typecheck → test → eval → preflight.
-
-### 🟡 npm Trusted Publishing (maintainer, ~2 min, one-time)
-CI **cannot publish** today — there is no `NPM_TOKEN` and no Trusted Publishing
-configured, so every release so far went out via the maintainer CLI
-(`npm publish`), with **no provenance**. The workflow is already OIDC-ready
-(`id-token: write`, npm upgraded). Close it once:
-
-> npmjs.com → package **`keyoku`** → Settings → **Trusted Publishing** → add
-> repository **`Keyoku-ai/keyoku`**, workflow **`release.yml`**.
-
-After that, pushing a `vX.Y.Z` tag publishes automatically, tokenless, with
-provenance. (Full detail in `docs/PUBLISHING.md`.)
-
-### 🟡 Repo consolidation (maintainer)
-Two working trees clone the same repo: `~/Development/Keyoku/keyoku-harness`
-(the one the **live MCP server runs** — `dist/index.js`) and
-`~/Development/Keyoku Harness/keyoku-harness` (used for site deploys). Editing
-the wrong one ships nothing. Pick one canonical checkout; it's a foot-gun for
-any contributor. See `docs/REPO-MAP.md`.
-
-### ✅ Store growth bounds (done, pre-existing)
-`activity.jsonl` self-caps (trims at ~2.5 MB → last 8k events, `store.ts`);
-observations cap per goal (500 → 400); `audit.jsonl` caps at ~1 MB (v2.12.1);
-`executions.json` caps at 200 terminal runs while keeping all in-flight ones
-(2.17.x). A large file on disk just means the *old* global install (pre-cap)
-was writing it — the current build trims it. Remaining minor gap (🔵):
-`knowledge.jsonl` is still unbounded (small today); add the same size-cap if it grows.
-
-### 🔵 Store concurrency
-The JSON store is synchronous, atomic (tmp+rename), cacheless, last-writer-wins
-per entry — fine for one machine / a few sessions. Team or heavy multi-session
-use wants real atomic ops or a SQLite backend (the `Store` interface is already
-the seam for that swap). Documented in `store.ts`.
-
-## Tier 2 — the product promise (learning that compounds)
-
-### ✅ Self-pruning (done — 2.8.0)
-Suggestions now rank by `similarity × precision`, where precision is learned
-from whether a workflow's steps actually recur in later converged goals. A
-workflow that only ever word-matches sinks. This is the first real
-**outcome-grounded** signal in retrieval. (`recordSuggestionOutcomes` +
-`suggestWorkflows` in `engine.ts`.)
-
-### 🔵 Semantic retrieval
-Recall is still jaccard (token overlap) — the lite-model re-rank layers on top
-but needs a key. Embeddings via the BSL engine/brain would finish the
-"no-heuristics" path for the offline/no-key case.
-
-### 🔵 Validation depth
-Lift is proven directionally (behavioral planning 1/3→3/3, one execution run,
-both n=1). A production claim wants multi-trial runs, a weaker-model row, and a
-behavioral metric tracked over time — today only the deterministic retrieval
-eval gates CI (`npm run eval`); the value evals are manual.
-
-## Tier 3 — trust & adoption
-
-### 🔵 Data trust (it reads your activity)
-Secret redaction exists at record time (`activity.ts`). Still wanted: a
-retention policy, scoping/opt-out controls, and a `keyoku inspect` that shows
-exactly what's in `~/.keyoku`. For a tool that ingests computer activity (and
-the basis for Lar's onboarding), this is make-or-break before broad promotion.
-
-### 🔵 Docs / site currency
-2.2→2.8 shipped a lot (goal_focus, pitfalls, re-rank, self-pruning). Per the
-ship-propagation rule, the site/README/quickstart should teach the new
-flagship (`goal_focus`) and land a <5-min "wow".
-
----
-
-**One-liner:** the engine is production-grade; Tier 1 is now mostly closed
-(release integrity ✅, growth bounds ✅) with two small **maintainer** toggles
-left (Trusted Publishing, repo consolidation). Tier 2's differentiator
-(self-pruning) has its first version shipped. Tiers 2–3 remaining are roadmap,
-not blockers.
+# Production-readiness gate
+
+Status: **unreleased v3 candidate — not approved for publication**
+Updated: 2026-08-25
+
+This document describes the narrow v3 assurance product. It supersedes the old
+v2 claim that Keyoku's memory/convergence engine was production-grade. The
+public npm `latest` and public website remain on v2 until the owner approves one
+exact, immutable replacement candidate.
+
+## Product boundary
+
+Keyoku is an independent proof, attention, and stakeholder-presentation layer
+around autonomous work. It does not run agents, schedule work, own a control
+plane, or mutate an orchestrator's state. A neutral system may select no
+assurance, basic receipt checks, or Keyoku high assurance through the optional
+EvidenceProvider and WorkEvent interfaces.
+
+The MIT package provides local Factfiles, Pulse planning/rendering, the CLI,
+MCP tools, schemas, fixtures, and adapters. The separately licensed Engine is
+optional durable multi-project storage/API infrastructure. The local path must
+remain fully useful without Engine.
+
+## Declared candidate matrix
+
+| Surface | Candidate target | Current evidence | Release position |
+|---|---|---|---|
+| Local CLI/library/MCP | Node.js 20 and 22 on Linux and macOS, Git repositories using regular files, internal relative symlinks, and tracked/nonignored untracked source | Full local tests and clean-package runs have passed during integration; Ubuntu CI is configured but has not run on an immutable v3 commit | Candidate only |
+| Source capsule probes | Reviewed repository commands, executed sequentially in fresh disposable checkouts | Dirty bytes, odd paths, executable modes, internal symlinks, mutation, mutate-restore, isolation, source races, unsupported entries, and timeout cleanup have regression coverage | Candidate only; not an OS sandbox |
+| Pulse local ledger | One repository-local installation with cooperative same-user writers | Canonical event validation, replay/idempotency, conflict rejection, linked-path rejection, exclusive write lock, bounded descriptor-anchored append, and fsync | Candidate only; not a distributed queue or malicious same-user containment boundary |
+| Engine | Supported Go toolchain and SQLite on Linux/macOS | Unit, race, vet, lint, semantic Factfile checks, corpus conformance, and backup/restore rehearsal passed during integration | Candidate only; hosted operations not proven |
+| Website | Current evergreen Chrome/WebKit-class browsers, keyboard, reduced motion, mobile reflow | Local lint/build/browser checks and original-resolution review passed during integration | Private replacement deployment still pending |
+| Windows | Not declared for v3 alpha | No exact-candidate Windows execution evidence | Unsupported until proven; fail reports are requested |
+
+## Required release gates
+
+The release candidate is publishable only when all of the following evidence is
+bound to committed revisions and exact archives:
+
+- Public CLI, API, Factfile, Pulse, decision, replay, export, and adapter flows
+ pass from clean install, including failure, stale proof, recovery, and upgrade
+ boundaries.
+- Generated JSON, JSONL, Markdown, HTML, manifests, receipts, and reports parse
+ and execute in their native consumers; tampering and semantic mismatches fail
+ closed.
+- Harness and Engine consume the same conformance corpus byte for byte and
+ produce the same canonical outcomes.
+- Dependency, secret, path, permission, concurrency, race, canonicalization,
+ and supply-chain checks pass for the exact candidate.
+- Human UX, terminal UX, keyboard operation, screen-reader semantics, contrast,
+ reduced motion, 200% zoom, narrow mobile layout, long evidence, and recovery
+ messages receive direct review.
+- Engine backup/restore, migrations, rollback, health checks, resource budgets,
+ and deployment runbooks are rehearsed against the final revision if Engine is
+ included in the release.
+- Exact archives install on clean Node 20 and 22 environments; checksums, SBOMs,
+ version mapping, release notes, migration guidance, and rollback instructions
+ agree across all repositories.
+- A fresh agent and a human independently exercise the exact final archives and
+ interactive product journey. Any source change invalidates that evidence.
+
+## Open publication blockers
+
+- Harness, Engine, and site now have local integration commits, but public CI has
+ not run those unpublished revisions and independent exact-archive acceptance
+ is still pending. The external evidence packet must record their final mapping.
+- Private owner-only deployment of the corrected interactive site is pending.
+- No verified private vulnerability intake exists. Enable GitHub private
+ vulnerability reporting or approve and verify a monitored security contact.
+- Engine licensing/licensor and repository-ownership posture require an explicit
+ owner decision before the public v3 sequence.
+- Final exact-revision archive, SBOM, release notes, migration, rollback, and
+ independent acceptance packets have not yet been issued.
+- npm Trusted Publishing and the release candidate itself have not been approved.
+
+## Rollback boundary
+
+Do not move npm `latest`, replace the public website, merge protected branches,
+or delete the v2 line while v3 is under review. A prerelease, if approved, must
+use the `next` dist-tag. The public v2 repository/tag/package/site combination is
+the rollback boundary until v3 is separately proven and accepted.
+
+## Release verdict
+
+**NO-GO for public release today.** The coherent local product slice is real,
+but publication requires exact committed revisions, rerun gates from final
+archives, corrected private deployment, independent acceptance, and the owner
+decisions listed above.
diff --git a/docs/PUBLIC-SURFACE.md b/docs/PUBLIC-SURFACE.md
new file mode 100644
index 0000000..6f3a102
--- /dev/null
+++ b/docs/PUBLIC-SURFACE.md
@@ -0,0 +1,66 @@
+# Keyoku v3 public surface
+
+This document is a release boundary, not a roadmap. The executable inventory in
+[`src/public-surface.ts`](../src/public-surface.ts) drives the public CLI help and
+MCP registration. Tests compare the built entrypoint with that inventory.
+
+## CLI
+
+| Command | Bounded responsibility |
+|---|---|
+| `keyoku proof …` | Create, run, review, accept, and present proof for one repository-owned outcome |
+| `keyoku factfile inspect …` | Validate and explain one content-bound Factfile |
+| `keyoku factfile verify …` | Also require the current Git head and worktree to match the Factfile |
+| `keyoku factfile assess …` | Evaluate one neutral evidence envelope without running commands or changing caller state |
+| `keyoku factfile publish …` | Explicitly publish a verified Factfile to an optional Engine endpoint |
+| `keyoku pulse …` | Ingest lifecycle events, verify checkpoints, plan dispatch, and render projections without sending |
+| `keyoku serve` | Serve the bounded MCP tool set over stdio |
+| `keyoku doctor` | Report installation, project, optional Engine, and authority boundaries |
+| `keyoku version` / `keyoku help` | Discovery only |
+
+`proof review` and `proof accept` require an identified human on the local CLI.
+They are deliberately absent from MCP. Any source change makes the prior
+Factfile stale and requires a new proof run before review or acceptance.
+
+## MCP
+
+The v3 server exposes thirteen tools:
+
+- Proof-session coordination: `contribution_report_work`,
+ `contribution_request_decision`, `contribution_next_instruction`,
+ `contribution_ack_instruction`, and `contribution_gate`.
+- Assurance: `evidence_evaluate`.
+- Pulse: `pulse_event_ingest`, `pulse_checkpoint_publish`,
+ `pulse_work_event_ingest`, `pulse_work_event_list`, `pulse_status`,
+ `pulse_dispatch_plan`, and `pulse_projection_render`.
+
+There is no MCP tool that accepts human review, runs an agent, changes delivery
+authority, sends a message, manages connectors, executes a learned workflow, or
+operates the v2 memory/goal system.
+
+The optional assurance adapter is not a runtime-neutral agent standard. Callers
+retain work orchestration and choose `none`, `basic`, or
+`keyoku_high_assurance` in their own policy. That profile is not required by or
+encoded in the neutral evidence envelope.
+
+The human or local workflow opens the contribution with `keyoku proof run` and
+passes its id to the coding harness. MCP deliberately cannot create a free-form
+goal or silently choose the repository's definition of done.
+
+## Compatibility boundary
+
+The v2 goal, workflow, connector, activity, memory, and execution implementation
+remains in source for regression and migration work. Its test entrypoint is not
+listed in `package.json` files and is not part of the v3 npm archive. Until the
+owner approves a v3 release, npm `latest` remains on the v2 release line. The
+explicit rollback for a future v3 alpha is `npm install keyoku@2`.
+
+The bounded v3 verifier runs repository command and HTTP criteria. Command
+criteria run sequentially in fresh disposable checkouts from one exact source
+capsule and must not write to that checkout. This is evidence isolation, not an
+OS sandbox: arbitrary commands still have the caller's user and network
+authority, and daemonizing or process-group-escaping commands are outside the
+trusted repository-command support boundary. A legacy MCP
+criterion fails closed because connector management is not shipped or registered
+in the v3 entrypoint; migrate that observation behind a repository-owned command
+or HTTP check before adopting v3.
diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md
index 1ab5cea..785c5d5 100644
--- a/docs/PUBLISHING.md
+++ b/docs/PUBLISHING.md
@@ -1,51 +1,45 @@
-# Publishing
+# Publishing Keyoku v3
-Releases are tag-driven. Pushing a `v*` tag runs `.github/workflows/release.yml`,
-which typechecks, tests, runs the muscle-memory eval (a hard quality gate), and
-then publishes to npm. The workflow is **idempotent** — it skips a version that is
-already on npm — and it **never reports a false success**: if no publish auth is
-configured it emits a loud warning and exits 0 rather than pretending it shipped.
+This repository is an unpublished v3 candidate. npm `latest` remains on the v2
+release line. A passing build or preflight is evidence for a candidate, not
+authorization to tag, push, publish, or move a dist-tag.
-## Cut a release
+## Candidate sequence
-1. Bump `version` in `package.json` and add a `CHANGELOG.md` entry.
-2. Commit, then tag and push:
- ```bash
- git tag vX.Y.Z
- git push origin vX.Y.Z
- ```
-3. Watch the **Release** workflow. Green + "Published keyoku@X.Y.Z with provenance"
- means done.
+1. Freeze exact candidate revisions for `keyoku`, `keyoku-engine`, and
+ `keyoku-site` without changing the existing public release.
+2. Run typecheck, full tests, public-surface inventory checks, security gates,
+ clean archive install, generated-output execution, browser acceptance, and
+ the declared support matrix against those exact revisions.
+3. Generate release notes, migration guidance, checksums, SBOM/provenance where
+ supported, rollback instructions, and a revision-bound evidence manifest.
+4. Obtain the owner's explicit approval for that exact candidate and resolve
+ any licensing or repository-posture decision.
+5. Publish an alpha to the `next` dist-tag. Do not move `latest`.
+6. Verify clean installation with `npm install keyoku@next`, the bounded CLI and
+ MCP inventories, `proof demo`, Factfile stale rejection, and Pulse replay.
+7. Only after alpha acceptance, separately approve a stable v3 tag and any
+ `latest` dist-tag change.
-## One-time auth setup (the only manual gap)
+## Rollback
-The workflow code is complete; npm just needs to trust it. Pick **one**:
+The stable rollback boundary is explicit:
-### Option A — Trusted Publishing (recommended: tokenless + provenance)
-
-No secret to manage. On npmjs.com:
-
-> package **`keyoku`** → **Settings** → **Trusted Publishing** → add publisher:
-> repository **`Keyoku-ai/keyoku`**, workflow **`release.yml`**.
-
-The workflow already requests the OIDC token (`permissions: id-token: write`) and
-upgrades npm to a version that supports OIDC, so once this is added the next tag
-publishes automatically with provenance.
-
-### Option B — `NPM_TOKEN` secret (fallback)
-
-Create an **Automation** access token on npmjs.com and add it as the repo secret
-`NPM_TOKEN` (Settings → Secrets and variables → Actions). The workflow already
-wires `NODE_AUTH_TOKEN` from it.
+```bash
+npm install -g keyoku@2
+```
-## Manual fallback
+Do not remove the v2 package line or overwrite an existing version. npm package
+versions are immutable; a bad v3 candidate should be deprecated or superseded,
+not silently replaced.
-Until A or B is configured, publish from an authenticated maintainer machine:
+## Authentication and provenance
-```bash
-npm run build
-npm publish --access public
-```
+Trusted Publishing with npm OIDC is preferred over a long-lived token. The
+release workflow requests `id-token: write`, but credentials do not grant
+product approval. Publication automation must preserve the chosen dist-tag and
+must fail visibly when publish did not occur.
-This is safe to run anytime — npm rejects a re-publish of an existing version,
-matching the workflow's idempotency.
+Never run `npm publish`, push a release tag, or change `latest` merely because
+this document or `npm run preflight` is present. Those are separately approved
+external actions.
diff --git a/docs/PULSE.md b/docs/PULSE.md
new file mode 100644
index 0000000..75e278a
--- /dev/null
+++ b/docs/PULSE.md
@@ -0,0 +1,124 @@
+# Keyoku Pulse
+
+Status: `v1alpha1` local thin slice
+License: MIT
+Scope: harness-neutral progress across exact-source Factfiles
+
+A **Factfile** is proof for one bounded checkpoint. **Pulse** is trusted progress across checkpoints.
+
+Pulse is not an agent transcript, a cron digest, a task tracker, or permission to send a message. It consumes typed lifecycle events from any harness, promotes only Factfile-bound checkpoints, deterministically decides whether an update is reportable, and renders several audiences from one content-bound snapshot.
+
+## Local path
+
+No Engine account or service is required:
+
+```bash
+# Inspect the contract with a generic JSONL fixture.
+keyoku pulse fixture generic --out /tmp/pulse.jsonl
+mkdir -p /tmp/pulse-project
+keyoku pulse ingest --root /tmp/pulse-project --file /tmp/pulse.jsonl
+keyoku pulse status --root /tmp/pulse-project --json
+keyoku pulse plan --root /tmp/pulse-project --now 2026-08-24T16:05:00.000Z --debounce-ms 0 --json
+```
+
+The generic fixture is synthetic and therefore produces
+`suppress/attested_checkpoint`, not a dispatchable snapshot. Audience rendering
+requires a locally verified checkpoint created by `pulse checkpoint publish`.
+
+The append-only ledger is `.keyoku/pulse/events.jsonl`. Replaying the same event id and digest is idempotent. Reusing an id with different content fails. Replay canonicalizes a valid event set by timestamp, lifecycle dependency rank, and event id, so arrival-order permutations produce the same state. Same-lease events that still have an ambiguous timestamp/rank fail closed rather than inheriting JSONL order.
+
+Local writes reject symlinked ledger paths, serialize writers with an exclusive
+fail-closed lock, append through identity-checked descriptors, and fsync the ledger
+and parent directory. If a process crashes while holding the lock, Keyoku reports
+the exact `.lock` path; remove it only after confirming that no writer is alive,
+then retry. This is cooperative same-user process safety, not containment against
+a malicious process running as that OS user.
+
+## Adapter contract
+
+Any caller may write the same `keyoku.dev/pulse-event/v1alpha1` JSONL. Keyoku is
+an optional assurance adapter, not the caller's runtime protocol or control plane.
+
+Lifecycle types are:
+
+- `started`
+- `heartbeat`
+- `verification_started`
+- `checkpoint_published`
+- `blocked`
+- `failed`
+- `completed`
+- `abandoned`
+
+Each lease names its harness, project, run, agent, canonical source root, bounded task/outcome, heartbeat, current state, source digest, and latest checkpoint. Every event and source identity has an exact SHA-256 content digest.
+
+## Checkpoint promotion
+
+A verified checkpoint contains one or more Factfile references, exact source, verification methods, a change story, visible assets, limitations, next task, and an optional human decision request.
+
+For local Factfiles, do not hand-author a `checkpoint_published` event. Use:
+
+```bash
+keyoku pulse checkpoint publish --root /path/to/project --file checkpoint-draft.json --json
+```
+
+The command reads every Factfile, recomputes its canonical digest and bytes digest,
+checks project/outcome identity plus Git head and worktree digest, and rejects
+symlinked or signature-mismatched media before appending the event.
+Before planning or rendering, the CLI and MCP adapter re-read those current
+bytes and omit any stale or self-asserted local checkpoint from the planner's
+trust set. Such a checkpoint returns `suppress/untrusted_local_checkpoint`.
+`adapter_attested` checkpoints must explicitly name the adapter and its
+responsibility. Adapter and fixture bindings remain visibly `attested`, never
+`verified`, and the local dispatcher always returns
+`suppress/attested_checkpoint` for them.
+
+Visual assets follow the same rule. An asset without a resolved digest renders as **Evidence asset unresolved**, not as a working image or video. A live adapter must resolve and digest the real file before delivery.
+
+## Deterministic dispatch
+
+The planner returns exactly one outcome:
+
+| Outcome | Meaning |
+|---|---|
+| `send` | One material verified checkpoint is ready for a separately authorized adapter |
+| `defer` | Fresh work/verifying activity or the coalescing window is still open |
+| `deduplicate` | The content-bound snapshot was already delivered |
+| `suppress` | No material checkpoint exists, or source/project/future-state conflict fails closed |
+| `coalesce` | Multiple compatible checkpoints share project and source ancestry |
+| `stale_no_send` | An active lease is stale; freeze the last locally reverified checkpoint and send no normal update |
+
+Partial uncheckpointed work never becomes a report. Future-dated events fail closed. Conflicting canonical roots or unconnected source ancestry fail closed. Cron may wake the planner to catch up an undelivered checkpoint, but time alone is not a material event.
+
+Material triggers are limited to a verified checkpoint, owner decision, stopped regression, confirmed deployment incident, or recovery.
+
+## Audience projections
+
+The same snapshot digest renders:
+
+- founder/stakeholder Markdown;
+- developer evidence Markdown;
+- accessible control-room timeline HTML;
+- email-safe HTML;
+- plain text;
+- canonical JSON for API or MCP use.
+
+Friendly model-written copy may be added only after the deterministic planner has selected a reportable snapshot. It must not change source, materiality, freshness, or dispatch decisions.
+
+## Delivery authority
+
+`planPulseDelivery` supports email, Slack, Teams, webhook, and MCP adapter plans. It returns a payload only when a current authority matches the channel and project. Fixture-bound checkpoints always return `no_send`, even with authority. The planner still does not perform the send. External delivery, retries, provider receipts, and permission storage belong to an explicit adapter or the optional Engine service.
+
+## Processyard fixture
+
+`keyoku pulse fixture processyard` provides a synthetic M0–M6 integration story. It includes:
+
+- a long-running development lease blocked on an owner decision;
+- synthetic checkpoint digests that remain nondispatchable;
+- a later `stale_no_send` planning instant;
+- unresolved Economy Theatre poster/video paths, labeled as fixture bindings because the media bytes are not present in this repository.
+
+The fixture exercises parsing, replay, stale handling, and attestation rejection.
+It does not establish a production Processyard integration, a deployed service,
+coalescing of locally verified Factfiles, a Gmail authority grant, or a sent
+founder email.
diff --git a/docs/REPO-MAP.md b/docs/REPO-MAP.md
index a59a8fc..8067287 100644
--- a/docs/REPO-MAP.md
+++ b/docs/REPO-MAP.md
@@ -1,63 +1,32 @@
-# Repo map — what's canonical, and what everything else is
-
-There are several directories named `keyoku*` across the workspace, and **two
-different packages literally named `keyoku`**. This note removes the ambiguity.
-
-## The one canonical, published package
-
-**This repo (`keyoku-harness/`) is `keyoku` on npm** — the convergence + muscle-memory
-harness, MCP-native, with the `keyoku` CLI.
-
-| | |
-|---|---|
-| npm | [`keyoku`](https://www.npmjs.com/package/keyoku) (the `latest` dist-tag) |
-| package name | `keyoku` (see `package.json`) |
-| bin | `keyoku` → `dist/index.js` |
-| install | `npm install -g keyoku && keyoku init` |
-| repo | `github.com/Keyoku-ai/keyoku` |
-| site | `keyoku.ai` |
-
-If you are installing, depending on, or contributing to "Keyoku" — this is it.
-Everything below is supporting or historical and is **not** what `npm i keyoku`
-gives you.
-
-## Supporting components (not this npm package)
-
-- **`keyoku-engine/`** — the optional Go backend for teams: knowledge graph,
- semantic search, memory decay, cross-device sync. The harness runs fully
- standalone without it; set `KEYOKU_ENGINE_URL` to connect one.
- Repo: `github.com/Keyoku-ai/keyoku-engine`.
-- **`keyoku-site/`** — the marketing site for `keyoku.ai` (deployed separately).
-
-## Historical / sibling (do not confuse with the published package)
-
-- **`../Keyoku Harness/keyoku/`** — an **earlier, private** package *also* named
- `keyoku` (v1.x), the original **AI-memory SDK** (auto-recall / auto-capture /
- heartbeat) from before the harness became the product. It is **not** published as
- the current `keyoku` and is not what this repo builds. Treat it as legacy unless
- you are specifically working on the memory SDK lineage.
-- **`keyoku-node/` (`@keyoku/sdk`), `keyoku-python/`, `keyoku-embedded/`,
- `keyoku-bot/`, `keyoku-dashboard/`, `keyoku-git*`, `keyoku-infra/`,
- `keyoku-demo/`** — experiments, SDKs, deploy infra, and demos in the broader
- Keyoku family. None of them is the published `keyoku` CLI.
-
-## Local working trees — the canonical clone
-
-Two local clones of **this same repo** exist on the maintainer's machine. Editing
-the wrong one ships nothing. The canonical one is:
-
-> **`~/Development/Keyoku/keyoku-harness`** — this is what the **live Claude Code
-> MCP server runs** (`~/.claude.json` points at its `dist/index.js`). Edit here,
-> `npm run build`, and the next session picks it up.
-
-The other clone — `~/Development/Keyoku Harness/keyoku-harness` — is a second
-checkout used historically for `keyoku-site` deploys. To remove the foot-gun,
-archive or delete it and keep a single working copy. (Both push to the same
-GitHub remote, so no history is lost.)
-
-## Rule of thumb
-
-> When someone says "Keyoku," they mean **this package** (`keyoku-harness` →
-> npm `keyoku`). The convergence loop and muscle memory live here. Anything else
-> is a satellite — name it explicitly (`keyoku-engine`, the memory SDK, the site)
-> to avoid the collision.
+# Keyoku repository map
+
+Keyoku v3 is one product split across three repositories. This integration
+branch is a local release candidate; it is not the published replacement yet.
+
+| Repository | Candidate responsibility | Current publication boundary |
+|---|---|---|
+| `keyoku` (this repository) | MIT CLI, Factfile and Pulse schemas, local verifier, planner, renderers, GitHub workflow, and bounded MCP adapters | npm `latest` remains on the v2 release line; no v3 package has been published |
+| `keyoku-engine` | Optional durable Factfile/Pulse SQLite registry and API | Optional; not required for local proof or Pulse |
+| `keyoku-site` | Authoritative product and documentation website | Replacement remains private until the exact candidate is approved |
+
+The v3 package contract is:
+
+```text
+package: keyoku
+binary: keyoku -> dist/index.js
+install candidate: npm install -g keyoku@next
+first command: keyoku proof init
+rollback after a future alpha: npm install -g keyoku@2
+```
+
+Do not use that install candidate as a claim that `next` exists today. Source
+evaluation uses `npm ci`, `npm run build`, and `npm link` from this repository.
+
+The v2 goal, workflow, connector, activity, memory, and execution implementation
+remains compatibility source. It is not registered by the v3 MCP server and its
+test-only build is excluded from the npm package archive. See
+[PUBLIC-SURFACE.md](PUBLIC-SURFACE.md) for the checked public inventory.
+
+The optional Engine does not make Keyoku an agent runner. Coding harnesses own
+agent execution; Keyoku owns bounded evidence, human attention, exact-source
+Factfiles, and trusted progress projections.
diff --git a/docs/SECURITY-REVIEW.md b/docs/SECURITY-REVIEW.md
new file mode 100644
index 0000000..41c2d81
--- /dev/null
+++ b/docs/SECURITY-REVIEW.md
@@ -0,0 +1,76 @@
+# Security review — contribution gate pivot
+
+Reviewed: 2026-08-25
+Scope: the new repository-local contribution, gate, review, Factfile rendering, publishing, and shared-ledger paths in `keyoku` and `keyoku-engine`.
+
+## Result
+
+The bounded local-alpha trust paths below have direct regression coverage.
+Command probes now execute sequentially in fresh disposable Git checkouts made
+from one content-addressed source capsule rather than in the mutable caller
+checkout. This closes the prior source-binding blocker; it is still not a
+general OS sandbox or a claim that arbitrary repository commands are safe.
+
+## Threat boundaries
+
+- Outcome command probes are trusted repository code. Adopting a project’s outcome contract is equivalent to trusting its test scripts. The disposable checkout protects evidence integrity, but the command retains the caller's user permissions, process, network, and external-filesystem access; inspect unfamiliar contracts and use ordinary CI isolation for untrusted forks.
+- Factfiles are designed to leave the repository. Probe output is recursively redacted before JSON, Markdown, or HTML is written.
+- Publishing is explicit. It accepts HTTPS or loopback HTTP, rejects credentials embedded in URLs, disables redirects, times out, and verifies that the repository still matches the Factfile before upload.
+- The shared engine validates and stores evidence but never executes uploaded probes.
+- A passing gate is not acceptance. Only an identified human can append acceptance, and stale Git/worktree snapshots are rejected.
+
+## Controls verified
+
+| Area | Control |
+|---|---|
+| Input validation | Zod schemas locally; the optional registry validates the stable envelope, enforces a 20 MB body limit, rejects duplicate JSON keys, and rejects unredacted credential-shaped fields |
+| Output safety | Contextual HTML escaping and a restrictive CSP in local Factfile reports; registry retrieval returns the original JSON only |
+| Evidence integrity | Fail-closed Git identity, NUL-delimited paths, a SHA-256 full-tree capsule containing tracked plus non-ignored untracked bytes and executable/symlink modes, fresh checkout per command criterion, mutation and mutate-restore rejection, original-tree revalidation, base/head/worktree binding, append-only snapshot history, canonical Factfile digest, and stale-proof rejection |
+| Pulse promotion | Project/outcome/source binding for local Factfiles; adapter and fixture checkpoints remain attested and nondispatchable; public adapter ingress cannot self-claim local verification |
+| Artifact containment | Lexical plus realpath containment, symbolic-link rejection, byte digests, size limits, and PNG/JPEG/WebP/MP4/WebM signature checks before portable embedding |
+| Adapter authority | The neutral evaluator consumes submitted results only; it cannot execute a shell, accept human review, mutate caller state, choose a caller's assurance profile, or control an agent runtime |
+| Authentication | The registry requires a bearer token for non-loopback binding and compares it in constant time |
+| Network exposure | The registry binds to loopback by default; remote binding is explicit and token-gated |
+| HTTP hardening | Header/body limits, read/header/write/idle timeouts, `nosniff`, frame denial, no-referrer, permissions policy |
+| Storage | SQLite-backed immutable receipts, idempotent retry for identical bytes, and `409 Conflict` for changed content under an existing digest |
+| CI permissions | GitHub proof workflow uses read-only repository permissions and uploads the generated receipt as an artifact |
+| Secret scanning | Whole-history gitleaks scanning; fingerprint-scoped ignores cover only reviewed synthetic credential-redaction fixtures and model identifiers already present in public history, so new findings and all other locations still fail |
+
+## Dependency evidence
+
+- `npm audit`: **0 vulnerabilities** after upgrading Vitest/transitive packages and pinning a fixed `esbuild` through package overrides.
+- `govulncheck ./...`: **0 reachable vulnerable symbols** and **0 vulnerable imported packages** after upgrading gRPC, OpenTelemetry, `x/net`, `x/crypto`, and related Go modules.
+- The Go scanner reports one module-level advisory for the unmaintained `golang.org/x/crypto/openpgp` subpackage. It has no fixed version, and this codebase does not import or call it. This should remain visible in future scans rather than being mislabeled as resolved.
+
+## Residual limits
+
+- A malicious command can still use the caller's OS authority outside its disposable checkout. Keyoku is not a container, VM, syscall sandbox, network sandbox, or secret broker. Use an isolated CI job or stronger sandbox when the repository or probe contract is not trusted.
+- Command criteria must be observational. Any write, add, delete, executable-mode change, or mutate-restore inside the disposable checkout rejects the proof. Build or test tools that write caches or generated outputs into the source tree need a read-only mode or a separately declared external work directory.
+- The capsule contains tracked and non-ignored untracked Git source. Generated
+ `.keyoku/contributions`, `.keyoku/pulse`, and `.keyoku/runtime` state is
+ excluded so proof bookkeeping cannot self-invalidate; project, policy,
+ outcome, and architecture contracts remain included. Git-ignored
+ dependencies/build caches are environment inputs, not claimed source bytes.
+ Escaping symlinks, submodules, invalid UTF-8 paths, FIFOs/sockets/devices,
+ and cwd paths outside the capsule fail closed.
+- Pulse dispatch planning accepts local checkpoints only when their current Factfile, source, and artifact bytes have been reverified through the filesystem adapter. Fixture and unauthenticated adapter attestations remain visible but nondispatchable.
+- Redaction is defense in depth, not a replacement for keeping secrets out of test output.
+- Factfile digests provide tamper evidence, not an externally authenticated signature. The versioned conformance manifest freezes numeric/Unicode canonicalization, strict UTF-8 and surrogate rejection, duplicate-key rejection, and the shared redaction marker for cross-language implementations.
+- “Passed” covers only declared criteria. It does not prove absence of undeclared bugs or vulnerabilities.
+
+## Reproduction
+
+```bash
+# keyoku
+npm audit
+npm run typecheck
+npm test -- --run
+npm run eval
+npm run preflight
+
+# keyoku-engine
+go test ./...
+go test -race ./factfile ./cmd/keyoku-registry
+go vet ./...
+go run golang.org/x/vuln/cmd/govulncheck@latest -show verbose ./...
+```
diff --git a/docs/artifacts/keyoku-factfile-current.png b/docs/artifacts/keyoku-factfile-current.png
new file mode 100644
index 0000000..ce17125
Binary files /dev/null and b/docs/artifacts/keyoku-factfile-current.png differ
diff --git a/docs/artifacts/keyoku-live-decision.webm b/docs/artifacts/keyoku-live-decision.webm
new file mode 100644
index 0000000..4449451
Binary files /dev/null and b/docs/artifacts/keyoku-live-decision.webm differ
diff --git a/docs/demo-evidence.md b/docs/demo-evidence.md
new file mode 100644
index 0000000..110f33e
--- /dev/null
+++ b/docs/demo-evidence.md
@@ -0,0 +1,224 @@
+# Demo evidence: record → watch → gate
+
+`keyoku demo` turns a scripted product demo into a recorded, agent-watched,
+machine-checkable piece of evidence — usable in **any** project, not just
+Keyoku itself.
+
+## Why
+
+Humans digest a demo instantly: open the app, click through the flow, look
+at what's on screen. That's exactly the evidence a reviewer actually trusts
+— more than a green test suite, because it shows the thing the user will
+see. But a demo is normally a one-off, unrecorded, unverifiable act: someone
+clicked through it once, said "looks good," and that moment is gone.
+
+Coding agents can watch a demo the same way a human does — by looking at
+screenshots and checking them against stated expectations. `keyoku demo`
+makes that watching step first-class:
+
+1. **`keyoku demo record`** drives a real browser through a scripted walk of
+ the running app and captures one screenshot ("frame") per stop, plus a
+ manifest of what each frame is supposed to show.
+2. **`keyoku demo watch`** has an agent actually look at each frame, check
+ it against the stated expectations, and also run a general UI/UX audit
+ across the whole demo — then write a structured verdict.
+3. **`keyoku demo watch --assert`** turns that verdict into a pass/fail exit
+ code, so `keyoku demo record && keyoku demo watch --assert` can be pasted
+ straight into an outcome's `criteria[].probe.run` — the watched demo
+ becomes part of the proof, not a claim about the proof.
+
+Keyoku's evidence model (`EvidencePresentationSchema` in `src/contribution.ts`)
+already supports `artifacts[].kind: "screenshot"` / `"video"` in a Factfile.
+`keyoku demo` is the workflow that actually *produces* those artifacts and
+validates them before they're presented, instead of leaving it to whoever
+proposed the change to attach (or not attach) a screenshot by hand.
+
+## The `demo.yaml` schema
+
+`keyoku demo init` writes `.keyoku/demo.yaml` (never overwrites an existing
+one) with a commented template. Full shape:
+
+```yaml
+baseUrl: http://localhost:3000 # required — where the app is running
+
+viewport: # optional, default 1440x900
+ width: 1440
+ height: 900
+
+settleMs: 2500 # optional, default 2500 — wait after
+ # navigating/acting, before the shot
+
+fullPage: true # optional, default true — can be
+ # overridden per-stop
+
+auth: # optional — runs ONCE, before stop 1
+ url: /login # relative to baseUrl, or absolute
+ steps: # same Action union as stops[].actions
+ - fill: { selector: "#email", value: "demo@example.com" }
+ - fill: { selector: "#password", value: "demo-password" }
+ - click: "button[type=submit]"
+ - waitMs: 1000
+
+stops: # required, at least one
+ - id: dashboard # required, slug (lowercase/digits/-._)
+ title: Dashboard # optional, shown to the watching agent
+ url: /dashboard # optional — relative to baseUrl or
+ # absolute; omit to stay on the current
+ # page (e.g. after a click from a
+ # previous stop)
+ actions: # optional, run in order after goto
+ - click: "#nav-settings"
+ - waitMs: 500
+ fullPage: false # optional, overrides the global default
+ expect: # REQUIRED, at least one — plain
+ # language assertions about what must
+ # be VISIBLE in this frame
+ - "The main navigation is visible"
+ - "At least one summary metric card is rendered with a non-empty value"
+ caption: "Landing view after login" # optional, shown in the report
+```
+
+`Action` (used in both `auth.steps` and `stops[].actions`) is one of:
+
+```ts
+type Action =
+ | { goto: string }
+ | { click: string }
+ | { fill: { selector: string; value: string } }
+ | { select: { selector: string; label: string } }
+ | { press: string }
+ | { waitMs: number };
+```
+
+## Recording
+
+`keyoku demo record`:
+
+1. Reads and zod-validates `.keyoku/demo.yaml`.
+2. Launches Chromium via `playwright`, resolved from **the target project**
+ (via `createRequire` against that project's own `package.json`) — not
+ from keyoku's own dependencies. If `playwright` isn't installed there,
+ the command exits with a clear message: `npm i -D playwright`.
+3. Runs `auth` once, if present.
+4. For each stop, in order: `goto` (if `url` given) → run `actions` → wait
+ `settleMs` → screenshot to
+ `.keyoku/demo/frames/-.jpeg` (JPEG, quality 80, animations
+ disabled, full-page unless overridden).
+5. Writes `.keyoku/demo/manifest.json`:
+
+```json
+{
+ "recordedAt": "2026-08-23T12:00:00.000Z",
+ "baseUrl": "http://localhost:3000",
+ "stops": [
+ {
+ "id": "dashboard",
+ "order": 1,
+ "frame": ".keyoku/demo/frames/01-dashboard.jpeg",
+ "title": "Dashboard",
+ "expect": ["The main navigation is visible", "..."],
+ "caption": "Landing view after login"
+ }
+ ]
+}
+```
+
+A stop that throws (bad selector, navigation failure, timeout, ...) is
+recorded and reported by id, and the command exits non-zero — but every
+*other* stop still gets attempted and included in the manifest.
+
+## Watching — the verdict contract
+
+`keyoku demo watch` reads the manifest, builds one prompt covering every
+frame plus its `expect` list, and runs it through an agent. The default
+runner is the `claude` CLI:
+
+```
+claude -p "" --permission-mode acceptEdits
+```
+
+run with `cwd` set to the project root. If `claude` isn't on `PATH`, the
+command exits with code `2` and names the contract below, so **any other
+agent runner can be substituted** — the only requirement is that it writes
+`.keyoku/demo/verdict.json` matching this shape:
+
+```json
+{
+ "watched_at": "2026-08-23T12:05:00.000Z",
+ "frames": [
+ {
+ "id": "dashboard",
+ "requirement_met": true,
+ "evidence_seen": "Top nav with 4 links is visible; 3 metric cards show non-empty values.",
+ "concerns": []
+ }
+ ],
+ "overall": {
+ "frames_pass": 1,
+ "frames_partial": 0,
+ "frames_fail": 0,
+ "verdict": "pass",
+ "summary": "Dashboard renders as expected; no missing elements."
+ },
+ "uiux_audit": {
+ "findings": [
+ { "severity": "low", "description": "Metric card labels truncate on narrow viewports.", "suggested_fix": "Wrap instead of truncating, or shorten labels." }
+ ],
+ "top_priorities": ["Fix metric card label truncation"]
+ }
+}
+```
+
+- `requirement_met` is `true`, `false`, or `"partial"` per frame.
+- `overall.verdict` is `"pass"` **if and only if** no frame has
+ `requirement_met === false`.
+- `uiux_audit` is a separate, cross-frame pass — visual hierarchy, density,
+ truncation, empty/broken charts, color semantics, cross-page consistency —
+ independent of whether the per-stop `expect` assertions held.
+
+After the run, `keyoku demo watch` validates `verdict.json` against this
+contract with zod and prints a summary (pass/partial/fail counts, failing
+frames, top UI/UX findings).
+
+## Gating: `--assert`
+
+`keyoku demo watch --assert` exits:
+
+- **`0`** only if `overall.verdict === "pass"` **and**
+ `verdict.watched_at` is strictly newer than `manifest.recordedAt` (i.e.
+ the verdict is actually about the demo that was just recorded, not a
+ stale one from a previous run).
+- **`1`** otherwise, printing which frames failed/partialed or why the
+ verdict was considered stale.
+- **`2`** for setup problems (no manifest, no `claude` CLI, a verdict that
+ doesn't match the contract).
+
+This makes `keyoku demo record && keyoku demo watch --assert` a valid
+`command` probe for an outcome criterion — `keyoku demo init` prints a
+ready-to-paste snippet:
+
+```yaml
+- description: "The recorded product demo passes agent review"
+ probe:
+ kind: command
+ run: "keyoku demo record && keyoku demo watch --assert"
+ timeoutMs: 900000
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: "An agent watched the recorded demo frames against their stated expectations and ran a UI/UX audit."
+ whyItMatters: "The demo is recorded evidence, not a claim about it — the same frames a human would watch are what the agent checked."
+ code: []
+ artifacts:
+ - kind: screenshot
+ path: ".keyoku/demo/frames/*.jpeg"
+ label: "Recorded demo frames"
+ caption: "One frame per stop, captured by keyoku demo record"
+```
+
+`timeoutMs: 900000` (15 minutes) matches the raised `CommandProbeSchema` /
+`HttpProbeSchema` cap in `src/types.ts` — a real record → launch-agent →
+watch round trip, on a real frontend build, routinely exceeds the old
+5-minute cap.
diff --git a/docs/examples/contribution.yaml b/docs/examples/contribution.yaml
new file mode 100644
index 0000000..df1afd1
--- /dev/null
+++ b/docs/examples/contribution.yaml
@@ -0,0 +1,21 @@
+schemaVersion: keyoku.dev/contribution/v1alpha1
+id: release-build-2026-08-09-a1b2c3d4
+title: Make the release build reproducible
+outcomeId: release-build
+outcomeRevision: 1
+baseSha: 0123456789abcdef0123456789abcdef01234567
+actors:
+ - kind: human
+ id: maintainer@example.com
+ name: Project maintainer
+ role: accountable owner
+ - kind: agent
+ id: codex:gpt-5.6-sol
+ name: Codex
+ role: implementation agent
+ ownerId: maintainer@example.com
+ harness: Codex
+ model: gpt-5.6-sol
+status: draft
+createdAt: 2026-08-09T00:00:00Z
+updatedAt: 2026-08-09T00:00:00Z
diff --git a/docs/examples/outcome.yaml b/docs/examples/outcome.yaml
new file mode 100644
index 0000000..b1b7581
--- /dev/null
+++ b/docs/examples/outcome.yaml
@@ -0,0 +1,67 @@
+schemaVersion: keyoku.dev/outcome/v1alpha1
+id: release-build
+revision: 1
+title: The release artifact builds from source
+objective: A maintainer can produce and smoke-test the release artifact from a clean checkout.
+owner:
+ kind: human
+ id: maintainer@example.com
+ name: Project maintainer
+ role: accountable owner
+constraints:
+ - The check runs locally and in CI.
+ - No production credential is required.
+scope:
+ include:
+ - src/**
+ - tests/**
+ - package.json
+ - package-lock.json
+ exclude:
+ - docs/**
+ maxChangedFiles: 30
+criteria:
+ - description: The release build completes successfully
+ probe:
+ kind: command
+ run: npm run build
+ timeoutMs: 120000
+ parse: text
+ assert:
+ path: exitCode
+ op: eq
+ value: 0
+ evidence:
+ summary: The repository produced its release build at the exact revision shown in the Factfile.
+ whyItMatters: A release cannot ship if downstream users cannot build its declared artifact.
+ code:
+ - path: src/build.ts
+ purpose: Produces the release bundle.
+ artifacts:
+ - kind: report
+ path: dist/build-manifest.json
+ label: Build manifest
+ caption: Lists the files emitted by the verified build.
+ - description: The built CLI reports its version
+ probe:
+ kind: command
+ run: node dist/index.js --version
+ timeoutMs: 30000
+ parse: text
+ assert:
+ path: output
+ op: matches
+ value: ^[0-9]+\.[0-9]+\.[0-9]+
+ evidence:
+ summary: The built command starts and reports a semantic version.
+ whyItMatters: This smoke test observes the artifact users will actually invoke, not only its source files.
+ code:
+ - path: src/index.ts
+ purpose: Implements the command-line entry point.
+ artifacts: []
+humanCriteria:
+ - id: release-notes-quality
+ description: A maintainer confirms the release notes clearly explain user-visible changes and upgrade risk
+ guidance: Read the notes as a downstream user and record a pass or fail with a short explanation.
+createdAt: 2026-08-09T00:00:00Z
+updatedAt: 2026-08-09T00:00:00Z
diff --git a/fixtures/assurance/v1/evidence.json b/fixtures/assurance/v1/evidence.json
new file mode 100644
index 0000000..d995922
--- /dev/null
+++ b/fixtures/assurance/v1/evidence.json
@@ -0,0 +1,47 @@
+{
+ "schemaVersion": "evidence-provider/v1",
+ "work": {
+ "id": "sample-change",
+ "objective": "Confirm the bounded change behaves as declared."
+ },
+ "claims": [
+ {
+ "id": "behavior",
+ "statement": "The declared behavior passes its native check.",
+ "verdict": "pass",
+ "evidenceRefs": [
+ "native-check",
+ "result-log"
+ ]
+ }
+ ],
+ "source": {
+ "capturedDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "currentDigest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "label": "source snapshot"
+ },
+ "commands": [
+ {
+ "id": "native-check",
+ "command": "project-test-command",
+ "exitCode": 0,
+ "resultDigest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+ }
+ ],
+ "artifacts": [
+ {
+ "id": "result-log",
+ "path": "evidence/result.txt",
+ "digest": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ }
+ ],
+ "limitations": [
+ "This generic fixture contains synthetic digests and establishes no deployment claim."
+ ],
+ "authority": {
+ "kind": "human",
+ "id": "review-owner",
+ "decision": "approved"
+ },
+ "contentDigest": "f9b802d170c98e99b11e7e46fcfd60114ae80c5a31795f133fdf69708dacf22e"
+}
diff --git a/fixtures/assurance/v1/work-events.jsonl b/fixtures/assurance/v1/work-events.jsonl
new file mode 100644
index 0000000..6c89405
--- /dev/null
+++ b/fixtures/assurance/v1/work-events.jsonl
@@ -0,0 +1,2 @@
+{"schemaVersion":"work-event/v1","id":"sample-checkpoint","kind":"checkpoint","at":"2026-08-25T16:00:00.000Z","workId":"sample-change","summary":"A content-bound evidence result is available for caller review.","outcome":"checkpoint_ready","sourceDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","limitations":["This generic fixture contains synthetic digests and establishes no deployment claim."],"eventDigest":"a7176a9ccd8c2ab17c78b464a2d948f7d47c47d63e25f5379fa953c9b5d21e6c"}
+{"schemaVersion":"work-event/v1","id":"sample-terminal","kind":"terminal","at":"2026-08-25T16:05:00.000Z","workId":"sample-change","summary":"The caller recorded its terminal outcome.","outcome":"complete","limitations":["This generic fixture contains synthetic digests and establishes no deployment claim."],"eventDigest":"845bb5fef664b1fac1c844d7e860e5a43a1f938fed9ae8732a3fb10be71bc98b"}
diff --git a/fixtures/conformance/v1/README.md b/fixtures/conformance/v1/README.md
new file mode 100644
index 0000000..fd5d531
--- /dev/null
+++ b/fixtures/conformance/v1/README.md
@@ -0,0 +1,17 @@
+# Keyoku Pulse conformance vectors · v1
+
+These files are the stable cross-implementation contract for Keyoku Pulse v1alpha1. They are deterministic fixtures, not live product or deployment evidence.
+
+- `manifest.json` defines canonical JSON, exact byte digests, replay expectations, source-conflict behavior, and dispatch outcomes.
+- `events/*.jsonl` contains the exact lifecycle inputs, including reversed input, a deliberately ambiguous same-time event set, and incompatible source roots.
+- `factfiles/verified.json` is a complete digest-valid Factfile used to bind the raw-file `bytesDigest` vector.
+- `assets/demo-bytes.bin` and `assets/poster.svg` bind `digest` and `posterDigest` to exact fixture bytes. The `.bin` file is intentionally not playable media.
+
+Regenerate and verify from repository source:
+
+```bash
+npm run fixtures:conformance
+npx vitest run tests/conformance.test.ts
+```
+
+Changing any vector is a protocol change: update the conformance version or explicitly reconcile every consuming implementation.
diff --git a/fixtures/conformance/v1/assets/demo-bytes.bin b/fixtures/conformance/v1/assets/demo-bytes.bin
new file mode 100644
index 0000000..b0344de
--- /dev/null
+++ b/fixtures/conformance/v1/assets/demo-bytes.bin
@@ -0,0 +1 @@
+KEYOKU PULSE CONFORMANCE ASSET BYTES v1
diff --git a/fixtures/conformance/v1/assets/poster.svg b/fixtures/conformance/v1/assets/poster.svg
new file mode 100644
index 0000000..6e95efe
--- /dev/null
+++ b/fixtures/conformance/v1/assets/poster.svg
@@ -0,0 +1 @@
+Keyoku Pulse conformance posterDeterministic fixture bytes; not live product evidence.KEYOKU PULSEConformance fixtureExact poster bytes · v1fixture only · not live evidence
diff --git a/fixtures/conformance/v1/events/generic-reversed.jsonl b/fixtures/conformance/v1/events/generic-reversed.jsonl
new file mode 100644
index 0000000..2612b55
--- /dev/null
+++ b/fixtures/conformance/v1/events/generic-reversed.jsonl
@@ -0,0 +1,4 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-completed","at":"2026-08-24T16:04:00.000Z","eventDigest":"f079776e16bb6e69e33580afb34afb77320b18b9a07aa59c17621eaf9c36220a","type":"completed","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"checkpointId":"cart-recovery-verified"}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-checkpoint","at":"2026-08-24T16:03:00.000Z","eventDigest":"05060a1add9a0b247f44267fd9f6ac3104a7fdbdb2b6d5c348910f0ab6f53512","type":"checkpoint_published","leaseId":"generic-run-agent-1","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"cart-recovery-verified","projectId":"checkout-example","outcomeId":"restore-cart","runId":"checkout-example-run-20260824","leaseIds":["generic-run-agent-1"],"title":"Saved-cart fixture checkpoint","changeSummary":"The cart recovery path now restores product ids and quantities from the saved session.","whyItMatters":"Returning shoppers can continue checkout without rebuilding their cart.","publishedAt":"2026-08-24T16:03:00.000Z","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:03:00.000Z","methods":[{"kind":"command","label":"cart-recovery-verified acceptance check","reproduce":"./scripts/verify-checkpoint cart-recovery-verified","result":"Passed in the integration fixture","evidenceDigest":"95e173bc297d6ab5ff167c3dad6ad4cb59380871cd118a4efb0d9647453693d1"}]},"evidenceBinding":{"mode":"fixture","label":"checkout-example demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"cart-recovery-verified-factfile","projectId":"checkout-example","outcomeId":"restore-cart","path":".keyoku/contributions/cart-recovery-verified/factfile.json","digest":"34b9827ff17251b1d578faabd89898c34c6e84458fdbd90d5a1c85e45d9fb3ac","sourceDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c","state":"ready_for_review"}],"assets":[],"limitations":["Visual review of the empty-cart state is still pending."],"nextTask":"Capture the empty-cart visual Factfile.","materialTrigger":"verified_checkpoint","contentDigest":"64bbc59fb709ca09cc7a78b23b45611a3f2bc2cc351a69f5b393fe430f32b734"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
diff --git a/fixtures/conformance/v1/events/generic-same-time-ambiguous.jsonl b/fixtures/conformance/v1/events/generic-same-time-ambiguous.jsonl
new file mode 100644
index 0000000..bdd1878
--- /dev/null
+++ b/fixtures/conformance/v1/events/generic-same-time-ambiguous.jsonl
@@ -0,0 +1,5 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-checkpoint","at":"2026-08-24T16:03:00.000Z","eventDigest":"05060a1add9a0b247f44267fd9f6ac3104a7fdbdb2b6d5c348910f0ab6f53512","type":"checkpoint_published","leaseId":"generic-run-agent-1","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"cart-recovery-verified","projectId":"checkout-example","outcomeId":"restore-cart","runId":"checkout-example-run-20260824","leaseIds":["generic-run-agent-1"],"title":"Saved-cart fixture checkpoint","changeSummary":"The cart recovery path now restores product ids and quantities from the saved session.","whyItMatters":"Returning shoppers can continue checkout without rebuilding their cart.","publishedAt":"2026-08-24T16:03:00.000Z","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:03:00.000Z","methods":[{"kind":"command","label":"cart-recovery-verified acceptance check","reproduce":"./scripts/verify-checkpoint cart-recovery-verified","result":"Passed in the integration fixture","evidenceDigest":"95e173bc297d6ab5ff167c3dad6ad4cb59380871cd118a4efb0d9647453693d1"}]},"evidenceBinding":{"mode":"fixture","label":"checkout-example demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"cart-recovery-verified-factfile","projectId":"checkout-example","outcomeId":"restore-cart","path":".keyoku/contributions/cart-recovery-verified/factfile.json","digest":"34b9827ff17251b1d578faabd89898c34c6e84458fdbd90d5a1c85e45d9fb3ac","sourceDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c","state":"ready_for_review"}],"assets":[],"limitations":["Visual review of the empty-cart state is still pending."],"nextTask":"Capture the empty-cart visual Factfile.","materialTrigger":"verified_checkpoint","contentDigest":"64bbc59fb709ca09cc7a78b23b45611a3f2bc2cc351a69f5b393fe430f32b734"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-completed","at":"2026-08-24T16:04:00.000Z","eventDigest":"f079776e16bb6e69e33580afb34afb77320b18b9a07aa59c17621eaf9c36220a","type":"completed","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"checkpointId":"cart-recovery-verified"}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-same-time-heartbeat","at":"2026-08-24T16:02:00.000Z","eventDigest":"7632d22fff00d0acf98d21e6d597f77d88fd9a0a2ea1093a3a08bf4e545c0129","type":"heartbeat","leaseId":"generic-run-agent-1","state":"working","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
diff --git a/fixtures/conformance/v1/events/generic-through-checkpoint.jsonl b/fixtures/conformance/v1/events/generic-through-checkpoint.jsonl
new file mode 100644
index 0000000..739b02a
--- /dev/null
+++ b/fixtures/conformance/v1/events/generic-through-checkpoint.jsonl
@@ -0,0 +1,3 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-checkpoint","at":"2026-08-24T16:03:00.000Z","eventDigest":"05060a1add9a0b247f44267fd9f6ac3104a7fdbdb2b6d5c348910f0ab6f53512","type":"checkpoint_published","leaseId":"generic-run-agent-1","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"cart-recovery-verified","projectId":"checkout-example","outcomeId":"restore-cart","runId":"checkout-example-run-20260824","leaseIds":["generic-run-agent-1"],"title":"Saved-cart fixture checkpoint","changeSummary":"The cart recovery path now restores product ids and quantities from the saved session.","whyItMatters":"Returning shoppers can continue checkout without rebuilding their cart.","publishedAt":"2026-08-24T16:03:00.000Z","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:03:00.000Z","methods":[{"kind":"command","label":"cart-recovery-verified acceptance check","reproduce":"./scripts/verify-checkpoint cart-recovery-verified","result":"Passed in the integration fixture","evidenceDigest":"95e173bc297d6ab5ff167c3dad6ad4cb59380871cd118a4efb0d9647453693d1"}]},"evidenceBinding":{"mode":"fixture","label":"checkout-example demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"cart-recovery-verified-factfile","projectId":"checkout-example","outcomeId":"restore-cart","path":".keyoku/contributions/cart-recovery-verified/factfile.json","digest":"34b9827ff17251b1d578faabd89898c34c6e84458fdbd90d5a1c85e45d9fb3ac","sourceDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c","state":"ready_for_review"}],"assets":[],"limitations":["Visual review of the empty-cart state is still pending."],"nextTask":"Capture the empty-cart visual Factfile.","materialTrigger":"verified_checkpoint","contentDigest":"64bbc59fb709ca09cc7a78b23b45611a3f2bc2cc351a69f5b393fe430f32b734"}}
diff --git a/fixtures/conformance/v1/events/generic-through-verification.jsonl b/fixtures/conformance/v1/events/generic-through-verification.jsonl
new file mode 100644
index 0000000..9d5adf2
--- /dev/null
+++ b/fixtures/conformance/v1/events/generic-through-verification.jsonl
@@ -0,0 +1,2 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
diff --git a/fixtures/conformance/v1/events/generic.jsonl b/fixtures/conformance/v1/events/generic.jsonl
new file mode 100644
index 0000000..18d81cd
--- /dev/null
+++ b/fixtures/conformance/v1/events/generic.jsonl
@@ -0,0 +1,4 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-checkpoint","at":"2026-08-24T16:03:00.000Z","eventDigest":"05060a1add9a0b247f44267fd9f6ac3104a7fdbdb2b6d5c348910f0ab6f53512","type":"checkpoint_published","leaseId":"generic-run-agent-1","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"cart-recovery-verified","projectId":"checkout-example","outcomeId":"restore-cart","runId":"checkout-example-run-20260824","leaseIds":["generic-run-agent-1"],"title":"Saved-cart fixture checkpoint","changeSummary":"The cart recovery path now restores product ids and quantities from the saved session.","whyItMatters":"Returning shoppers can continue checkout without rebuilding their cart.","publishedAt":"2026-08-24T16:03:00.000Z","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:03:00.000Z","methods":[{"kind":"command","label":"cart-recovery-verified acceptance check","reproduce":"./scripts/verify-checkpoint cart-recovery-verified","result":"Passed in the integration fixture","evidenceDigest":"95e173bc297d6ab5ff167c3dad6ad4cb59380871cd118a4efb0d9647453693d1"}]},"evidenceBinding":{"mode":"fixture","label":"checkout-example demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"cart-recovery-verified-factfile","projectId":"checkout-example","outcomeId":"restore-cart","path":".keyoku/contributions/cart-recovery-verified/factfile.json","digest":"34b9827ff17251b1d578faabd89898c34c6e84458fdbd90d5a1c85e45d9fb3ac","sourceDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c","state":"ready_for_review"}],"assets":[],"limitations":["Visual review of the empty-cart state is still pending."],"nextTask":"Capture the empty-cart visual Factfile.","materialTrigger":"verified_checkpoint","contentDigest":"64bbc59fb709ca09cc7a78b23b45611a3f2bc2cc351a69f5b393fe430f32b734"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-completed","at":"2026-08-24T16:04:00.000Z","eventDigest":"f079776e16bb6e69e33580afb34afb77320b18b9a07aa59c17621eaf9c36220a","type":"completed","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"checkpointId":"cart-recovery-verified"}
diff --git a/fixtures/conformance/v1/events/processyard.jsonl b/fixtures/conformance/v1/events/processyard.jsonl
new file mode 100644
index 0000000..3477aa0
--- /dev/null
+++ b/fixtures/conformance/v1/events/processyard.jsonl
@@ -0,0 +1,13 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-development-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"879425a35947b4f12a53c501c5abf3cd99dbb8fbcc594cbe784fe14870989395","type":"started","leaseId":"processyard-development","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-development","harness":"codex","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"development-agent","name":"Development agent"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre","title":"Build the Economy Theatre story","outcome":"A founder can see verified product progress without reading agent transcripts."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-evidence-started","at":"2026-08-24T16:01:00.000Z","eventDigest":"44e1c837a78f260f60b6e043c9a23bcbfd7b8fc0a17d54b9ae62b800cde75f06","type":"started","leaseId":"processyard-evidence","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-evidence","harness":"claude-code","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"evidence-agent","name":"Evidence agent"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre-evidence","title":"Capture human-readable product evidence","outcome":"The milestone story includes replayable UI evidence and explicit limitations."},"startedAt":"2026-08-24T16:01:00.000Z","heartbeatAt":"2026-08-24T16:01:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-ci-started","at":"2026-08-24T16:02:00.000Z","eventDigest":"4b5db6b77462a3eee14495eb347190a633ba57544f0c7497f09f9ff1f8907084","type":"started","leaseId":"processyard-ci","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-ci","harness":"github-actions","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"verification-workflow","name":"Verification workflow"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre-verification","title":"Verify the checkpoint boundary","outcome":"Every reported milestone is bound to a reproducible Factfile and exact source digest."},"startedAt":"2026-08-24T16:02:00.000Z","heartbeatAt":"2026-08-24T16:02:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m0-published","at":"2026-08-24T16:10:00.000Z","eventDigest":"00ebb5675a083f4643522730dd21dd160de4709f4356708ec633d6c6e6d98653","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m0","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M0 · Outcome pinned","changeSummary":"The founder outcome and source boundary were recorded before implementation.","whyItMatters":"The milestone story starts from an explicit definition of done.","publishedAt":"2026-08-24T16:10:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:10:00.000Z","methods":[{"kind":"command","label":"processyard-m0 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m0","result":"Passed in the integration fixture","evidenceDigest":"fe5b19d3508b254ac100801b7f9ef21e93359da67f8ac78a42f1d819f767c47e"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m0-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m0/factfile.json","digest":"687f833e5578025cdba94cbefd6cc2f61c66545508c1f1cdf3e36294e0e3253c","sourceDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M1 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"4cf871a73c22ddc216fdf13333a978296eacdd40abd0e7ee47ea1a813b38105e"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m1-published","at":"2026-08-24T16:15:00.000Z","eventDigest":"829e100c648f57406414915517f48b2133ac5158efdf707542cc1d4fdff28a65","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m1","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M1 · Storefront path observed","changeSummary":"The current customer journey and evidence gaps were captured.","whyItMatters":"Work begins from the real product path instead of an imagined interface.","publishedAt":"2026-08-24T16:15:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"0760994c45cc1f3c6a6c0c616f3dbb5ef1bf7f6154b7bbcdb0b9556cc29e74cb"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:15:00.000Z","methods":[{"kind":"command","label":"processyard-m1 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m1","result":"Passed in the integration fixture","evidenceDigest":"107bac10ebc481c8ba14b7ecc16aac336c203875fb90a2248087377217d10ca3"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m1-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m1/factfile.json","digest":"ed1472e4ff2362c8dd6cc5d53ea7955995d8ac0b80982326c8e6ed74e4e658d8","sourceDigest":"0760994c45cc1f3c6a6c0c616f3dbb5ef1bf7f6154b7bbcdb0b9556cc29e74cb","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M2 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"585c83ad4c10a2a082d4268721d193417885f9e835137a51d42603654d80a5cf"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m2-published","at":"2026-08-24T16:20:00.000Z","eventDigest":"c472daea268139d4eceb38852c858f76455fd4009fa8a347521d552c7289fd16","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m2","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M2 · Economy Theatre implemented","changeSummary":"The demonstration flow now shows the product outcome in a browser-facing experience.","whyItMatters":"A non-technical stakeholder can understand the product without a terminal transcript.","publishedAt":"2026-08-24T16:20:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"3333333333333333333333333333333333333333","worktreeDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222"],"verifiedDigest":"08c09c1b6927f28aba863849a63163971c9f0c14736b8d31f430067ee40508cd"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:20:00.000Z","methods":[{"kind":"command","label":"processyard-m2 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m2","result":"Passed in the integration fixture","evidenceDigest":"65138d03ca6c74288a4943fbabd5f492c9342e821482a8a757942fc7ff86bc93"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m2-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m2/factfile.json","digest":"fef0f000904d60a217f005a9eeab2461ca452073283ced41b1e6e3c02cf1960c","sourceDigest":"08c09c1b6927f28aba863849a63163971c9f0c14736b8d31f430067ee40508cd","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M3 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"2b06dfadc33bec7f2d54b416d84b5fb8d7a167f2035682f1158d858112abcceb"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m3-published","at":"2026-08-24T16:25:00.000Z","eventDigest":"ebd441eb26f7332dabdc02faf473fff52c0143ee488a7797fcf977f0480faec6","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m3","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M3 · Checks bound to source","changeSummary":"Automated checks and the changed-file boundary were bound to one source identity.","whyItMatters":"Passing output can no longer drift away from the code it describes.","publishedAt":"2026-08-24T16:25:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"4444444444444444444444444444444444444444","worktreeDigest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333"],"verifiedDigest":"337133589bea8a4f1d4c467b62390df227178b704612248789af685637982d23"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:25:00.000Z","methods":[{"kind":"command","label":"processyard-m3 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m3","result":"Passed in the integration fixture","evidenceDigest":"840d4ac0247eaab7a9b9ccdda08b9c94f183e8c0c326a904b7545925fe3d035c"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m3-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m3/factfile.json","digest":"7bc36fd3bcabdee84b8b4a3287831e89d4a25f8d69257dd07cb3e1994541d71c","sourceDigest":"337133589bea8a4f1d4c467b62390df227178b704612248789af685637982d23","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M4 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"c20b7d41f4634f1d0c30f15f9acc32c37bb8ad8fc65539fdeca65c556871b1d1"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m4-published","at":"2026-08-24T16:30:00.000Z","eventDigest":"496b57150697b39e97cbc688926c357e1d4d18c0733b4ed434ae3c3d9f9f4575","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m4","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M4 · Owner decision isolated","changeSummary":"The remaining launch choice was separated from machine verification.","whyItMatters":"The agent can continue independent work without manufacturing stakeholder consent.","publishedAt":"2026-08-24T16:30:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"5555555555555555555555555555555555555555","worktreeDigest":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444"],"verifiedDigest":"3341f7f2f19d06bcbacdcf98bd86e5b54a9e111a3838ab08ebdf7e777813d023"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:30:00.000Z","methods":[{"kind":"command","label":"processyard-m4 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m4","result":"Passed in the integration fixture","evidenceDigest":"1217b4c5e79770835bb24ba1d90673c095bb001fe1e74e4a0a8f79b12b369c64"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m4-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m4/factfile.json","digest":"a9019a697c343aef122f6f1b431c63e8d05619238e43f4c8ce8130f89583c8f9","sourceDigest":"3341f7f2f19d06bcbacdcf98bd86e5b54a9e111a3838ab08ebdf7e777813d023","state":"human_review_required"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M5 without changing the established source boundary.","humanDecisionRequest":{"id":"processyard-launch-boundary","title":"Choose the public launch boundary","whyHuman":"This changes the promise made to customers and belongs to the owner.","requestedAction":"Choose whether the launch stays local-first or includes the hosted Engine path.","options":["Local-first Keyoku","Keyoku plus hosted Engine"]},"materialTrigger":"owner_decision","contentDigest":"cef1790fd757ed233d58e4f23e488d3a0e44e3e7eb0785260c8ff8e5d6b1ab3f"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m5-published","at":"2026-08-24T16:35:00.000Z","eventDigest":"ade66ac5a541d706411edf7c658fc5441429b99de5f1785fa2971583edfe09f9","type":"checkpoint_published","leaseId":"processyard-evidence","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m5","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-evidence"],"title":"M5 · Evidence story staged","changeSummary":"A poster and replay path were declared in the synthetic checkpoint.","whyItMatters":"The fixture shows the intended evidence shape but cannot claim the referenced bytes exist.","publishedAt":"2026-08-24T16:35:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"6666666666666666666666666666666666666666","worktreeDigest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555"],"verifiedDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:35:00.000Z","methods":[{"kind":"command","label":"processyard-m5 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m5","result":"Passed in the integration fixture","evidenceDigest":"906f1a5d2beed676fa4010ce3450c7b5e9ba50829a4b8648296fd08600764b97"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m5-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m5/factfile.json","digest":"264d1e497735c4ae9c79e980af89c5f1bcb6cdb538969c58affeffe4407511a0","sourceDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6","state":"ready_for_review"}],"assets":[{"kind":"video","path":"evidence/economy-theatre-demo.mp4","label":"Economy Theatre product demonstration","caption":"Expected Processyard poster and replay binding; no matching media bytes were found in the checked workspace, so a live integration must resolve and digest them before dispatch.","posterPath":"evidence/economy-theatre-poster.png"}],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M6 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"90174f7d442b03c9aeaa78bba3560a07695667e5a6f61e9b818193f291d6e053"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m6-published","at":"2026-08-24T16:40:00.000Z","eventDigest":"2a68c11fb7e0f4223c7f5d18bc1841ff3d74b557d64b26540b8389cb4b217457","type":"checkpoint_published","leaseId":"processyard-ci","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m6","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-ci"],"title":"M6 · Release boundary rehearsed","changeSummary":"The complete M0–M6 story passed the fixture's schema and replay checks.","whyItMatters":"A live integration must still promote local Factfiles before any stakeholder snapshot is dispatchable.","publishedAt":"2026-08-24T16:40:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:40:00.000Z","methods":[{"kind":"command","label":"processyard-m6 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m6","result":"Passed in the integration fixture","evidenceDigest":"b835de76c3ef23734588fdab36234b11a582604e96f6158e4b7877505403879b"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m6-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m6/factfile.json","digest":"b573f29e6a47e3b7c80fd589b5584a5c9f16deb9fde25a30b665792d5c7c0930","sourceDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3","state":"ready_for_review"}],"assets":[],"limitations":["No production deployment is established by this local fixture.","Gmail delivery authority and sent-message verification are not configured."],"nextTask":"Request explicit channel authority before preparing any founder email delivery.","materialTrigger":"verified_checkpoint","contentDigest":"9b4febd6a6374379c2e7650e7f0d45d2a8240671b4781818c01d957534e5a0f8"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-development-blocked","at":"2026-08-24T16:41:10.000Z","eventDigest":"b7e492b703322a942cec915be785464854fce617081b3770d258d002ac0a1977","type":"blocked","leaseId":"processyard-development","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"reason":"The long-running development lease is waiting for the explicit launch-boundary decision.","humanDecisionRequest":{"id":"processyard-launch-boundary","title":"Choose the public launch boundary","whyHuman":"This changes the promise made to customers and belongs to the owner.","requestedAction":"Choose whether the launch stays local-first or includes the hosted Engine path.","options":["Local-first Keyoku","Keyoku plus hosted Engine"]}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-evidence-completed","at":"2026-08-24T16:41:20.000Z","eventDigest":"637511958e61adeb17e61e876d9617ec01a4c38363a52706d99812f196254a56","type":"completed","leaseId":"processyard-evidence","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"6666666666666666666666666666666666666666","worktreeDigest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555"],"verifiedDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6"},"checkpointId":"processyard-m5"}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-ci-completed","at":"2026-08-24T16:41:30.000Z","eventDigest":"94b886f5446fd5634d84ab6e6e5c0ab7dc69c4d3e404da0532aa67404abf6cbb","type":"completed","leaseId":"processyard-ci","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"checkpointId":"processyard-m6"}
diff --git a/fixtures/conformance/v1/events/source-conflict.jsonl b/fixtures/conformance/v1/events/source-conflict.jsonl
new file mode 100644
index 0000000..da3fcb4
--- /dev/null
+++ b/fixtures/conformance/v1/events/source-conflict.jsonl
@@ -0,0 +1,6 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-left-started","at":"2026-08-25T16:00:00.000Z","eventDigest":"2f8099bf66550ff29a180673ec33ca381b9a4e07f9a4807c255ef70b03f1d7d3","type":"started","leaseId":"conflict-left","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"conflict-left","harness":"conformance-jsonl","project":{"id":"source-conflict-project","name":"Source Conflict Project"},"runId":"source-conflict-run","agent":{"id":"conflict-left-agent","name":"conflict-left agent"},"canonicalSourceRoot":"repo://conformance/left","task":{"id":"conflict-left-task","title":"Produce a source-bound checkpoint","outcome":"Conflicting roots fail closed."},"startedAt":"2026-08-25T16:00:00.000Z","heartbeatAt":"2026-08-25T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://conformance/left","branch":"main","headSha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","worktreeDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ancestryShas":[],"verifiedDigest":"91d371dcee69c3f0e25314f1aee10466d5ff17eecbda04736c53b84756c41402"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-right-started","at":"2026-08-25T16:00:00.000Z","eventDigest":"f64768498851f81d20ad7c88d5b74d87ed5a448fdbdeac855de68cccf6a24f12","type":"started","leaseId":"conflict-right","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"conflict-right","harness":"conformance-jsonl","project":{"id":"source-conflict-project","name":"Source Conflict Project"},"runId":"source-conflict-run","agent":{"id":"conflict-right-agent","name":"conflict-right agent"},"canonicalSourceRoot":"repo://conformance/right","task":{"id":"conflict-right-task","title":"Produce a source-bound checkpoint","outcome":"Conflicting roots fail closed."},"startedAt":"2026-08-25T16:00:00.000Z","heartbeatAt":"2026-08-25T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://conformance/right","branch":"main","headSha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","worktreeDigest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ancestryShas":[],"verifiedDigest":"b29e7b84e37974600489b4b78eec760e0b4367ff80354953562a7ece7ca76762"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-left-published","at":"2026-08-25T16:01:00.000Z","eventDigest":"cc4683e8064f768205beb91a31988cf0c6834cbdc09adcaa0d3197f9714a0d35","type":"checkpoint_published","leaseId":"conflict-left","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"conflict-left-checkpoint","projectId":"source-conflict-project","outcomeId":"conflict-left-task","runId":"source-conflict-run","leaseIds":["conflict-left"],"title":"conflict-left-checkpoint attested fixture","changeSummary":"A synthetic checkpoint was attested on one declared canonical root.","whyItMatters":"Incompatible roots must never be combined into one report.","publishedAt":"2026-08-25T16:01:00.000Z","source":{"canonicalRoot":"repo://conformance/left","branch":"main","headSha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","worktreeDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ancestryShas":[],"verifiedDigest":"91d371dcee69c3f0e25314f1aee10466d5ff17eecbda04736c53b84756c41402"},"verification":{"status":"attested","verifiedAt":"2026-08-25T16:01:00.000Z","methods":[{"kind":"command","label":"Conformance check","reproduce":"keyoku conformance verify","result":"passed in a synthetic fixture"}]},"evidenceBinding":{"mode":"fixture","label":"Synthetic source-conflict conformance vector"},"factfiles":[{"id":"conflict-left-checkpoint-factfile","projectId":"source-conflict-project","outcomeId":"conflict-left-task","path":".keyoku/conflict-left-checkpoint.json","digest":"0d5f38ce4e70868d390d3be6f943cc42c20dc4234963515cea9a93103fe15ceb","sourceDigest":"91d371dcee69c3f0e25314f1aee10466d5ff17eecbda04736c53b84756c41402","state":"ready_for_review"}],"assets":[],"limitations":["Fixture-only evidence."],"materialTrigger":"verified_checkpoint","contentDigest":"a8b54af3128f8426b6a7db09586429edd2245e61680f01da56009f36460ce3d2"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-right-published","at":"2026-08-25T16:01:00.000Z","eventDigest":"3f5c38b1b1c0857ca06c1e2ec7b1bccbdf53aad459fca8035b8748de206445ae","type":"checkpoint_published","leaseId":"conflict-right","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"conflict-right-checkpoint","projectId":"source-conflict-project","outcomeId":"conflict-right-task","runId":"source-conflict-run","leaseIds":["conflict-right"],"title":"conflict-right-checkpoint attested fixture","changeSummary":"A synthetic checkpoint was attested on one declared canonical root.","whyItMatters":"Incompatible roots must never be combined into one report.","publishedAt":"2026-08-25T16:01:00.000Z","source":{"canonicalRoot":"repo://conformance/right","branch":"main","headSha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","worktreeDigest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ancestryShas":[],"verifiedDigest":"b29e7b84e37974600489b4b78eec760e0b4367ff80354953562a7ece7ca76762"},"verification":{"status":"attested","verifiedAt":"2026-08-25T16:01:00.000Z","methods":[{"kind":"command","label":"Conformance check","reproduce":"keyoku conformance verify","result":"passed in a synthetic fixture"}]},"evidenceBinding":{"mode":"fixture","label":"Synthetic source-conflict conformance vector"},"factfiles":[{"id":"conflict-right-checkpoint-factfile","projectId":"source-conflict-project","outcomeId":"conflict-right-task","path":".keyoku/conflict-right-checkpoint.json","digest":"df0a4e830b68902f4c5444a0116ba10a86671f3c2c207788a47d5cb511102582","sourceDigest":"b29e7b84e37974600489b4b78eec760e0b4367ff80354953562a7ece7ca76762","state":"ready_for_review"}],"assets":[],"limitations":["Fixture-only evidence."],"materialTrigger":"verified_checkpoint","contentDigest":"2820e78e265788586014dd6aa30ad6b599ebfa6d0e0098dc5799ae4f0d123995"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-left-completed","at":"2026-08-25T16:02:00.000Z","eventDigest":"87206bd7108745f848ebefad91cc64268d1afd4f2fa2e38c369fd8f0926478dd","type":"completed","leaseId":"conflict-left","source":{"canonicalRoot":"repo://conformance/left","branch":"main","headSha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","worktreeDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ancestryShas":[],"verifiedDigest":"91d371dcee69c3f0e25314f1aee10466d5ff17eecbda04736c53b84756c41402"},"checkpointId":"conflict-left-checkpoint"}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"conflict-right-completed","at":"2026-08-25T16:02:00.000Z","eventDigest":"40842672027099f37db5a8325165b5998468664c604fb3bda3880459fcd78977","type":"completed","leaseId":"conflict-right","source":{"canonicalRoot":"repo://conformance/right","branch":"main","headSha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","worktreeDigest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ancestryShas":[],"verifiedDigest":"b29e7b84e37974600489b4b78eec760e0b4367ff80354953562a7ece7ca76762"},"checkpointId":"conflict-right-checkpoint"}
diff --git a/fixtures/conformance/v1/factfiles/verified.json b/fixtures/conformance/v1/factfiles/verified.json
new file mode 100644
index 0000000..98a416e
--- /dev/null
+++ b/fixtures/conformance/v1/factfiles/verified.json
@@ -0,0 +1,124 @@
+{
+ "schemaVersion": "keyoku.dev/factfile/v1alpha1",
+ "id": "fact_conformance_v1",
+ "project": {
+ "id": "conformance-project",
+ "name": "Conformance Project",
+ "summary": "Stable cross-language trust vectors."
+ },
+ "outcome": {
+ "id": "conformance-outcome",
+ "revision": 1,
+ "title": "The conformance record is portable",
+ "objective": "Bind exact source, verification, and presentation bytes across implementations.",
+ "constraints": [
+ "Treat these bytes as fixtures, never live evidence."
+ ],
+ "owner": {
+ "kind": "human",
+ "id": "owner@example.com",
+ "name": "Fixture Owner"
+ },
+ "humanCriteria": []
+ },
+ "contribution": {
+ "schemaVersion": "keyoku.dev/contribution/v1alpha1",
+ "id": "conformance-contribution",
+ "title": "Portable conformance record",
+ "summary": "One complete deterministic Factfile.",
+ "knownLimits": [
+ "Fixture-only evidence."
+ ],
+ "outcomeId": "conformance-outcome",
+ "outcomeRevision": 1,
+ "baseSha": "1111111111111111111111111111111111111111",
+ "actors": [
+ {
+ "kind": "human",
+ "id": "owner@example.com",
+ "name": "Fixture Owner"
+ }
+ ],
+ "status": "ready_for_review",
+ "createdAt": "2026-08-25T12:00:00.000Z",
+ "updatedAt": "2026-08-25T12:00:00.000Z"
+ },
+ "repository": {
+ "repositoryRoot": "/conformance/keyoku",
+ "branch": "main",
+ "ahead": 0,
+ "behind": 0,
+ "lastCommit": "Conformance fixture",
+ "baseSha": "1111111111111111111111111111111111111111",
+ "headSha": "2222222222222222222222222222222222222222",
+ "worktreeDigest": "3333333333333333333333333333333333333333333333333333333333333333",
+ "sourceCapsuleDigest": "3333333333333333333333333333333333333333333333333333333333333333",
+ "dirty": false,
+ "changedFiles": []
+ },
+ "scope": {
+ "declared": false,
+ "passed": true,
+ "includedPaths": [],
+ "unexpectedPaths": [],
+ "excludedPaths": [],
+ "topLevelAreas": [],
+ "note": "No repository path scope was declared for this fixture."
+ },
+ "reviewPlan": [],
+ "session": {
+ "work": [],
+ "decisions": [],
+ "instructions": [],
+ "agents": [],
+ "directions": [],
+ "eventCount": 0
+ },
+ "state": "ready_for_review",
+ "generatedAt": "2026-08-25T12:00:00.000Z",
+ "reviews": [],
+ "evidence": [
+ {
+ "id": "criterion-0",
+ "description": "The structured fixture observation matches.",
+ "pass": true,
+ "actual": {
+ "😀": 6,
+ "ä": 4,
+ "a": 2,
+ "あ": 5,
+ "Á": 3,
+ "Z": 1
+ },
+ "expected": {
+ "op": "eq",
+ "value": 2,
+ "path": "output.a"
+ },
+ "durationMs": 1,
+ "verification": {
+ "kind": "command",
+ "label": "Fixture observation",
+ "reproduce": "keyoku conformance verify",
+ "assertion": {
+ "op": "eq",
+ "value": 2,
+ "path": "output.a"
+ }
+ }
+ }
+ ],
+ "summary": {
+ "passed": 1,
+ "failed": 0,
+ "total": 1,
+ "verified": true
+ },
+ "humanReview": {
+ "passed": 0,
+ "failed": 0,
+ "pending": 0,
+ "total": 0
+ },
+ "digest": "4558e8787677ff34462d14815c823c94c5542e626688a071fa0715a56fe4b417"
+}
diff --git a/fixtures/conformance/v1/manifest.json b/fixtures/conformance/v1/manifest.json
new file mode 100644
index 0000000..81d3fc4
--- /dev/null
+++ b/fixtures/conformance/v1/manifest.json
@@ -0,0 +1,288 @@
+{
+ "schemaVersion": "keyoku.dev/pulse-conformance/v1alpha1",
+ "canonicalJson": [
+ {
+ "id": "mixed-case-unicode",
+ "input": {
+ "😀": 6,
+ "ä": 4,
+ "a": 2,
+ "あ": 5,
+ "Á": 3,
+ "Z": 1
+ },
+ "inputJson": "{\"😀\":6,\"ä\":4,\"a\":2,\"あ\":5,\"Á\":3,\"Z\":1}",
+ "canonical": "{\"Z\":1,\"a\":2,\"Á\":3,\"ä\":4,\"あ\":5,\"😀\":6}",
+ "digest": "0f1fe24d4557ef543b80ef7d10e460dbcecdc0e4dac7955287950337bc4f0853"
+ },
+ {
+ "id": "negative-zero",
+ "input": {
+ "value": 0
+ },
+ "inputJson": "{\"value\":-0}",
+ "canonical": "{\"value\":0}",
+ "digest": "23d7b286bd429460b92a2a1c21b6afc34110446c5034c17363fda363aa0a7c5d"
+ },
+ {
+ "id": "one-million",
+ "input": {
+ "value": 1000000
+ },
+ "inputJson": "{\"value\":1000000}",
+ "canonical": "{\"value\":1000000}",
+ "digest": "7becf1021641e948d71d940cdc4851f26e6231592414a72a8e9f3a36d9a4aadb"
+ },
+ {
+ "id": "one-millionth",
+ "input": {
+ "value": 0.000001
+ },
+ "inputJson": "{\"value\":0.000001}",
+ "canonical": "{\"value\":0.000001}",
+ "digest": "bc1c982dd4e677d72ff5821cc67554e045e838a31ddc55fa0fdb1a32d5a1dce9"
+ },
+ {
+ "id": "one-ten-millionth",
+ "input": {
+ "value": 1e-7
+ },
+ "inputJson": "{\"value\":1e-7}",
+ "canonical": "{\"value\":1e-7}",
+ "digest": "910750038eb30a40d06da88009b6259aabaf3751b3cb212c616d51abb6b008a4"
+ },
+ {
+ "id": "one-sextillion",
+ "input": {
+ "value": 1e+21
+ },
+ "inputJson": "{\"value\":1e+21}",
+ "canonical": "{\"value\":1e+21}",
+ "digest": "776bf0d292f3767fdfca3fdc9683d0ba51886b8e6e79cd228a41bafcfa9a1a6f"
+ },
+ {
+ "id": "literal-line-separators",
+ "input": {
+ "value": "line paragraph end"
+ },
+ "inputJson": "{\"value\":\"line paragraph end\"}",
+ "canonical": "{\"value\":\"line paragraph end\"}",
+ "digest": "92f0f4bb82a26bac6b6ff0a8c15283961f927c0664d32c22c795395b59b59c03"
+ },
+ {
+ "id": "redacted-marker",
+ "input": {
+ "value": "«redacted»"
+ },
+ "inputJson": "{\"value\":\"«redacted»\"}",
+ "canonical": "{\"value\":\"«redacted»\"}",
+ "digest": "39fa045469afc9faac3f99e320df59328cc73253352fcfbd9107c8aa1fcb2c98"
+ }
+ ],
+ "strictJson": [
+ {
+ "id": "invalid-utf8",
+ "inputBytesBase64": "eyJ2YWx1ZSI6IsMoIn0=",
+ "expectedErrorIncludes": "invalid UTF-8"
+ },
+ {
+ "id": "escaped-high-surrogate",
+ "inputBytesBase64": "eyJ2YWx1ZSI6Ilx1ZDgwMCJ9",
+ "expectedErrorIncludes": "surrogate forms"
+ },
+ {
+ "id": "escaped-surrogate-pair",
+ "inputBytesBase64": "eyJ2YWx1ZSI6Ilx1ZDgzZFx1ZGUwMCJ9",
+ "expectedErrorIncludes": "surrogate forms"
+ }
+ ],
+ "bytes": {
+ "factfile": {
+ "path": "factfiles/verified.json",
+ "bytesDigest": "4511a0900d2b7e249645384451879601be1942e23941da42fe2a5644ed1e8520",
+ "byteLength": 3237,
+ "factfileDigest": "4558e8787677ff34462d14815c823c94c5542e626688a071fa0715a56fe4b417"
+ },
+ "asset": {
+ "kind": "video",
+ "path": "assets/demo-bytes.bin",
+ "label": "Deterministic conformance asset bytes",
+ "caption": "Digest fixture only; these bytes are intentionally not playable media or live evidence.",
+ "digest": "d15e52289df855aecec2dbbe52a784c51a4b3c31959c1be72993192dcea69a8e",
+ "byteLength": 40,
+ "posterPath": "assets/poster.svg",
+ "posterDigest": "93c68dcdcb3949e0c0bb86377b0ea3bcb2b7a0729a1544f5e0d2d03c3a84bd4c"
+ },
+ "poster": {
+ "path": "assets/poster.svg",
+ "posterDigest": "93c68dcdcb3949e0c0bb86377b0ea3bcb2b7a0729a1544f5e0d2d03c3a84bd4c",
+ "byteLength": 863
+ }
+ },
+ "eventSets": {
+ "generic": "events/generic.jsonl",
+ "generic-through-verification": "events/generic-through-verification.jsonl",
+ "generic-through-checkpoint": "events/generic-through-checkpoint.jsonl",
+ "generic-reversed": "events/generic-reversed.jsonl",
+ "generic-same-time-ambiguous": "events/generic-same-time-ambiguous.jsonl",
+ "processyard": "events/processyard.jsonl",
+ "source-conflict": "events/source-conflict.jsonl"
+ },
+ "ordering": [
+ {
+ "id": "canonical-order",
+ "eventSet": "generic",
+ "expectedEventIds": [
+ "generic-started",
+ "generic-verifying",
+ "generic-checkpoint",
+ "generic-completed"
+ ],
+ "expectedCheckpointIds": [
+ "cart-recovery-verified"
+ ]
+ },
+ {
+ "id": "reversed-input-same-replay",
+ "eventSet": "generic-reversed",
+ "expectedEventIds": [
+ "generic-started",
+ "generic-verifying",
+ "generic-checkpoint",
+ "generic-completed"
+ ],
+ "expectedCheckpointIds": [
+ "cart-recovery-verified"
+ ]
+ },
+ {
+ "id": "same-time-rank-ambiguity",
+ "eventSet": "generic-same-time-ambiguous",
+ "expectedErrorIncludes": "ambiguous ordering"
+ }
+ ],
+ "sourceConflict": {
+ "eventSet": "source-conflict",
+ "dispatchVector": "suppress-source-conflict"
+ },
+ "dispatch": [
+ {
+ "id": "suppress-attested-checkpoint",
+ "eventSet": "generic",
+ "plan": {
+ "now": "2026-08-24T16:05:00.000Z",
+ "staleAfterMs": 300000,
+ "debounceMs": 0,
+ "deliveredContentDigests": []
+ },
+ "expected": {
+ "outcome": "suppress",
+ "reasonCode": "attested_checkpoint",
+ "failClosed": true,
+ "checkpointIds": [
+ "cart-recovery-verified"
+ ]
+ }
+ },
+ {
+ "id": "defer-uncheckpointed-work",
+ "eventSet": "generic-through-verification",
+ "plan": {
+ "now": "2026-08-24T16:02:10.000Z",
+ "staleAfterMs": 300000,
+ "debounceMs": 0,
+ "deliveredContentDigests": []
+ },
+ "expected": {
+ "outcome": "defer",
+ "reasonCode": "fresh_uncheckpointed_work",
+ "failClosed": false,
+ "checkpointIds": []
+ }
+ },
+ {
+ "id": "defer-fresh-agent-with-candidate",
+ "eventSet": "generic-through-checkpoint",
+ "plan": {
+ "now": "2026-08-24T16:03:10.000Z",
+ "staleAfterMs": 300000,
+ "debounceMs": 0,
+ "deliveredContentDigests": []
+ },
+ "expected": {
+ "outcome": "suppress",
+ "reasonCode": "attested_checkpoint",
+ "failClosed": true,
+ "checkpointIds": [
+ "cart-recovery-verified"
+ ]
+ }
+ },
+ {
+ "id": "coalesce-compatible-checkpoints",
+ "eventSet": "processyard",
+ "plan": {
+ "now": "2026-08-24T16:42:00.000Z",
+ "staleAfterMs": 3600000,
+ "debounceMs": 0,
+ "deliveredContentDigests": [
+ "0109d493f8993301b26be76be16360cdc7c8e0dad17a9f0d217ce30528b1d8fa",
+ "4c0f9e4e2b11d590dffb1a77453f16da0e7f899169f6bddc962772dd65bf25f1",
+ "2b1a303bdee856b4cc22792b95542e3c422a0ed4e76f6a18cec76bcf7546d80f",
+ "9e04a6fc2293ae7c714f7aab88933ce3eadd75e04466f5e39257a21c67465478",
+ "d7a37f11701a8ff47bb028b6ce6e43e2fea916e75b256c981b15ff228ce05ba4"
+ ]
+ },
+ "expected": {
+ "outcome": "suppress",
+ "reasonCode": "attested_checkpoint",
+ "failClosed": true,
+ "checkpointIds": [
+ "processyard-m5",
+ "processyard-m6"
+ ]
+ }
+ },
+ {
+ "id": "stale-no-send",
+ "eventSet": "processyard",
+ "plan": {
+ "now": "2026-08-24T16:59:00.000Z",
+ "staleAfterMs": 300000,
+ "debounceMs": 0,
+ "deliveredContentDigests": [
+ "0109d493f8993301b26be76be16360cdc7c8e0dad17a9f0d217ce30528b1d8fa",
+ "4c0f9e4e2b11d590dffb1a77453f16da0e7f899169f6bddc962772dd65bf25f1",
+ "2b1a303bdee856b4cc22792b95542e3c422a0ed4e76f6a18cec76bcf7546d80f",
+ "9e04a6fc2293ae7c714f7aab88933ce3eadd75e04466f5e39257a21c67465478",
+ "d7a37f11701a8ff47bb028b6ce6e43e2fea916e75b256c981b15ff228ce05ba4"
+ ]
+ },
+ "expected": {
+ "outcome": "stale_no_send",
+ "reasonCode": "stale_activity_lease",
+ "failClosed": true,
+ "checkpointIds": []
+ }
+ },
+ {
+ "id": "suppress-source-conflict",
+ "eventSet": "source-conflict",
+ "plan": {
+ "now": "2026-08-25T16:03:00.000Z",
+ "staleAfterMs": 300000,
+ "debounceMs": 0,
+ "deliveredContentDigests": []
+ },
+ "expected": {
+ "outcome": "suppress",
+ "reasonCode": "source_conflict",
+ "failClosed": true,
+ "checkpointIds": [
+ "conflict-left-checkpoint",
+ "conflict-right-checkpoint"
+ ]
+ }
+ }
+ ]
+}
diff --git a/fixtures/pulse/README.md b/fixtures/pulse/README.md
new file mode 100644
index 0000000..70ea556
--- /dev/null
+++ b/fixtures/pulse/README.md
@@ -0,0 +1,19 @@
+# Pulse integration fixtures
+
+- `generic.jsonl` is the harness-neutral JSONL/stdin contract.
+- `processyard-m0-m6.jsonl` is the Processyard M0–M6 multi-harness story.
+- `processyard-coalesced.json` retains its historical filename but now records the fail-closed `attested_checkpoint` suppression decision.
+- `processyard-stale-no-send.json` is the later fail-closed decision after that lease becomes stale.
+- `processyard-timeline.html` explains why no audience projection exists for synthetic attested checkpoints.
+
+These are generated fixtures, not live customer evidence. They are visibly
+`attested`, never `verified`, and never dispatchable. The Economy Theatre media
+paths intentionally remain unresolved because the corresponding bytes are not
+present in this repository.
+
+Regenerate after building:
+
+```bash
+npm run build
+npm run fixtures:pulse
+```
diff --git a/fixtures/pulse/generic.jsonl b/fixtures/pulse/generic.jsonl
new file mode 100644
index 0000000..18d81cd
--- /dev/null
+++ b/fixtures/pulse/generic.jsonl
@@ -0,0 +1,4 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"8f4cf64f696e0a95e1d9d9865a609a66531e8fceff0bc1e43d309e4bbb9c4d5a","type":"started","leaseId":"generic-run-agent-1","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"generic-run-agent-1","harness":"generic-jsonl","project":{"id":"checkout-example","name":"Checkout Example"},"runId":"checkout-example-run-20260824","agent":{"id":"agent-1","name":"Fixture agent"},"canonicalSourceRoot":"repo://example/checkout","task":{"id":"restore-cart","title":"Restore the saved cart","outcome":"Returning shoppers recover the same products and quantities."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"b6359aff2fbdabcc47410462203c78e065e2378b03a102fe7112dfd431e792bd"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-verifying","at":"2026-08-24T16:02:00.000Z","eventDigest":"7290a26fed55502deeb10462743d5ae2d27ef8fab21ff359606f1f1572f51367","type":"verification_started","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-checkpoint","at":"2026-08-24T16:03:00.000Z","eventDigest":"05060a1add9a0b247f44267fd9f6ac3104a7fdbdb2b6d5c348910f0ab6f53512","type":"checkpoint_published","leaseId":"generic-run-agent-1","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"cart-recovery-verified","projectId":"checkout-example","outcomeId":"restore-cart","runId":"checkout-example-run-20260824","leaseIds":["generic-run-agent-1"],"title":"Saved-cart fixture checkpoint","changeSummary":"The cart recovery path now restores product ids and quantities from the saved session.","whyItMatters":"Returning shoppers can continue checkout without rebuilding their cart.","publishedAt":"2026-08-24T16:03:00.000Z","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:03:00.000Z","methods":[{"kind":"command","label":"cart-recovery-verified acceptance check","reproduce":"./scripts/verify-checkpoint cart-recovery-verified","result":"Passed in the integration fixture","evidenceDigest":"95e173bc297d6ab5ff167c3dad6ad4cb59380871cd118a4efb0d9647453693d1"}]},"evidenceBinding":{"mode":"fixture","label":"checkout-example demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"cart-recovery-verified-factfile","projectId":"checkout-example","outcomeId":"restore-cart","path":".keyoku/contributions/cart-recovery-verified/factfile.json","digest":"34b9827ff17251b1d578faabd89898c34c6e84458fdbd90d5a1c85e45d9fb3ac","sourceDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c","state":"ready_for_review"}],"assets":[],"limitations":["Visual review of the empty-cart state is still pending."],"nextTask":"Capture the empty-cart visual Factfile.","materialTrigger":"verified_checkpoint","contentDigest":"64bbc59fb709ca09cc7a78b23b45611a3f2bc2cc351a69f5b393fe430f32b734"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"generic-completed","at":"2026-08-24T16:04:00.000Z","eventDigest":"f079776e16bb6e69e33580afb34afb77320b18b9a07aa59c17621eaf9c36220a","type":"completed","leaseId":"generic-run-agent-1","source":{"canonicalRoot":"repo://example/checkout","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"b3f18dcdce5146ed0253c6c773c40971c70aea21b02459d2d6e67bdb92470f3c"},"checkpointId":"cart-recovery-verified"}
diff --git a/fixtures/pulse/processyard-coalesced.json b/fixtures/pulse/processyard-coalesced.json
new file mode 100644
index 0000000..e8c0295
--- /dev/null
+++ b/fixtures/pulse/processyard-coalesced.json
@@ -0,0 +1,12 @@
+{
+ "schemaVersion": "keyoku.dev/pulse-dispatch/v1alpha1",
+ "plannedAt": "2026-08-24T16:42:00.000Z",
+ "outcome": "suppress",
+ "reasonCode": "attested_checkpoint",
+ "reason": "2 checkpoints are fixture- or adapter-attested, not locally verified. No dispatchable snapshot was produced.",
+ "failClosed": true,
+ "checkpointIds": [
+ "processyard-m5",
+ "processyard-m6"
+ ]
+}
diff --git a/fixtures/pulse/processyard-m0-m6.jsonl b/fixtures/pulse/processyard-m0-m6.jsonl
new file mode 100644
index 0000000..3477aa0
--- /dev/null
+++ b/fixtures/pulse/processyard-m0-m6.jsonl
@@ -0,0 +1,13 @@
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-development-started","at":"2026-08-24T16:00:00.000Z","eventDigest":"879425a35947b4f12a53c501c5abf3cd99dbb8fbcc594cbe784fe14870989395","type":"started","leaseId":"processyard-development","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-development","harness":"codex","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"development-agent","name":"Development agent"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre","title":"Build the Economy Theatre story","outcome":"A founder can see verified product progress without reading agent transcripts."},"startedAt":"2026-08-24T16:00:00.000Z","heartbeatAt":"2026-08-24T16:00:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-evidence-started","at":"2026-08-24T16:01:00.000Z","eventDigest":"44e1c837a78f260f60b6e043c9a23bcbfd7b8fc0a17d54b9ae62b800cde75f06","type":"started","leaseId":"processyard-evidence","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-evidence","harness":"claude-code","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"evidence-agent","name":"Evidence agent"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre-evidence","title":"Capture human-readable product evidence","outcome":"The milestone story includes replayable UI evidence and explicit limitations."},"startedAt":"2026-08-24T16:01:00.000Z","heartbeatAt":"2026-08-24T16:01:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-ci-started","at":"2026-08-24T16:02:00.000Z","eventDigest":"4b5db6b77462a3eee14495eb347190a633ba57544f0c7497f09f9ff1f8907084","type":"started","leaseId":"processyard-ci","lease":{"schemaVersion":"keyoku.dev/pulse-lease/v1alpha1","id":"processyard-ci","harness":"github-actions","project":{"id":"processyard","name":"Processyard"},"runId":"processyard-run-20260824","agent":{"id":"verification-workflow","name":"Verification workflow"},"canonicalSourceRoot":"repo://processyard/main","task":{"id":"economy-theatre-verification","title":"Verify the checkpoint boundary","outcome":"Every reported milestone is bound to a reproducible Factfile and exact source digest."},"startedAt":"2026-08-24T16:02:00.000Z","heartbeatAt":"2026-08-24T16:02:00.000Z","state":"working","currentSource":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"}}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m0-published","at":"2026-08-24T16:10:00.000Z","eventDigest":"00ebb5675a083f4643522730dd21dd160de4709f4356708ec633d6c6e6d98653","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m0","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M0 · Outcome pinned","changeSummary":"The founder outcome and source boundary were recorded before implementation.","whyItMatters":"The milestone story starts from an explicit definition of done.","publishedAt":"2026-08-24T16:10:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"1111111111111111111111111111111111111111","worktreeDigest":"8888888888888888888888888888888888888888888888888888888888888888","ancestryShas":[],"verifiedDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:10:00.000Z","methods":[{"kind":"command","label":"processyard-m0 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m0","result":"Passed in the integration fixture","evidenceDigest":"fe5b19d3508b254ac100801b7f9ef21e93359da67f8ac78a42f1d819f767c47e"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m0-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m0/factfile.json","digest":"687f833e5578025cdba94cbefd6cc2f61c66545508c1f1cdf3e36294e0e3253c","sourceDigest":"1ceb1ca10e27873d4c61eddd81058dd41ba3616ea8e5a1891d9aea76fbb9c95a","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M1 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"4cf871a73c22ddc216fdf13333a978296eacdd40abd0e7ee47ea1a813b38105e"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m1-published","at":"2026-08-24T16:15:00.000Z","eventDigest":"829e100c648f57406414915517f48b2133ac5158efdf707542cc1d4fdff28a65","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m1","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M1 · Storefront path observed","changeSummary":"The current customer journey and evidence gaps were captured.","whyItMatters":"Work begins from the real product path instead of an imagined interface.","publishedAt":"2026-08-24T16:15:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"2222222222222222222222222222222222222222","worktreeDigest":"9999999999999999999999999999999999999999999999999999999999999999","ancestryShas":["1111111111111111111111111111111111111111"],"verifiedDigest":"0760994c45cc1f3c6a6c0c616f3dbb5ef1bf7f6154b7bbcdb0b9556cc29e74cb"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:15:00.000Z","methods":[{"kind":"command","label":"processyard-m1 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m1","result":"Passed in the integration fixture","evidenceDigest":"107bac10ebc481c8ba14b7ecc16aac336c203875fb90a2248087377217d10ca3"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m1-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m1/factfile.json","digest":"ed1472e4ff2362c8dd6cc5d53ea7955995d8ac0b80982326c8e6ed74e4e658d8","sourceDigest":"0760994c45cc1f3c6a6c0c616f3dbb5ef1bf7f6154b7bbcdb0b9556cc29e74cb","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M2 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"585c83ad4c10a2a082d4268721d193417885f9e835137a51d42603654d80a5cf"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m2-published","at":"2026-08-24T16:20:00.000Z","eventDigest":"c472daea268139d4eceb38852c858f76455fd4009fa8a347521d552c7289fd16","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m2","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M2 · Economy Theatre implemented","changeSummary":"The demonstration flow now shows the product outcome in a browser-facing experience.","whyItMatters":"A non-technical stakeholder can understand the product without a terminal transcript.","publishedAt":"2026-08-24T16:20:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"3333333333333333333333333333333333333333","worktreeDigest":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222"],"verifiedDigest":"08c09c1b6927f28aba863849a63163971c9f0c14736b8d31f430067ee40508cd"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:20:00.000Z","methods":[{"kind":"command","label":"processyard-m2 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m2","result":"Passed in the integration fixture","evidenceDigest":"65138d03ca6c74288a4943fbabd5f492c9342e821482a8a757942fc7ff86bc93"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m2-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m2/factfile.json","digest":"fef0f000904d60a217f005a9eeab2461ca452073283ced41b1e6e3c02cf1960c","sourceDigest":"08c09c1b6927f28aba863849a63163971c9f0c14736b8d31f430067ee40508cd","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M3 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"2b06dfadc33bec7f2d54b416d84b5fb8d7a167f2035682f1158d858112abcceb"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m3-published","at":"2026-08-24T16:25:00.000Z","eventDigest":"ebd441eb26f7332dabdc02faf473fff52c0143ee488a7797fcf977f0480faec6","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m3","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M3 · Checks bound to source","changeSummary":"Automated checks and the changed-file boundary were bound to one source identity.","whyItMatters":"Passing output can no longer drift away from the code it describes.","publishedAt":"2026-08-24T16:25:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"4444444444444444444444444444444444444444","worktreeDigest":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333"],"verifiedDigest":"337133589bea8a4f1d4c467b62390df227178b704612248789af685637982d23"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:25:00.000Z","methods":[{"kind":"command","label":"processyard-m3 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m3","result":"Passed in the integration fixture","evidenceDigest":"840d4ac0247eaab7a9b9ccdda08b9c94f183e8c0c326a904b7545925fe3d035c"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m3-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m3/factfile.json","digest":"7bc36fd3bcabdee84b8b4a3287831e89d4a25f8d69257dd07cb3e1994541d71c","sourceDigest":"337133589bea8a4f1d4c467b62390df227178b704612248789af685637982d23","state":"ready_for_review"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M4 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"c20b7d41f4634f1d0c30f15f9acc32c37bb8ad8fc65539fdeca65c556871b1d1"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m4-published","at":"2026-08-24T16:30:00.000Z","eventDigest":"496b57150697b39e97cbc688926c357e1d4d18c0733b4ed434ae3c3d9f9f4575","type":"checkpoint_published","leaseId":"processyard-development","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m4","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-development"],"title":"M4 · Owner decision isolated","changeSummary":"The remaining launch choice was separated from machine verification.","whyItMatters":"The agent can continue independent work without manufacturing stakeholder consent.","publishedAt":"2026-08-24T16:30:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"5555555555555555555555555555555555555555","worktreeDigest":"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444"],"verifiedDigest":"3341f7f2f19d06bcbacdcf98bd86e5b54a9e111a3838ab08ebdf7e777813d023"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:30:00.000Z","methods":[{"kind":"command","label":"processyard-m4 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m4","result":"Passed in the integration fixture","evidenceDigest":"1217b4c5e79770835bb24ba1d90673c095bb001fe1e74e4a0a8f79b12b369c64"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m4-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m4/factfile.json","digest":"a9019a697c343aef122f6f1b431c63e8d05619238e43f4c8ce8130f89583c8f9","sourceDigest":"3341f7f2f19d06bcbacdcf98bd86e5b54a9e111a3838ab08ebdf7e777813d023","state":"human_review_required"}],"assets":[],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M5 without changing the established source boundary.","humanDecisionRequest":{"id":"processyard-launch-boundary","title":"Choose the public launch boundary","whyHuman":"This changes the promise made to customers and belongs to the owner.","requestedAction":"Choose whether the launch stays local-first or includes the hosted Engine path.","options":["Local-first Keyoku","Keyoku plus hosted Engine"]},"materialTrigger":"owner_decision","contentDigest":"cef1790fd757ed233d58e4f23e488d3a0e44e3e7eb0785260c8ff8e5d6b1ab3f"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m5-published","at":"2026-08-24T16:35:00.000Z","eventDigest":"ade66ac5a541d706411edf7c658fc5441429b99de5f1785fa2971583edfe09f9","type":"checkpoint_published","leaseId":"processyard-evidence","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m5","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-evidence"],"title":"M5 · Evidence story staged","changeSummary":"A poster and replay path were declared in the synthetic checkpoint.","whyItMatters":"The fixture shows the intended evidence shape but cannot claim the referenced bytes exist.","publishedAt":"2026-08-24T16:35:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"6666666666666666666666666666666666666666","worktreeDigest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555"],"verifiedDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:35:00.000Z","methods":[{"kind":"command","label":"processyard-m5 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m5","result":"Passed in the integration fixture","evidenceDigest":"906f1a5d2beed676fa4010ce3450c7b5e9ba50829a4b8648296fd08600764b97"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m5-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m5/factfile.json","digest":"264d1e497735c4ae9c79e980af89c5f1bcb6cdb538969c58affeffe4407511a0","sourceDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6","state":"ready_for_review"}],"assets":[{"kind":"video","path":"evidence/economy-theatre-demo.mp4","label":"Economy Theatre product demonstration","caption":"Expected Processyard poster and replay binding; no matching media bytes were found in the checked workspace, so a live integration must resolve and digest them before dispatch.","posterPath":"evidence/economy-theatre-poster.png"}],"limitations":["Later Processyard milestones remain outside this checkpoint."],"nextTask":"Verify M6 without changing the established source boundary.","materialTrigger":"verified_checkpoint","contentDigest":"90174f7d442b03c9aeaa78bba3560a07695667e5a6f61e9b818193f291d6e053"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-m6-published","at":"2026-08-24T16:40:00.000Z","eventDigest":"2a68c11fb7e0f4223c7f5d18bc1841ff3d74b557d64b26540b8389cb4b217457","type":"checkpoint_published","leaseId":"processyard-ci","checkpoint":{"schemaVersion":"keyoku.dev/pulse-checkpoint/v1alpha1","id":"processyard-m6","projectId":"processyard","outcomeId":"processyard-modernization","runId":"processyard-run-20260824","leaseIds":["processyard-ci"],"title":"M6 · Release boundary rehearsed","changeSummary":"The complete M0–M6 story passed the fixture's schema and replay checks.","whyItMatters":"A live integration must still promote local Factfiles before any stakeholder snapshot is dispatchable.","publishedAt":"2026-08-24T16:40:00.000Z","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"verification":{"status":"attested","verifiedAt":"2026-08-24T16:40:00.000Z","methods":[{"kind":"command","label":"processyard-m6 acceptance check","reproduce":"./scripts/verify-checkpoint processyard-m6","result":"Passed in the integration fixture","evidenceDigest":"b835de76c3ef23734588fdab36234b11a582604e96f6158e4b7877505403879b"}]},"evidenceBinding":{"mode":"fixture","label":"processyard demonstration fixture; adapters must bind real Factfile bytes before delivery"},"factfiles":[{"id":"processyard-m6-factfile","projectId":"processyard","outcomeId":"processyard-modernization","path":".keyoku/contributions/processyard-m6/factfile.json","digest":"b573f29e6a47e3b7c80fd589b5584a5c9f16deb9fde25a30b665792d5c7c0930","sourceDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3","state":"ready_for_review"}],"assets":[],"limitations":["No production deployment is established by this local fixture.","Gmail delivery authority and sent-message verification are not configured."],"nextTask":"Request explicit channel authority before preparing any founder email delivery.","materialTrigger":"verified_checkpoint","contentDigest":"9b4febd6a6374379c2e7650e7f0d45d2a8240671b4781818c01d957534e5a0f8"}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-development-blocked","at":"2026-08-24T16:41:10.000Z","eventDigest":"b7e492b703322a942cec915be785464854fce617081b3770d258d002ac0a1977","type":"blocked","leaseId":"processyard-development","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"reason":"The long-running development lease is waiting for the explicit launch-boundary decision.","humanDecisionRequest":{"id":"processyard-launch-boundary","title":"Choose the public launch boundary","whyHuman":"This changes the promise made to customers and belongs to the owner.","requestedAction":"Choose whether the launch stays local-first or includes the hosted Engine path.","options":["Local-first Keyoku","Keyoku plus hosted Engine"]}}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-evidence-completed","at":"2026-08-24T16:41:20.000Z","eventDigest":"637511958e61adeb17e61e876d9617ec01a4c38363a52706d99812f196254a56","type":"completed","leaseId":"processyard-evidence","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"6666666666666666666666666666666666666666","worktreeDigest":"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555"],"verifiedDigest":"96ce6096523207ba3f550a5ae5ba392d44d423c618a6901b4c014a9aa51264f6"},"checkpointId":"processyard-m5"}
+{"schemaVersion":"keyoku.dev/pulse-event/v1alpha1","id":"processyard-ci-completed","at":"2026-08-24T16:41:30.000Z","eventDigest":"94b886f5446fd5634d84ab6e6e5c0ab7dc69c4d3e404da0532aa67404abf6cbb","type":"completed","leaseId":"processyard-ci","source":{"canonicalRoot":"repo://processyard/main","branch":"main","baseSha":"1111111111111111111111111111111111111111","headSha":"7777777777777777777777777777777777777777","worktreeDigest":"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","ancestryShas":["1111111111111111111111111111111111111111","2222222222222222222222222222222222222222","3333333333333333333333333333333333333333","4444444444444444444444444444444444444444","5555555555555555555555555555555555555555","6666666666666666666666666666666666666666"],"verifiedDigest":"0506d0991fde0f8580576468ed61bbcce4795a96da704c2ecf2dcccac7f598f3"},"checkpointId":"processyard-m6"}
diff --git a/fixtures/pulse/processyard-stale-no-send.json b/fixtures/pulse/processyard-stale-no-send.json
new file mode 100644
index 0000000..474f3f8
--- /dev/null
+++ b/fixtures/pulse/processyard-stale-no-send.json
@@ -0,0 +1,9 @@
+{
+ "schemaVersion": "keyoku.dev/pulse-dispatch/v1alpha1",
+ "plannedAt": "2026-08-24T16:59:00.000Z",
+ "outcome": "stale_no_send",
+ "reasonCode": "stale_activity_lease",
+ "reason": "No normal update: 1 active lease is stale; the last trusted checkpoint is frozen.",
+ "failClosed": true,
+ "checkpointIds": []
+}
diff --git a/fixtures/pulse/processyard-timeline.html b/fixtures/pulse/processyard-timeline.html
new file mode 100644
index 0000000..fe75862
--- /dev/null
+++ b/fixtures/pulse/processyard-timeline.html
@@ -0,0 +1 @@
+Attested fixture — no projection
This synthetic fixture is attested, not locally verified. Keyoku correctly produced no dispatchable timeline.
";
+ const limits = (snapshot.contribution.knownLimits?.length ? snapshot.contribution.knownLimits : ["Only the claims shown here were evaluated.", "Passing checks do not establish product fit, maintainability, or universal security.", "Any source change requires a new Factfile."]).map((limit) => `
`;
+ const directionActionsHtml = directionSuggestions.length
+ ? `Write a custom direction — when prepared paths miss your intent
State what should change, the constraint to preserve, and which evidence should look different afterward.
`
+ : "";
+ const humanDecisionHtml = `
+
Human decision
+
Required judgment
${blockedInsightItems}${humanInsightItems}
+ `;
+
+ // Evidence is the product: keep a small claim set visible, with coordination subordinate. ------
+ const evidenceFold = `Evidence & reproduction${snapshot.summary.passed}/${snapshot.summary.total} supported · ${artifactCount} artifacts›