Write the pattern catalog as Role predicates #1404

Closed
opened 2026-10-26 20:14:30 +00:00 by jared · 3 comments
Owner

VERDICT: CREATE

Problem

The six patterns of the experiment exist only as a prose table. A detector cannot score a component against them until the catalog is data over ClassFacts. #1412 shipped the five ClassFacts fields (io, entry?, routes_to, holds_state?, calls) plus pure?, so the predicates can now be written.

Reproduce

ls plugins/os-sdlc/lib/os_sdlc/system_model/ | grep -i -E 'pattern|catalog'

Output: nothing.

Approach

Write the catalog as data: HEX, LAY, P&F, CMD, REPO, TXS. Each Pattern has id, roles (each Role has a name and a predicate over ClassFacts), rules (allowed edges between role names, and forbidden rules each with an id and a sentence), and source (the published reference from experiment section 1). Sources and prose role definitions: docs/system-design-format/proof/pattern-distance-experiment.md section 1. REPO's two predicates: docs/system-design-format/pattern-matching.md section 2.

The docs give predicates only for REPO. This ticket fixes the rest as follows (over a ClassFacts f; storage? means f.io.any? { |i| i.kind == :storage }, terminal? means f.io.any? { |i| i.kind == :terminal }):

  • HEX (Cockburn 2005): DrivingAdapter f.entry? && !f.calls.empty?; UseCase f.pure? && !f.entry?; Port f.pure? && f.calls.empty?; DrivenAdapter !f.pure? && !f.entry?; CompositionRoot f.entry? && f.routes_to.empty? && f.calls.size > 1.
  • LAY (POSA1 1996; Fowler PoEAA ch. 1): Presentation f.entry? || terminal?; Domain f.pure?; DataSource storage?.
  • P&F (POSA1; Hohpe & Woolf EIP): Source !f.pure? && f.calls.empty?; Filter f.pure? && !f.holds_state?; Sink !f.pure? && !f.entry?; Pipe f.pure? && f.calls.size > 1.
  • CMD (GoF 1994; Fowler PoEAA Front Controller): Router !f.routes_to.empty?; Command !f.entry? && f.routes_to.empty? && !f.calls.empty?; Receiver !f.pure? && f.calls.empty?; Client f.entry? && f.routes_to.empty?.
  • REPO (Fowler PoEAA; Evans DDD): Repository storage? && f.routes_to.empty? && !f.entry?; Entity f.holds_state? && !storage?; Mapper f.pure? && f.calls.empty?; Client !storage? && !f.calls.empty?.
  • TXS (Fowler PoEAA): Entry f.entry? && !f.calls.empty?; Script !f.entry? && !f.calls.empty? && !terminal?; Gateway !f.pure? && f.calls.empty? && !f.entry?.

Rules per pattern are the allowed-edge list and the forbidden rules R1.. from experiment section 1, carried as data (id plus sentence); evaluating a rule against a component belongs to step 5, not here.

Required work:

  1. Add Pattern, Role, and Rule values under the system-model library — check: present Rule in plugins/os-sdlc/lib/os_sdlc/system_model/pattern.rb
  2. Add the HEX pattern with its five roles, rules, and source — check: present Cockburn in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  3. Add the LAY pattern — check: present POSA1 in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  4. Add the P&F pattern — check: present Hohpe in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  5. Add the CMD pattern — check: present Front Controller in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  6. Add the REPO pattern — check: present Evans in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  7. Add the TXS pattern — check: present TXS in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb
  8. Add a Catalog that holds the six patterns and finds one by id — check: present Catalog in plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb

Acceptance criteria

  1. Given the catalog, when loaded, then it holds exactly six patterns with ids HEX, LAY, P&F, CMD, REPO, TXS, and finding by id returns the pattern.
  2. Given any pattern, when read, then it has at least one role, a non-empty allowed-edge list, at least one forbidden rule with an id and a sentence, and a non-empty source.
  3. Given a ClassFacts with entry? true and a non-empty calls, when HEX's DrivingAdapter predicate runs, then it returns true; given the same facts, when HEX's UseCase predicate runs, then it returns false.
  4. Given a ClassFacts with empty io, when LAY's Domain predicate runs, then it returns true; given a ClassFacts with :storage io, when LAY's DataSource predicate runs, then it returns true.
  5. Given a ClassFacts with empty io, holds_state? false, and one entry in calls, when P&F's Filter predicate runs, then it returns true.
  6. Given a ClassFacts with a non-empty routes_to, when CMD's Router predicate runs, then it returns true; given a ClassFacts with empty routes_to, then it returns false.
  7. Given a ClassFacts with :storage io, no routing, and entry? false, when REPO's Repository predicate runs, then it returns true; given a ClassFacts with holds_state? true and no :storage io, when REPO's Entity predicate runs, then it returns true.
  8. Given a ClassFacts with entry? false, a non-empty calls, and no :terminal io, when TXS's Script predicate runs, then it returns true; given the same facts with :terminal io, then it returns false.
  9. Given the facts.json an os-sdlc system-model run wrote for plugins/os, when each entry is rebuilt as a ClassFacts and REPO's Repository predicate runs on Handoff::Store, then it returns true.

Owner sketch

Pattern = Data.define(:id, :roles, :rules, :source)
Role = Data.define(:name, :predicate)   # predicate: a callable over ClassFacts
Rule = Data.define(:id, :text)          # forbidden rule
Catalog                                 # holds the six, finds by id

Non-goals

Scoring, Match, and Opportunity belong to build-plan step 5. Evaluating allowed and forbidden edges against a component belongs to step 5. Updating pattern-matching.md section 2 with the predicates above is done by hand after this ticket closes (the pipeline cannot check docs).

Origin

  • Trigger: pattern-distance method validated by fast-batch-17 (main 80a39130)
  • Improvised this session: none
  • Chain: no automated pattern detection ← build-plan step 4/5 not built ← DESIGN (docs/system-design-format/pattern-matching.md section 8)
  • Root candidate: none found; searched pattern catalog, Role predicate, Cockburn, ClassFacts, HEX/LAY/REPO, pattern-matching
  • Where: docs/system-design-format/pattern-matching.md sections 2 and 10; docs/system-design-format/proof/pattern-distance-experiment.md section 1
  • Session: 4e988e4b-0efd-4090-9e80-c4ccd99e73b6
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-fast-batch-15/4e988e4b-0efd-4090-9e80-c4ccd99e73b6.jsonl
  • Criteria rewritten 2026-10-27 (session 47f54f99): one item and one criterion per pattern plus the value types; predicates for the non-REPO roles fixed in the Approach because the docs gave prose only.
