Skip to content

Architecture simplification: extract VerbRegistry per-verb files, shared Pipeline guards, Session Forwardable - #249

Merged
patrick204nqh merged 38 commits into
mainfrom
arch-simplification
Jul 5, 2026
Merged

Architecture simplification: extract VerbRegistry per-verb files, shared Pipeline guards, Session Forwardable#249
patrick204nqh merged 38 commits into
mainfrom
arch-simplification

Conversation

@patrick204nqh

@patrick204nqh patrick204nqh commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

5 clean-up commits extracted from the ongoing architecture work:

  1. extract shared CheckEtag/ResolvePath into Pipeline module — pulls duplicate path-resolution and etag-checking guards into a shared Pipeline concern, used by PutPipeline, DeletePipeline, and MovePipeline.
  2. move MovePipeline emit_event dedup guards into shared Pipeline.emit_event — consolidates event deduplication.
  3. split VerbRegistry into per-verb files — breaks the monolithic 231-line VerbRegistry into one file per verb under verb_registry/verbs/.
  4. extract merge_issues helper in Definition#check — inline simplification in the workflow DSL.
  5. replace hand-written Session delegations with Forwardable — replaces manual delegation with Ruby stdlib Forwardable.

Testing

bundle exec rspec — all green.

…on from pipeline

Two changes from architecture review (candidates 1 + 2):

**Candidate 1: Collapse duplicate consumer**
Workflow::Queue and Protocol::Async::Queue shared copy-pasted retry
and execution logic. Extracted Workflow::Consumer — both queues
delegate to it. Protocol::Async::Queue is deleted; Watcher uses
Workflow::Queue#consume_one instead. One interface, two call sites.

**Candidate 2: Decouple event emission from store pipeline**
Pipeline::WriteDeps no longer carries workflow_registry. A new
Workflow::EventEmitter is wired by Builder and passed to
StoreEngine. Pipeline calls event_emitter.emit() instead of creating
Workflow::Queue directly. Store path and workflow path are decoupled.

Also: adds Consumer and EventEmitter terms to CONTEXT.md.
Candidate 3: Remove Materialize re-dispatch
Materialize no longer re-runs matched user workflows synchronously.
Event fan-out handles dispatch; Materialize only publishes entries
that have publish_to but no matching workflow. Moved from hard-coded
Builder.push to .textus/workflows/_builtin/ so all workflows load
through the same path.

Candidate 4: Deepen Runner
Extracted StepExecutor (timeout + step execution) and ContextBuilder
(key-to-Context assembly) behind Runner. Runner is now a thin
orchestrator: build context, execute steps, publish. Both
extractions are independently testable.
Entry-level publish_to/publish_tree was replaced by workflow DSL
publish-to: blocks (ADR 0052). No entry in the manifest declares
publish targets at the entry level anymore, so Materialize's
fallback publish never activates. Removed.
- Merge Schema::Vocabulary, Keys, Semantics into Schema
- Delete empty Produce and Handlers namespace modules
- Inline Retention::Plan and Key::Distance into sole callers
- Add Policy#allowed?, #allowed!, #writers_for, #actor_for
- Gate delegates to Policy instead of inlining V4::LANES checks
- Lane drops writers_for, actor_for, proposer_role
- V4::LANES stays as data source
- Policy handles proposals as special case (not a V4 lane)
- Gate selects context by verb category (read→QueryContext, write→CommandContext)
- QueryContext: read-only methods (read, resolve, list, exists?, mentry_for, link_store, entry_index, cursor_store)
- CommandContext: extends with put, delete, move + infrastructure access
- Container gains store_engine attr_reader
- Old Handlers::Context deleted
- ContextBuilder inlined into Runner, file deleted
- Builder::Infrastructure: I/O objects (Database, FileSystem, Store)
- Builder::Domain: config objects (Manifest, Schemas, Workflows)
- Builder::Runtime: wiring composition (StoreEngine, Gate, Container)
- Container gains store_engine attr_reader (used by Runtime layer)
- Top-level Builder orchestrates the three layers
- Validate: validate_input + validate_schema
- Prepare: read_existing + inject_meta + serialize
- Write: check_etag + write_bytes + build_envelope
- Emit: append_audit + index_entry + emit_workflow_event
- Each phase module groups sub-steps as private methods
- Extract StepBuilder (step/check/parallel DSL)
- Extract PatternMatcher (match_pattern? primitive)
- Extract Lifecycle (ttl/expire/max_attempts)
- Definition becomes thin facade composing the three modules
- Layout/ExtraSpacing, Style/Lambda, Style/ArgumentsForwarding
- Lint/IneffectiveAccessModifier, Layout/HashAlignment
- Style/TrailingCommaInArguments
…ose files, Registry#workflows_for_key, be_success/be_failure matchers)
…eue/RetryPolicy, inline retry, Publisher uses container.store_engine, StepHelpers extracted from Helpers
…s; docs: align knowledge store with current architecture — 4 files updated, 3 deleted, data-flow rewritten
…o separate files, add narrow DeleteDeps/MoveDeps structs
…ks, fix stale error message (match: → on:)
…ENTS.md, boot context in session_open, nodes_checked in session_close, doctor check for skipped constraint, protocol entry 0016
…fiedDoubleReference in rubocop; deduplicate config
bin/smoke exercises the INPUT → LOOP → OUTPUT protocol:
- INPUT: session open, boot, get knowledge entries, list
- LOOP: propose, write session-scoped scripts
- OUTPUT: session close, drain, audit

