Local E2E Workflow
The formal local end-to-end loop is now repository-owned:
- a sanitized Obsidian vault fixture:
tests/fixtures/obsidian_vault_minimal - a real disposable NocoDB service:
tests/fixtures/nocodb_local - one formal suite:
tests/test_e2e_obsidian_nocodb.py
This is the standard way to validate the business loop:
Obsidian Thought -> ingest -> reactive action -> Bookmark -> writeback -> NocoDB
Preconditions
- Docker-compatible runtime available locally OrbStack headless was validated as one working option.
uvinstalled
One-command entry point
Use the repository script:
scripts/run-local-e2e.shThis does four things in order:
docker compose up -dfor the local NocoDB service- bootstraps the
自媒体作品快照 / 作品快照schema and seed rows - loads the generated env file
- runs the formal e2e suite
Useful variants
Run pytest in quiet mode:
scripts/run-local-e2e.sh -- -qStop the local NocoDB service after the run:
scripts/run-local-e2e.sh --downRebuild from a clean local state:
scripts/run-local-e2e.sh --fresh -- -qFilter to one test:
scripts/run-local-e2e.sh -- -k writeback -qGenerated state
Bootstrap writes:
tests/fixtures/nocodb_local/.env.generatedThat file is the source of truth for:
RUNEX_E2E_NOCO_URLRUNEX_E2E_NOCO_TOKENRUNEX_E2E_NOCO_BASE_IDRUNEX_E2E_NOCO_TABLE_ID
Runtime NocoDB data lives under:
tests/fixtures/nocodb_local/.runtime/Both paths are git-ignored.
Clean rebuild mode
Use --fresh when you want to throw away the local NocoDB runtime state
and bootstrap again from scratch:
scripts/run-local-e2e.sh --fresh -- -qThis does three extra things before the normal workflow:
docker compose down- removes
tests/fixtures/nocodb_local/.runtime/ - removes
tests/fixtures/nocodb_local/.env.generated
Use it after changing bootstrap logic, table shape, or seed assumptions.
What is actually proven
The formal suite covers two assertions:
- the local NocoDB seed rows are readable through the real
media-snapshotsource adapter - a real Obsidian Thought fixture containing a bilibili link ingests, creates a Bookmark, and writes that link into the local NocoDB table
This is intentionally stronger than a stub-only test and intentionally safer than using production NocoDB.
Maintenance rule
When a fixture note, NocoDB column mapping, or ontology rule changes, rerun:
scripts/run-local-e2e.sh -- -qDo not treat the local fixture as throwaway data. It is part of the repository contract.
Proactive reflex arc (v0.2 Phase 5)
The second repository-owned loop is the proactive reflex arc — the milestone’s complete user-visible reflex: a Thought becomes a reviewed, approved, real Task, with every hop visible in graph state and event history.
Obsidian Thought
→ ingest → reactive request_extraction (Thought machine: new → extraction_requested)
→ ExtractionScheduler signals a WorkItem (durable, off the bus)
→ WorkerLoop runs the extraction Agent handler (slow I/O, off the bus)
→ Task Proposal written back (pending_review) with provenance + link to the Thought
→ StoreWatcher projects the Proposal into Obsidian for review
→ human sets `审批结果: approved` in Frontmatter
→ ingest → reactive approve_proposal (Proposal machine → approved)
→ ProposalApplier materializes exactly one real Task (idempotent by natural key)
→ StoreWatcher projects the Task back into ObsidianFormal suite: tests/test_reflex_arc_e2e.py.
It runs from a clean RUNEX_HOME, injects a worker crash during the work stage
(expired lease → recovery → exactly-once), and asserts no echo loop, full
provenance, and that rejecting creates no Task.
Run it
uv run python -m pytest tests/test_reflex_arc_e2e.py -qDrive it live against a vault
The long-lived host composes all stages (ingest → signal → recover → work → materialize → project) in one supervised loop:
runex ontology load <skill>/ontology/core/memory.scm
runex ontology load <skill>/ontology/core/conflict.scm
runex ontology load <skill>/ontology/core/workitem.scm
runex ontology load <skill>/ontology/core/proposal.scm
runex ontology listen --vault /path/to/vault --poll 2To review pending proposals from the terminal (the operator/debug fallback — the canonical review surface is the Obsidian Frontmatter):
runex ontology proposals # list, with state + risk + summary
runex ontology proposal-approve <提议键> --by me --reason "..."
runex ontology proposal-reject <提议键> --by meHow the layers hold the boundary (ADR-0010)
- Decision/policy is ontology data. The Thought and Proposal machines, their
guards, and field ownership live in
.scm. Approval is a later graph write (a field a human/CLI/Obsidian sets), never a waiting thread. - Slow work is durable and off the bus. Extraction runs in a handler behind a
WorkItemwith lease/retry/recovery — a crash mid-work is reclaimed and finished exactly once. - Scheduling and materialization are thin peripherals.
ExtractionSchedulerturns extraction-requested Thoughts into WorkItems via the one blessedsignal_workitem;ProposalApplierupserts the approved command batch by stable natural key. Both are idempotent, so duplicate delivery / restart / replay never double-applies an effect.
Troubleshooting
- A proposal never appears in the vault. A machine-born node is projected
only once it has a
来源路径. The Agent/applier stamp it under the vault when given a projection base (--vault); without a base, the arc still runs graph-only (no Obsidian files). - An approval edit seems ignored. The supertag is resolved from the
type:Frontmatter key; keep it. Set审批结果to exactlyapproved/rejected/revision/cancelled. The decision only fires frompending_review/revision_requested. - A Task is materialized twice. It should not be: materialization upserts by
natural key and stamps
已实现. If you see duplicates, check that the proposed command’snatural_keymatches the target supertag’s schema. 物化错误is set on a Proposal. The command batch failed re-validation against the current manifest (stale context). Fix the schema drift; the applier retries the still-approved proposal on the next tick.
Safe natural-language rule evolution (v0.2 Phase 6)
The third repository-owned loop lets a human change behavior in words without
giving an Agent direct production authority. A BusinessRule flows:
draft (人写自然语言 源稿)
→ compile (Compiler Agent → .scm 编译产物, off the bus) draft → compiled
→ verify (RuleVerifier: parse + static check vs manifest + primitive catalog)
compiled → verified | failed
→ approve (人写 审批结果: approved in Frontmatter/CLI) verified → approved
→ deploy (RuleDeployer loads the .scm into production) approved → deployed
→ rollback (reload the preserved prior snapshot) deployed → rolled_backFormal suite: tests/test_rule_compiler.py,
including the dogfood rule “remind on a blocked Task” — which actually changes
production behavior only after it is approved and deployed.
The vocabulary a rule may use (RULE-01)
runex ontology primitives # full catalog
runex ontology primitives --kind effect # only write primitives
runex ontology primitives --json # machine-readable (for a compiler)Each entry carries kind (control/builtin/read/effect), arity, whether it is
guard-safe, a summary, and a minimal example. The verifier rejects any symbol
outside this catalog as an unknown_primitive diagnostic.
Review & deploy (RULE-06)
The canonical approval surface is the Obsidian Frontmatter (审批结果: approved).
The operator/debug fallback:
runex ontology rules # list with state + version + diag flag
runex ontology rule-approve <规则键> --by meWhat the verifier catches (RULE-04/05)
Each is written back as a structured 诊断 entry and fails the rule closed
(→ failed), so it never reaches production:
parse_error— the draft is not valid.scmunknown_field— a(field "X")/(set-field "X" …)naming a field the target supertag does not declare (the canonical “fails visibly” case)unknown_primitive— a symbol outside the primitive catalogunknown_machine— an action on a machine that does not existblocking_kernel— a blocking kernel called directly in an effect (would freeze the bus; use Signal-Then-Work)
How the layers hold the boundary (ADR-0010)
- Lifecycle/policy is ontology data (
businessrule.scm). Approval is a later graph write, never a waiting thread. - Compilation is slow Agent work, off the bus — it only produces a draft; it has no deploy authority (RULE-03).
- Verify and deploy are thin peripherals. The verifier is pure static
analysis against the live manifest + primitive catalog; the deployer is the
only component that loads into production, and only from
approved.
Troubleshooting
- A rule sits in
compiled. The verifier stage hasn’t run, or it failed — checkrunex ontology rulesand the rule’s诊断. - A rule is
failedwithunknown_field. The compiled.scmreferences a field the supertag doesn’t have. Fix the source/compiler; re-author to retry. - An approved rule didn’t deploy. Deploy loads the
.scm; a load error fails the rule closed with adeploy_errordiagnostic. Inspect诊断. - Rollback didn’t remove a newly-added action. v0.2 rollback is a best-effort
override-restore of the prior snapshot; fully retracting actions a draft
added needs loader unload support (a later phase). The prior version is
preserved in
回滚目标either way.