VERDICT: CREATE ## Problem The six patterns of the experiment exist only as a prose table. A detector cannot score a component against them until the catalog is data over `ClassFacts`. #1412 shipped the five `ClassFacts` fields (`io`, `entry?`, `routes_to`, `holds_state?`, `calls`) plus `pure?`, so the predicates can now be written. ## Reproduce ``` ls plugins/os-sdlc/lib/os_sdlc/system_model/ | grep -i -E 'pattern|catalog' ``` Output: nothing. ## Approach Write the catalog as data: HEX, LAY, P&F, CMD, REPO, TXS. Each `Pattern` has `id`, `roles` (each `Role` has a name and a predicate over `ClassFacts`), `rules` (allowed edges between role names, and forbidden rules each with an id and a sentence), and `source` (the published reference from experiment section 1). Sources and prose role definitions: `docs/system-design-format/proof/pattern-distance-experiment.md` section 1. REPO's two predicates: `docs/system-design-format/pattern-matching.md` section 2. The docs give predicates only for REPO. This ticket fixes the rest as follows (over a `ClassFacts` `f`; `storage?` means `f.io.any? { |i| i.kind == :storage }`, `terminal?` means `f.io.any? { |i| i.kind == :terminal }`): * HEX (Cockburn 2005): DrivingAdapter `f.entry? && !f.calls.empty?`; UseCase `f.pure? && !f.entry?`; Port `f.pure? && f.calls.empty?`; DrivenAdapter `!f.pure? && !f.entry?`; CompositionRoot `f.entry? && f.routes_to.empty? && f.calls.size > 1`. * LAY (POSA1 1996; Fowler PoEAA ch. 1): Presentation `f.entry? || terminal?`; Domain `f.pure?`; DataSource `storage?`. * P&F (POSA1; Hohpe & Woolf EIP): Source `!f.pure? && f.calls.empty?`; Filter `f.pure? && !f.holds_state?`; Sink `!f.pure? && !f.entry?`; Pipe `f.pure? && f.calls.size > 1`. * CMD (GoF 1994; Fowler PoEAA Front Controller): Router `!f.routes_to.empty?`; Command `!f.entry? && f.routes_to.empty? && !f.calls.empty?`; Receiver `!f.pure? && f.calls.empty?`; Client `f.entry? && f.routes_to.empty?`. * REPO (Fowler PoEAA; Evans DDD): Repository `storage? && f.routes_to.empty? && !f.entry?`; Entity `f.holds_state? && !storage?`; Mapper `f.pure? && f.calls.empty?`; Client `!storage? && !f.calls.empty?`. * TXS (Fowler PoEAA): Entry `f.entry? && !f.calls.empty?`; Script `!f.entry? && !f.calls.empty? && !terminal?`; Gateway `!f.pure? && f.calls.empty? && !f.entry?`. Rules per pattern are the allowed-edge list and the forbidden rules R1.. from experiment section 1, carried as data (id plus sentence); evaluating a rule against a component belongs to step 5, not here. Required work: 1. Add `Pattern`, `Role`, and `Rule` values under the system-model library — check: present `Rule` in `plugins/os-sdlc/lib/os_sdlc/system_model/pattern.rb` 2. Add the HEX pattern with its five roles, rules, and source — check: present `Cockburn` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 3. Add the LAY pattern — check: present `POSA1` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 4. Add the P&F pattern — check: present `Hohpe` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 5. Add the CMD pattern — check: present `Front Controller` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 6. Add the REPO pattern — check: present `Evans` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 7. Add the TXS pattern — check: present `TXS` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` 8. Add a `Catalog` that holds the six patterns and finds one by id — check: present `Catalog` in `plugins/os-sdlc/lib/os_sdlc/system_model/**/*.rb` ## Acceptance criteria 1. Given the catalog, when loaded, then it holds exactly six patterns with ids HEX, LAY, P&F, CMD, REPO, TXS, and finding by id returns the pattern. 2. Given any pattern, when read, then it has at least one role, a non-empty allowed-edge list, at least one forbidden rule with an id and a sentence, and a non-empty source. 3. Given a `ClassFacts` with `entry?` true and a non-empty `calls`, when HEX's DrivingAdapter predicate runs, then it returns true; given the same facts, when HEX's UseCase predicate runs, then it returns false. 4. Given a `ClassFacts` with empty `io`, when LAY's Domain predicate runs, then it returns true; given a `ClassFacts` with `:storage` io, when LAY's DataSource predicate runs, then it returns true. 5. Given a `ClassFacts` with empty `io`, `holds_state?` false, and one entry in `calls`, when P&F's Filter predicate runs, then it returns true. 6. Given a `ClassFacts` with a non-empty `routes_to`, when CMD's Router predicate runs, then it returns true; given a `ClassFacts` with empty `routes_to`, then it returns false. 7. Given a `ClassFacts` with `:storage` io, no routing, and `entry?` false, when REPO's Repository predicate runs, then it returns true; given a `ClassFacts` with `holds_state?` true and no `:storage` io, when REPO's Entity predicate runs, then it returns true. 8. Given a `ClassFacts` with `entry?` false, a non-empty `calls`, and no `:terminal` io, when TXS's Script predicate runs, then it returns true; given the same facts with `:terminal` io, then it returns false. 9. Given the facts.json an os-sdlc `system-model` run wrote for `plugins/os`, when each entry is rebuilt as a `ClassFacts` and REPO's Repository predicate runs on `Handoff::Store`, then it returns true. ## Owner sketch ```ruby Pattern = Data.define(:id, :roles, :rules, :source) Role = Data.define(:name, :predicate) # predicate: a callable over ClassFacts Rule = Data.define(:id, :text) # forbidden rule Catalog # holds the six, finds by id ``` ## Non-goals Scoring, `Match`, and `Opportunity` belong to build-plan step 5. Evaluating allowed and forbidden edges against a component belongs to step 5. Updating `pattern-matching.md` section 2 with the predicates above is done by hand after this ticket closes (the pipeline cannot check docs). ## Origin - Trigger: pattern-distance method validated by fast-batch-17 (main 80a39130) - Improvised this session: none - Chain: no automated pattern detection ← build-plan step 4/5 not built ← DESIGN (docs/system-design-format/pattern-matching.md section 8) - Root candidate: none found; searched pattern catalog, Role predicate, Cockburn, ClassFacts, HEX/LAY/REPO, pattern-matching - Where: docs/system-design-format/pattern-matching.md sections 2 and 10; docs/system-design-format/proof/pattern-distance-experiment.md section 1 - Session: 4e988e4b-0efd-4090-9e80-c4ccd99e73b6 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-fast-batch-15/4e988e4b-0efd-4090-9e80-c4ccd99e73b6.jsonl - Criteria rewritten 2026-10-27 (session 47f54f99): one item and one criterion per pattern plus the value types; predicates for the non-REPO roles fixed in the Approach because the docs gave prose only.
Author
Owner

Blocked by #1412 as well: #1403 shipped ClassFacts with io only (main 47c7f64e). entry?, routes_to, holds_state?, calls, and writing the facts to the output are in #1412.

Blocked by #1412 as well: #1403 shipped ClassFacts with io only (main 47c7f64e). entry?, routes_to, holds_state?, calls, and writing the facts to the output are in #1412.
Author
Owner

Work started on branch ticket-1404 via /os-sdlc:implement (session 47f54f99). Criteria rewritten first: one per pattern; non-REPO predicates fixed in the Approach.

Work started on branch ticket-1404 via /os-sdlc:implement (session 47f54f99). Criteria rewritten first: one per pattern; non-REPO predicates fixed in the Approach.
Author
Owner

Resolution

Done: Pattern catalog as data: Pattern, Role, Rule, Rules values; Catalog with HEX, LAY, P&F, CMD, REPO, TXS, each with role predicates over ClassFacts, allowed edges, forbidden rules, and source; ClassFacts gains storage? and terminal?.

Evidence: Implementation 184 complete, 8 dispatches, 0 retries, suite green; behavior-verifier passed all nine criteria; test plugins/os-sdlc/tests/system_model_catalog_test.rb.

Follow-ups: 1) port the non-REPO predicates into docs/system-design-format/pattern-matching.md section 2 by hand this session (stated in ticket Non-goals); 2) worktree guard blocked the behavior-verifier's subagent-stop in every form, to be captured as its own ticket this session.

## Resolution **Done:** Pattern catalog as data: Pattern, Role, Rule, Rules values; Catalog with HEX, LAY, P&F, CMD, REPO, TXS, each with role predicates over ClassFacts, allowed edges, forbidden rules, and source; ClassFacts gains storage? and terminal?. **Evidence:** Implementation 184 complete, 8 dispatches, 0 retries, suite green; behavior-verifier passed all nine criteria; test plugins/os-sdlc/tests/system_model_catalog_test.rb. **Follow-ups:** 1) port the non-REPO predicates into docs/system-design-format/pattern-matching.md section 2 by hand this session (stated in ticket Non-goals); 2) worktree guard blocked the behavior-verifier's subagent-stop in every form, to be captured as its own ticket this session.
jared closed this issue 2026-10-27 16:29:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1404
No description provided.