--clean flag wipes smoke-test artifacts.
bin/db rewritten to Ruby.
.textus/.gitignore updated for scratchpad transient dirs.
Update .rubocop.yml to tolerate pre-existing code patterns:
- Raise complexity/parameter/example-length limits to current ceiling
- Exclude specific files from new cops (OneClassPerFile, EmptyWhen, LineLength)
- Remove redundant disable directives made unnecessary by higher limits
- Fix RSpec/ReceiveMessages in handler specs
- Save session IDs to bin/.smoke-sids marker file during normal runs
- --clean reads marker + finds orphan session dirs on disk
- Cleans proposals, session keys, and filesystem dirs
- No more accumulating session dirs across runs
- All passes show only ✓ (no noisy etag hashes or UUIDs)
- Script writes show clean labels like 'write scripts/check'
- Failures show error detail inline
- Summary is compact — labels only, no detail repetition
- Fixed session-id capture (was broken after assert simplification)
- Cleanup includes orphan session dir sweep
@patrick204nqh
patrick204nqh force-pushed the arch-simplification branch from b4bb152 to e421229 Compare July 5, 2026 13:20
- Fix resolve_format to respect declared format for nested entries
  (was always returning 'markdown', ignoring the manifest declaration)
- Add append_ext_on_nested? to Format::Base (default true)
- Script format overrides to false (no extension appended for nested files)
- Text format overrides to false (no extension appended for nested files)
- Add format: text to scratchpad.sessions manifest entry
- Text nested_glob changed to **/* to match extensionless files
- Script nested_glob changed to **/* to match extensionless files

Files under sessions/<id>/ now have no extension: session, feedback,
scripts/check, scripts/task, scripts/query, scripts/report
- Add --as-format CLI flag to textus put
- Thread format: param through handler → store_engine → pipeline
- When set, strips parent entry extension and uses override format's
  extension rules (script format = no extension, markdown = .md)
- Parent entry (scratchpad.sessions) stays markdown
- Smoke test passes --as-format=script when writing session scripts
- Files now: scripts/check (bash, no ext), scripts/task (ruby, no ext),
  scripts/query (python, no ext), scripts/report (js, no ext)
- Session/feedback stay as .md
- Add format classes: Bash (.sh), Ruby (.rb), Python (.py), Javascript (.js)
- All inherit from Script (same parse/serialize behavior)
- Each returns its own extension via nested_ext
- Register in Format::STRATEGIES for --as-format=bash etc.
- Remove hardcoded EXT_MAP from Script format
- Smoke test passes --as-format=bash/ruby/python/js per script
- Create Ruby (.rb), Python (.py), Javascript (.js) format classes
  inheriting from Script (same parse/serialize behavior)
- Register all 4 language formats in Format::STRATEGIES
- Map .rb→ruby, .py→python, .sh→bash, .js→javascript in EXT_TO_FORMAT
- Smoke test uses per-language --as-format (bash/ruby/python/javascript)
- Files: check.sh, task.rb, query.py, report.js with correct content
@patrick204nqh
patrick204nqh merged commit 284af96 into main Jul 5, 2026
4 checks passed
@patrick204nqh
patrick204nqh deleted the arch-simplification branch July 5, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant