updated code format doc, working on tree walker core_developer_branch
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 5 Dec 2025 08:08:52 +0000 (08:08 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 5 Dec 2025 08:08:52 +0000 (08:08 +0000)
document/Harmony/02_RT_Code_Format.org
shared/authored/dir-walk.zip [deleted file]
shared/authored/dir-walk/doc.org [new file with mode: 0644]
shared/authored/dir-walk/type.org [new file with mode: 0644]
shared/authored/dw2/Mediator.py [new file with mode: 0644]
shared/authored/dw2/TM_EA.py [new file with mode: 0644]
shared/authored/dw2/machines.py [new file with mode: 0644]
shared/authored/dw2/property_manager.py [new file with mode: 0644]

index 78b2900..bc269f7 100644 (file)
@@ -1,6 +1,6 @@
-#+TITLE: 02 - RT Prescriptive Code Format Guide
+#+TITLE: 02 - RT Prescriptive Code Format Guide (Version 3)
 #+AUTHOR: Thomas Walker Lynch
-#+DATE: 2025-11-21
+#+DATE: 2025-12-05
 #+OPTIONS: toc:2 num:nil
 
 #+HTML_HEAD_EXTRA: <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">
@@ -18,10 +18,20 @@ languages and tools.
 This document covers:
 
 1. Naming conventions  
-2. Vertical comma lists  
-3. Enclosure spacing  
-4. Line breaks and indentation  
-5. Cross-language guidance  
+2. Object vs. Instance Nomenclature  
+3. Vertical comma lists  
+4. Enclosure spacing  
+5. Line breaks and indentation  
+6. Cross-language guidance  
+
+* Object vs. Instance Nomenclature
+
+In the RT world, we reserve the word 'object' for its general English meaning, as its technical meaning in programming often causes confusion.
+
+- **Object:** Anything that can be described or reasoned about. A 'math object' is anything defined using mathematics, and a 'Python object' is anything that can be described with Python syntax.
+- **Instance:** Data that is only accessed or manipulated through a defined interface. This term is used to clearly denote data encapsulation and separation of concerns, avoiding the ambiguity of the word 'object'.
+
+When discussing data that is manipulated solely through a defined interface, use the term **instance**.
 
 * Identifer Naming Conventions
 
@@ -76,15 +86,21 @@ Add a container type suffix instead of making variables names plural. For exampl
   
 * Comma separated list
 
-RT code format treats the comma in a list as belong to the item that caused the comma to be needed. Hence, this is an example of a horizontal comma separated list:
+RT code format treats the comma in a list as belonging to the item that caused the comma to be needed.
+
+** Horizontal Comma List
+
+For lists on a single line, the comma is preceded by a space and abuts the item it follows.
 
 #+BEGIN_SRC c
   int x ,y ,z;
 #+END_SRC
 
-Note the space before the comma, and the comma abuts the item that caused the comma to be needed. It does not matter if the last occurs in a language statement or in a data value.
+Note the space before the comma, and the comma abuts the item that caused the comma to be needed. This applies to language statements and data values alike.
+
+** Vertical Comma List
 
-For a vertical comma list, the comma remains attached to the item that caused the comma to be needed:
+For lists spanning multiple lines, the comma is placed *before* the item on the new line, aligned with the item's indentation.
 
 #+BEGIN_SRC c
 result = some_function(
@@ -107,17 +123,21 @@ items = [
 - Two-space indent.  
 - Comma at column after indentation.  
 - All items aligned except the first, as it does not have a comma before it.
-- Works identically across C, Python, Bash arrays, and JSON-like data.
+- This convention works identically across C, Python, Bash arrays, and JSON-like data.
 
-* 3. Enclosure Spacing
+* Enclosure Spacing
 
 This rule applies on a line by line basis.
 
-** For one level enclosures:
+** General Rules
 
-- No space before =)=, =]=, =}=.  
-- No space before or after enclosure pairs.  
-- One-space exception: short single-line enclosures for readability.
+1.  **No Space Before Closing Punctuation:** No space before =)=, =]=, or =}=.
+2.  **No Space Before Opening Brace:** Do not include a space before the opening curly brace =\{= for structures or code blocks.
+3.  **No Space Between Adjacent Enclosures:** Generally, there is no space between adjacent enclosure punctuation (e.g., `f(g(x))`).
+
+** Single-Level Enclosures
+
+For enclosures that do not contain other enclosures (e.g., a simple `if(condition)`), there is **no space padding** around the enclosure punctuation.
 
 Conforming:
 
@@ -143,9 +163,9 @@ if ( condition ) {
 }
 #+END_SRC
 
-** For multi-level enclosures:
+** Multi-Level Enclosures
 
-One space around the level one (outermost) enclosure punctuation. Other levels are as for the one level enclosure.
+For enclosures that contain other enclosures (e.g., `if( f(g(x)) )`), one space of padding is applied only to the **level one (outermost)** enclosure punctuation. All other levels follow the single-level rule (no padding).
 
 #+BEGIN_SRC c
 if( f(g(x)) ){
@@ -153,21 +173,57 @@ if( f(g(x)) ){
 }
 #+END_SRC
 
-In this example the =if= has a three level enclosure structure. (When measuring enclosure levels identifiers are ignored.) Hence level one gets one space of padding, while all other levels get no padding.
+In this example, the =if= has a three-level enclosure structure. The outermost parentheses of the =if= condition get one space of padding, while the inner parentheses for =f(...)= and =g(...)= get no padding.
 
-** Unmatched enclosure punctuation.
+** Unmatched Enclosure Punctuation
 
-Format the enclosure punctuation that is present, as though it were matched.
+Format the enclosure punctuation that is present, as though it were matched. Treat an orphaned opening enclosure punctuation as though it were closed at the end of the line. Treat an extraneous closing, as though there were an opening at the beginning of the line.
 
+** Short Stuff Rule
+
+If a statement, such as an =if= block or a loop, can fit on a single line and is shorter than a reasonable line length (e.g., 40-60 characters), it should be kept on a single line without braces.
+
+#+BEGIN_SRC c
+if(x == 0)  return;
+#+END_SRC
 
 * Indentation
 
 Harmony mandates:
 
 - Two spaces per indentation level.  
-- Never use tabs.  
+- Never use tabs (except in Makefiles, i.e. where they are mandated.)
 - Nest lines under the syntactic element that opened them.
 
+* Exercises
+
+To ensure a full understanding of the RT code format, please complete the following exercises.
+
+** Exercise 1: Comma and Function Call Formatting
+
+Reformat the following C code snippet to strictly adhere to the RT code format rules. Pay close attention to the horizontal and vertical comma lists, and the enclosure spacing for the function call.
+
+#+BEGIN_SRC c
+void my_function(int a, int b, int c) {
+  int result = calculate_value(a, b, c);
+  printf("Result: %d, a: %d, b: %d, c: %d\n", result, a, b, c);
+}
+#+END_SRC
+
+** Exercise 2: Multi-Level Enclosure and Short Stuff Rule
+
+Reformat the following C code snippet. The `if` statement should use the multi-level enclosure rule, and the `for` loop body should use the short stuff rule.
+
+#+BEGIN_SRC c
+if (check_permissions(user_id, file_path) && is_valid(file_path)) {
+  for (int i = 0; i < 10; i++) {
+    if (i % 2 == 0) {
+      printf("Even: %d\n", i);
+    }
+  }
+}
+#+END_SRC
+
 #+BEGIN_EXPORT html
 </div>
 #+END_EXPORT
diff --git a/shared/authored/dir-walk.zip b/shared/authored/dir-walk.zip
deleted file mode 100644 (file)
index e46a154..0000000
Binary files a/shared/authored/dir-walk.zip and /dev/null differ
diff --git a/shared/authored/dir-walk/doc.org b/shared/authored/dir-walk/doc.org
new file mode 100644 (file)
index 0000000..dc52425
--- /dev/null
@@ -0,0 +1,218 @@
+#+TITLE: Tape, Tape Machine, and Mediator Specification
+#+AUTHOR: Thomas Walker Lynch & Caelivar
+#+OPTIONS: toc:3 num:3
+
+* Overview
+This document specifies the unified design for the Tape abstraction,
+the Tape Machine (TM), and the Mediator that performs relational
+operations on one or more tape machines.
+
+A central conceptual breakthrough is the separation of:
+
+1. Tape topology (cells, adjacency, shifts, insertions, deletions)
+2. Logical access points (heads) with differing semantics
+3. Mediated operations transferring or relocating *content*, not *cells*
+4. Shannon factorization during =__init__= to assign a performance-optimal
+   wrapper around the tape object
+
+This conforms to the principles in *TTCA*, but generalizes the model by
+introducing explicit *head-modes* and a fully externalized *Mediator*.
+
+* Core Abstractions
+
+** Tape
+A *tape* is a topology of cells. It does not know anything about
+"head" or "read/write" semantics.
+
+A tape minimally provides:
+
+- =extent()= :: Returns the maximum valid integer offset.
+- =get(i)= :: Returns the content at cell index =i=.
+- =set(i, value)= :: Writes to cell index =i=.
+- =clone()= :: Returns a deep or shallow copy of the tape, preserving content.
+- =insert(i)= :: Insert a new cell before index =i=.
+- =delete(i)= :: Delete the cell at index =i=.
+
+get(i) and set(i) are needed for A style machines, where the tape has an address space.
+For reference style tapes, the head is a reference to a cell, and a dereferencing operations
+is used to get and set values.
+
+A tape might have a head and tail end, for example a linked list. For a tape with locally
+allocated cells within an outer address space, such as for an array, one end is the reference end.
+
+When a tape is mounted on a tape machine, the tape machine will endow one end cell of the tape with the moniker of 'leftmost' and the end cell other as 'rightmost'.  The same tape on different machines could be end to end swapped with mounted.  Hence, 'left' and 'right' are properties endowed upon tapes by tape machines.
+
+
+*** Tape Wrappers via Shannon Factorization
+During TM construction, the input object is examined for a minimal set
+of primitive methods (A).
+
+Example primitive set A:
+- =__len__=
+- =__getitem__=
+- =__setitem__=
+- =append=
+- =extend=
+
+Another example primitive set A:
+- =head=
+
+Given these, we can construct the required complex set B:
+- =extent=
+- =insert=
+- =delete=
+- =clone=
+- =shift=
+- etc.
+
+Wrapper selection is performed in descending order of performance:
+
+1. If the object already implements all methods in B, no wrapper needed.
+2. If the object lacks B but has strong primitives (list-like), install
+   a "vector wrapper".
+3. If the object is minimal (getitem/setitem only), install a "baseline
+   wrapper" with indirection.
+4. As a last resort, we construct a uniform wrapper implementing *all*
+   methods through slow paths.
+
+This factorization pushes complex logic into init time, not run time.
+
+** Tape Machine
+A Tape Machine is:
+
+1. A reference to a Tape (=self.tape=)
+2. A head position (=self.hpos=)
+3. A head mode:
+
+   - =L= (location-bound): Head stays fixed to a cell index
+   - =C= (content-bound): Head moves with the content during topology changes
+
+A TM does *not* directly manipulate content. There is no =read= or =write=
+operation on the TM itself. Instead, all content transfer is mediated by
+the Mediator.
+
+*** TM Responsibilities
+- Represent a position in the tape
+- Allow =cue(i)= to reposition the head
+- Provide =can_step(n)= and =step(n)= for *head movement only*
+- Provide =clone()= for creating an independent machine with its own tape clone
+- Provide =entangle()= only for ND (non-destructive) machines, copying the
+  head but *not* the tape.
+
+*** Head Behavior in Topology Changes
+When the Mediator invokes a topology operation:
+
+- =L-head= (location-bound):
+  Stays at the same numeric index, unless its target cell is deleted.
+
+- =C-head= (content-bound):
+  Moves with the content it references, shifting its index when content shifts.
+
+This distinction unifies all previous confusion between "content ops"
+and "topology ops" — the difference is now purely in head semantics.
+
+* Mediator
+
+** Purpose
+The Mediator performs *relational operations* between one or more tape
+machines. The TM itself *never* performs cross-machine operations.
+
+All operations that move content, copy content, insert/delete cells,
+or correlate multiple tapes go here.
+
+*** Examples of Mediator Functions
+- =copy(src_tm, dst_tm, count)=  
+  Copy =count= cells starting at =src_tm.hpos= → =dst_tm.hpos=
+
+- =copy_insert(src_tm, dst_tm, count)=  
+  Same as =copy= but inserts new space in destination tape.
+
+- =shift(tm, n, policy)=  
+  Shift content left or right by =n= cells according to policies:
+
+  - spill to another TM  
+  - collapse  
+  - extend  
+  - fill with constant  
+  - fill from TM  
+
+Policy functions determine how end effects are handled:
+=policy.spill_left=, =policy.spill_right=,
+=policy.fill_left=, =policy.fill_right=.
+
+*** Multi-head Coordination
+Mediator methods always consult:
+
+1. Whether a TM is ND vs Solo  
+2. Whether each head is L or C mode  
+3. Whether content motion corresponds to head-index motion
+
+Thus the Mediator owns the rules for:
+
+- How heads shift when content shifts
+- What happens if a head’s content is deleted
+- Whether topology change is permitted
+
+*** Entanglement and Mediator Safety
+If a TM is ND and entangled, policy enforcement ensures:
+
+- No topology mutation occurs  
+- Heads remain valid because content is never deleted  
+- Mediator can operate safely
+
+* Region Semantics
+This spec adopts TTCA’s principle that a *region* is a range of
+values, and that copying/moving/swapping regions requires:
+
+- Two TMs for source and destination
+- Mediator-level coordination
+- Optional scratch machines for temporary storage
+
+Regions become a Mediator construct, not a TM construct.
+
+* Architecture Summary
+| Layer      | Responsibility                                       |
+|------------+-------------------------------------------------------|
+| Tape       | Cell topology only                                    |
+| TapeMachine| Pointing mechanism (head + mode)                      |
+| Mediator   | All data movement, shifting, copying, insertion, etc. |
+
+This three-layer design is orthogonal, minimal, and complete.
+
+* Appendix: Shannon Factorization for Wrappers
+The core principle:
+
+#+BEGIN_QUOTE
+If an input type provides primitive method set A, the system can construct
+a wrapper providing method set B. Wrapper selection should attempt the
+highest-performance mapping first, then fall back to slower but more
+general wrappers.
+#+END_QUOTE
+
+** Wrapper Selection Steps
+1. *Check if tape already supports full interface B.*  
+   If yes → zero-cost direct use.
+
+2. *Check for list-like primitives (len, getitem, setitem, append, extend).*  
+   Install a vector wrapper that maps:
+
+   - =extent= → =len(tape)-1=
+   - =insert= → =tape.insert=
+   - =delete= → =tape.pop=
+   - =clone=  → shallow copy via slicing
+
+3. *Check for minimal sequence-like behavior.*  
+   Install a "minimal wrapper" implementing:
+
+   - =insert/delete= via element shifting  
+   - =extend= via repeated append  
+   - =clone= via python =copy= or listification
+
+4. *Fallback wrapper:*  
+   Provide universal implementations of B using only getitem/setitem.
+
+** Purpose
+This pushes decision-making into initialization rather than runtime,
+greatly reducing overhead for loops, copying, and traversal.
+
+* End of Document
diff --git a/shared/authored/dir-walk/type.org b/shared/authored/dir-walk/type.org
new file mode 100644 (file)
index 0000000..cd53aab
--- /dev/null
@@ -0,0 +1,455 @@
+#+TITLE: RT Dynamic Type System — Capability Sets, Routine Equivalence, and Factories
+#+AUTHOR: Thomas Walker Lynch & Caelivar
+#+OPTIONS: toc:2 num:3
+
+* Overview
+RT does not treat “type” as mere structure (“has methods named X, Y, Z”).
+Instead, a type is:
+
+1. A *capability set* intended by the human programmer
+2. Realized as a collection of *methods* on an instance
+3. Whose methods belong to specific *routine equivalence classes*
+   (e.g. =Math.add= vs =Animal.add=), not just name collisions
+
+Concretely:
+
+- A type is defined by *intended capability* (“this supports Tape_EA semantics”),
+  expressed as:
+  - required method names, *and*
+  - membership of those methods in specific routine-equivalence classes.
+- Python type annotations describe *shape* and *intent for humans*,
+  but are structurally shallow.
+- Runtime safety is enforced via explicit =is_a_*= capability predicates,
+  which check *both* presence and equivalence-class membership.
+- Construction is done via =make_into_*= factories, which may wrap or rebuild
+  an object to satisfy a capability set.
+
+* Capability Sets
+A capability set is a *named* collection of required conceptual behaviors.
+
+** Tape Capability Sets
+
+| Capability | Meaning                                                   |
+|------------+-----------------------------------------------------------|
+| =Tape_A=   | Has =extent()=, =get(i)=, =set(i, v)= in the Tape algebra |
+| =Tape_SE=  | Tape_A + extend_right()/collapse_right() (single-ended)   |
+| =Tape_DE=  | Tape_SE + extend_left()/collapse_left() (double-ended)    |
+| =Tape_EA=  | Tape_DE + clone()/entangle bookkeeping for EA semantics   |
+
+** Tape Machine Capability Sets
+
+| Capability  | Meaning                                                  |
+|-------------+----------------------------------------------------------|
+| =TM_A=      | Addressable head, cue, step(n); half-step head model     |
+| =TM_ND=     | Non-destructive; entangle() allowed; no topology change  |
+| =TM_1H=     | Solo destructive; collapse/extend safe; no entanglement  |
+| =TM_EA=     | Entanglement accounting + destructive topology ops       |
+| =TM_Region= | Built on EA; extract/overlay/insert for sub-tapes        |
+
+Again: these are *semantic contracts*, not class hierarchies.
+
+* Routine Equivalence Classes
+
+** Problem: Same name, different algebra
+
+Example:
+
+- Class C has =add(self, animal)=, “put an animal into the menagerie.”
+- Class D has =accumulate(self, acc)=, and checks =hasattr(acc, "add")=.
+- At runtime someone passes a set-of-animals instance as =acc=.
+- Structurally it has =add=, but semantically it is the *wrong add*;
+  the routine is from the Animal algebra, not the Math algebra.
+
+Plain duck-typing fails here.
+
+** Solution: Routine Equivalence Classes
+
+We introduce *routine equivalence classes*:
+
+- =Math.add= :: “binary operation obeying the Math-add algebra”
+- =Animal.add= :: “insert an animal into this collection”
+- =Tape.extent= :: “highest valid cell index on this tape”
+- etc.
+
+Every method that participates in our algebra is *tagged* as belonging
+to one or more routine equivalence classes.
+
+Then:
+
+- =is_a_Accumulator(x)= does *not* just check for =hasattr(x, "add")=,
+  it checks that:
+  - =x.add= belongs to the =Math.add= routine class,
+  - not merely that a method named “add” exists.
+
+Implementation-wise, RT maintains a registry mapping:
+
+- routine-equivalence-class → methods
+
+Rather than trusting names alone.
+
+* Duck Typing: is_a_* with Routine Classes
+
+** Capability predicates
+
+Each capability set gets an =is_a_*()= predicate that checks:
+
+1. *Structure* :: required method names exist.
+2. *Routine class* :: each method is tagged as belonging to
+   the correct routine equivalence class.
+
+Example outline:
+
+#+BEGIN_SRC python
+REQ_TAPE_A_METHODS = {
+  "extent": "Tape.extent",
+  "get":    "Tape.get",
+  "set":    "Tape.set",
+}
+
+def is_a_Tape_A(x) -> bool:
+  for name, rclass in REQ_TAPE_A_METHODS.items():
+    m = getattr(x, name, None)
+    if m is None or not callable(m):
+      return False
+    if not method_in_routine_class(m, rclass):
+      return False
+  return True
+#+END_SRC
+
+Here, =method_in_routine_class(m, "Tape.extent")= consults a global
+routine-class registry (described in the implementation section).
+
+These predicates are used:
+
+- in debug builds for assertion checks
+- at module boundaries where we want hard guarantees
+- in factories (=make_into_*()=) to decide if wrapping is needed
+
+* Subtype Factories: make_into_*
+
+Factories are how we *concretely* obtain objects that satisfy a capability set.
+
+Form:
+
+- =make_into_Tape_A(x)=
+- =make_into_Tape_EA(x)=
+- =make_into_TM_1H(x)=
+- =make_into_TM_EA(x)=
+
+Return:
+
+#+BEGIN_SRC python
+tag, fixed = make_into_Tape_A(x)
+# tag ∈ {"Fail", "O(1)", "O(n)", ...}
+#+END_SRC
+
+Rules:
+
+- Never mutate the original =x=.
+- Either:
+  - return it as-is if it satisfies the capability (tag "O(1)"), or
+  - wrap it with a suitable wrapper that provides the required methods
+    *and* proper routine-class tags (tag e.g. "O(n)"), or
+  - return ("Fail", x) if impossible.
+
+Factories are also where **Shannon factorization** lives:
+
+- Given primitive method set A (e.g. a =list= with =__len__, __getitem__,
+  __setitem__, append, extend=),
+- choose a wrapper that yields method set B (Tape_A, Tape_SE, etc.)
+  with minimal runtime overhead.
+
+* Tape Wrappers via Shannon Factorization
+
+We define canonical wrapper strategies:
+
+1. **No wrapper**  
+   If =is_a_Tape_EA(x)= is already true → use as-is.
+
+2. **Vector wrapper** (for list-like types)  
+   If =hasattr(x, "__len__")=, =__getitem__=, =__setitem__=, =append=, =extend=:
+   - Provide:
+     - =extent()= → =len(x) - 1=
+     - =get(i), set(i, v)= → indexing
+     - =extend_* / collapse_*= → efficient list operations
+     - =clone()= → shallow copy
+   - Tag methods with appropriate routine equivalence classes.
+
+3. **Minimal wrapper**  
+   If only =__getitem__/__setitem__= exist:
+   - Implement insert/delete/extend/collapse via element shifting.
+   - Slower, but still correct.
+
+4. **Fallback wrapper**  
+   Last resort – uses only minimal primitives, may be O(n²) in bad cases.
+   This is acceptable for rare/debug scenarios.
+
+Factories select the highest-performance feasible wrapper
+and record the performance tag.
+
+* Tape Machine (TM) Semantics
+
+A TM_EA (for example) is:
+
+1. A handle to an EA-capable Tape
+2. One or more heads, each sitting on *links* between cells
+   (half-step conceptual model)
+3. Entanglement accounting metadata
+
+Key properties:
+
+- Heads conceptually live at “links”, not on cells.
+- A read-value comes from the cell indicated by the head.
+- Deleting cells adjusts links; heads remain on links unless special
+  rightmost/leftmost rules apply.
+- For EA machines, destructive operations update all entangled heads
+  according to the half-step model.
+
+Methods we standardize (interface level):
+
+- =cue_leftmost()=, =cue_rightmost()=
+- =cue(i)= (for addressable tapes)
+- =can_step(n)=, =step(n)=
+- =on_leftmost(extent: int = 0) -> bool=
+- =on_rightmost(extent: int = 0) -> bool=
+- =entangle()= (for ND / EA as appropriate)
+- =clone()= (tape clone + TM clone)
+
+“Read” and “write” at the content level are not TM methods;
+they are mediated via the Mediator.
+
+* Region Semantics and TM_EA.Region
+
+A Region is a TM view over a sub-tape:
+
+- Extracted regions :: copies of cells, removed from base tape; no entanglement.
+- Overlay regions :: entangled submachines with left/right bounds.
+- Insert regions :: allocate new cells in base tape, entangled with region.
+
+For TM_EA:
+
+- Extract may *not* empty the base TM (fundamental TM cannot become empty).
+- Regions are inclusive bounds; addresses remain complete.
+- The head’s half-step model ensures that, after extract/insert,
+  base heads and region heads remain well-defined.
+
+Region operations belong in the =TM_EA.Region= namespace, with methods like:
+
+- =extract(tm: TM_EA, extent: int) -> TM_EA=      (new TM over extracted tape)
+- =overlay(tm: TM_EA, extent: int) -> TM_EA=      (entangled region)
+- =insert(tm: TM_EA, extent: int) -> TM_EA=       (new cells on base tape)
+
+Extend/collapse-at-ends are expressed as region operations anchored at tape ends.
+
+* Mediator
+
+The Mediator is the *relational* layer between tape machines.
+
+It knows nothing about Python lists or strings; it only knows:
+
+- “Here is a TM whose head indicates some cell.”
+- “Here are other TMs.”
+- “Apply algebraic operations to their content and positions.”
+
+Core primitives:
+
+- =copy_cell(src_tm, *dst_tms)=  
+  - REQUIRES: all satisfy TM_A; all heads indicate valid cells.  
+  - Semantics: copy indicated content from src to each dst.
+
+- =copy_machine(src_tm, *dst_tms)=  
+  - REQUIRES: user has already created compatible regions on each TM.  
+  - Implementation: loop calling =copy_cell= and =step_all=.
+
+- =step_all(*tms, n: int = 1)=  
+  - REQUIRES: each TM_A.  
+  - Implementation: first check aggregate =can_step=; then step all.
+
+Assignments:
+
+- =assign_py_to_tm(value, tm)=  
+  - REQUIRES: TM_A, plus tape implementing a write-capable routine class.
+  - Uses an internal =Tape.write_cell(tm, value)= operation.
+
+- =assign_tm_to_py(tm)=  
+  - REQUIRES: TM_A.
+  - Returns the Python object from the indicated cell.
+
+Higher-level operations (rotate, shift across multiple tapes, etc.)
+are built on top of these primitives and Region construction.
+
+* Python Protocols vs RT’s System (Summary)
+
+- Protocols let us say: “I need something with methods named X, Y, Z.”
+- RT’s system says: “I need something that participates in the algebra:
+  X ∈ =Math.add= class, Y ∈ =Tape.extent= class, etc., with head and
+  entanglement semantics obeying TTCA rules.”
+
+Protocols are useful for:
+
+- Public API shape, editor help, static checks.
+
+RT’s dynamic type system is the source of truth for:
+
+- Routine equivalence
+- Entanglement
+- Region invariants
+- Tape topology contracts
+
+* Implementation Sketch
+
+** 1. Routine Class Registry
+
+We centralize concept-labels for routines:
+
+#+BEGIN_SRC python
+from dataclasses import dataclass
+from typing import Dict, Set, Callable, Tuple
+
+@dataclass(frozen=True)
+class RoutineClass:
+  name: str  # e.g. "Math.add", "Tape.extent"
+
+ROUTINE_CLASSES: Dict[str, RoutineClass] = {}
+METHOD_CLASSES: Dict[Tuple[type, str], Set[RoutineClass]] = {}
+#+END_SRC
+
+Helpers:
+
+#+BEGIN_SRC python
+def get_rclass(name: str) -> RoutineClass:
+  if name not in ROUTINE_CLASSES:
+    ROUTINE_CLASSES[name] = RoutineClass(name)
+  return ROUTINE_CLASSES[name]
+
+def register_method_class(cls: type, method_name: str, rclass_name: str) -> None:
+  rc = get_rclass(rclass_name)
+  key = (cls, method_name)
+  METHOD_CLASSES.setdefault(key, set()).add(rc)
+
+def method_in_routine_class(method: Callable, rclass_name: str) -> bool:
+  rc = ROUTINE_CLASSES.get(rclass_name)
+  if rc is None:
+    return False
+  # Look up by (owner_class, method_name)
+  owner = getattr(method, "__self__", None)
+  func  = getattr(method, "__func__", None)
+  if owner is None or func is None:
+    return False
+  cls = type(owner)
+  key = (cls, func.__name__)
+  return rc in METHOD_CLASSES.get(key, set())
+#+END_SRC
+
+We can also provide decorators:
+
+#+BEGIN_SRC python
+def routine_class(rclass_name: str):
+  def deco(fn):
+    # fn is a function defined inside a class; we’ll register it
+    fn.__rt_rclass__ = rclass_name
+    return fn
+  return deco
+#+END_SRC
+
+And then in class definitions, an initialization helper walks the class dict
+and calls =register_method_class= for any functions carrying =__rt_rclass__=.
+
+This avoids attaching per-instance state; we keep one global registry mapping
+(=cls, method_name)= → {routine classes}.
+
+** 2. is_a_* Predicates
+
+Each capability set defines:
+
+#+BEGIN_SRC python
+REQ_TAPE_A_METHODS = {
+  "extent": "Tape.extent",
+  "get":    "Tape.get",
+  "set":    "Tape.set",
+}
+
+def is_a_Tape_A(x) -> bool:
+  for name, rclass in REQ_TAPE_A_METHODS.items():
+    m = getattr(x, name, None)
+    if m is None or not callable(m):
+      return False
+    if not method_in_routine_class(m, rclass):
+      return False
+  return True
+#+END_SRC
+
+We can optionally allow a “structural-only” fallback in debug modes
+to help transition code; but conceptually, equivalence-class membership
+is what matters.
+
+** 3. make_into_* Factories
+
+Pattern:
+
+#+BEGIN_SRC python
+def make_into_Tape_A(x):
+  # 1) Already good?
+  if is_a_Tape_A(x):
+    return "O(1)", x
+
+  # 2) Recognized primitive — list-like
+  if hasattr(x, "__len__") and hasattr(x, "__getitem__") and hasattr(x, "__setitem__"):
+    wrapped = ListTapeWrapper(x)   # implements extent/get/set/...
+    return "O(n)", wrapped
+
+  # 3) Fail
+  return "Fail", x
+#+END_SRC
+
+=ListTapeWrapper= is defined in =TM_EA.py= (or a tape module) and in its
+class body we tag methods with the right routine classes, e.g.:
+
+#+BEGIN_SRC python
+class ListTapeWrapper:
+  def __init__(self, data):
+    self._data = data
+
+  @routine_class("Tape.extent")
+  def extent(self) -> int: ...
+
+  @routine_class("Tape.get")
+  def get(self, i: int): ...
+
+  @routine_class("Tape.set")
+  def set(self, i: int, v): ...
+#+END_SRC
+
+A post-class-initialization step runs through =ListTapeWrapper.__dict__=
+and registers method classes in =METHOD_CLASSES=.
+
+** 4. TM_EA, Region, Mediator Modules
+
+- =TM_EA.py=  
+  - Defines the TM core, using a wrapped tape with known capabilities.
+  - Uses =make_into_Tape_EA= in its constructor.
+  - Provides =entangle()=, =clone()=, =on_leftmost()=, =on_rightmost()=, etc.
+  - Nested / namespaced =Region= helper for extract/overlay/insert/end-extend/collapse.
+
+- =Mediator.py=  
+  - Uses only =is_a_TM_A=, =is_a_Tape_A= etc.
+  - Implements =copy_cell=, =copy_machine=, =step_all=, =assign_py_to_tm=, =assign_tm_to_py=.
+  - Higher-level =rotate=, =shift=, etc., built on regions and these primitives.
+
+- =machines.py=  
+  - Uses TM_EA/1H/ND factories to build useful concrete machines:
+    - Constant-fill tapes
+    - Reverse-machine wrapper (left/right swapped)
+    - Simple iterators for test, etc.
+
+* Summary
+- For RT, *type* = (capability set) + (routine equivalence classes of its methods),
+  not just method names.
+- Python Protocols and annotations help document shape but are not authoritative.
+- The real authority is:
+  - routine-class registry
+  - =is_a_*= capability predicates
+  - Shannon-factorized =make_into_*= factories
+- This framework lets us implement TM_EA, regions, entanglement accounting,
+  and Mediator semantics in a way that aligns with TTCA, while still sitting
+  comfortably inside Python.
diff --git a/shared/authored/dw2/Mediator.py b/shared/authored/dw2/Mediator.py
new file mode 100644 (file)
index 0000000..2e793e6
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Mediator.py
+
+Implements:
+  - Mediator
+    copy_cell
+    copy_machine
+    step_all
+    assign_py_to_tm
+    assign_tm_to_py
+    rotate
+    shift
+"""
+
+from typing import Any, List
+from property_manager import property_manager, require_concept
+from TM_EA import TM, TM_EA
+
+
+class Mediator:
+
+    @staticmethod
+    def copy_cell(src:TM, dst:TM):
+        require_concept(src, "TM_A")
+        require_concept(dst, "TM_A")
+        v = src.write_cell_value()
+        dst.tape.set(dst.hpos, v)
+
+    @staticmethod
+    def copy_machine(src:TM, dsts:List[TM]):
+        for dst in dsts:
+            require_concept(dst, "TM_A")
+        require_concept(src, "TM_A")
+
+        # naive: copy entire region implied by src/dst extents
+        max_len = min(
+            src.tape.extent(),
+            min(dst.tape.extent() for dst in dsts)
+        )
+        for offset in range(max_len+1):
+            src.cue(offset)
+            for dst in dsts:
+                dst.cue(offset)
+                Mediator.copy_cell(src, dst)
+
+    @staticmethod
+    def step_all(tms:List[TM], n:int):
+        for tm in tms:
+            tm.step(n)
+
+    @staticmethod
+    def assign_py_to_tm(value:Any, tm:TM):
+        require_concept(tm, "TM_A")
+        tm.tape.set(tm.hpos, value)
+
+    @staticmethod
+    def assign_tm_to_py(tm:TM):
+        require_concept(tm, "TM_A")
+        return tm.write_cell_value()
+
+    # rotate: each tm's tape shifts into the next
+    @staticmethod
+    def rotate(tms:List[TM]):
+        if not tms:
+            return
+        # For simplicity, rotate cell 0 across all tapes
+        vals = [tm.tape.get(0) for tm in tms]
+        shifted = vals[-1:] + vals[:-1]
+        for tm, v in zip(tms, shifted):
+            tm.tape.set(0, v)
+
+    # shift: fill with None, drop values falling off
+    @staticmethod
+    def shift(tms:List[TM], n:int):
+        # only shift the first machine, others ignore
+        if not tms:
+            return
+        tm = tms[0]
+        ext = tm.tape.extent()
+        new_lst = [None]*(ext+1)
+        for i in range(ext+1):
+            j = i - n
+            if 0 <= j <= ext:
+                new_lst[i] = tm.tape.get(j)
+        tm.tape = type(tm.tape)(new_lst)
diff --git a/shared/authored/dw2/TM_EA.py b/shared/authored/dw2/TM_EA.py
new file mode 100644 (file)
index 0000000..e9de452
--- /dev/null
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+TM_EA.py
+
+Implements:
+  - EA Tape wrappers
+  - TM (base)
+  - TM_EA
+  - Region (returned by region operations)
+"""
+
+from typing import Any, List, Optional
+from property_manager import property_manager, object_path, attach_properties
+
+
+# ============================================================
+# Tape wrappers
+# ============================================================
+
+class BaseTapeWrapper:
+    """
+    Parent of all tape wrappers.
+    Required interface we standardize:
+      - extent()
+      - get(i)
+      - set(i,val)
+      - insert(i)
+      - delete(i)
+      - clone()
+      - extend_right(n, fill_tm)
+      - collapse_right(n) -> tm (region)
+      - extend_left(n, fill_tm)
+      - collapse_left(n) -> tm (region)
+    """
+
+    def extent(self) -> int:
+        raise NotImplementedError
+
+    def get(self, i:int) -> Any:
+        raise NotImplementedError
+
+    def set(self, i:int, v:Any) -> None:
+        raise NotImplementedError
+
+    def insert(self, i:int) -> None:
+        raise NotImplementedError
+
+    def delete(self, i:int) -> None:
+        raise NotImplementedError
+
+    def clone(self):
+        raise NotImplementedError
+
+    def extend_right(self, n:int, fill_tm):
+        raise NotImplementedError
+
+    def collapse_right(self, n:int):
+        raise NotImplementedError
+
+    def extend_left(self, n:int, fill_tm):
+        raise NotImplementedError
+
+    def collapse_left(self, n:int):
+        raise NotImplementedError
+
+
+class ListTapeWrapper(BaseTapeWrapper):
+    """
+    Wraps a Python list.
+    """
+
+    def __init__(self, lst:List[Any]):
+        self.lst = list(lst)
+
+    def extent(self) -> int:
+        return len(self.lst)-1
+
+    def get(self, i:int) -> Any:
+        return self.lst[i]
+
+    def set(self, i:int, v:Any) -> None:
+        self.lst[i] = v
+
+    def insert(self, i:int) -> None:
+        self.lst.insert(i, None)
+
+    def delete(self, i:int) -> None:
+        del self.lst[i]
+
+    def clone(self):
+        return ListTapeWrapper(self.lst[:])
+
+    def extend_right(self, n:int, fill_tm):
+        for _ in range(n):
+            v = None
+            if fill_tm is not None:
+                v = fill_tm.write_cell_value()
+            self.lst.append(v)
+
+    def collapse_right(self, n:int):
+        removed = []
+        for _ in range(min(n, len(self.lst))):
+            removed.append(self.lst.pop())
+        return removed
+
+    def extend_left(self, n:int, fill_tm):
+        for _ in range(n):
+            v = None
+            if fill_tm is not None:
+                v = fill_tm.write_cell_value()
+            self.lst.insert(0, v)
+
+    def collapse_left(self, n:int):
+        removed = []
+        for _ in range(min(n, len(self.lst))):
+            removed.append(self.lst.pop(0))
+        return removed
+
+
+# ============================================================
+# Region
+# ============================================================
+
+class Region:
+    """
+    Represents a region produced by:
+      - insert
+      - overlay
+      - extract
+
+    Holds:
+      tape   : BaseTapeWrapper
+      tag    : {"insert","overlay","extract"}
+      parent_tm : TM_EA that produced it
+    """
+    def __init__(self, tape:BaseTapeWrapper, tag:str, parent_tm):
+        self.tape = tape
+        self.tag = tag
+        self.parent_tm = parent_tm
+
+
+# ============================================================
+# TM base
+# ============================================================
+
+class TM:
+    """
+    Base tape machine:
+      - has a tape wrapper
+      - has a head offset indicating which cell is 'indicated'
+      - no read/write: mediated externally
+    """
+
+    debug = False
+
+    def __init__(self, tape:BaseTapeWrapper):
+        self.tape = tape
+        self.hpos = 0   # head indicates cell 0 by default
+
+    @staticmethod
+    def setup(property_manager):
+        """
+        Called during program initialization.
+        Can register properties for TM if desired.
+        """
+        pass
+
+    def on_leftmost(self, extent:int=0) -> bool:
+        return (0 <= self.hpos <= extent)
+
+    def on_rightmost(self, extent:int=0) -> bool:
+        ext = self.tape.extent()
+        return (ext - extent) <= self.hpos <= ext
+
+    def cue(self, i:int):
+        self.hpos = i
+        if self.hpos < 0:
+            self.hpos = 0
+        if self.hpos > self.tape.extent():
+            self.hpos = self.tape.extent()
+
+    def step(self, n:int):
+        self.cue(self.hpos + n)
+
+    def clone(self):
+        return TM(self.tape.clone())
+
+    def write_cell_value(self):
+        """
+        Return the cell content indicated by this machine.
+        Used for fill operations.
+        """
+        i = self.hpos
+        if i < 0: i = 0
+        if i > self.tape.extent(): i = self.tape.extent()
+        return self.tape.get(i)
+
+
+# ============================================================
+# TM_EA — entanglement accounting + destructive ops
+# ============================================================
+
+class TM_EA(TM):
+
+    def __init__(self, tape:BaseTapeWrapper):
+        super().__init__(tape)
+        self.entangled_heads = []   # not fully implemented: placeholder
+
+    @staticmethod
+    def setup(property_manager):
+        # Register TM_EA as a semantic concept
+        property_manager.register_concept(
+            "TM_A",
+            doc="Address-space tape machine",
+            structure_fn=None
+        )
+        property_manager.register_concept(
+            "TM_EA",
+            doc="Entanglement accounting machine",
+            structure_fn=None
+        )
+        # mark class-level membership
+        property_manager.add_membership(TM_EA, "TM_A")
+        property_manager.add_membership(TM_EA, "TM_EA")
+
+    # ------------------------------------------------------
+    # Region operations
+    # ------------------------------------------------------
+
+    def extract_region(self, extent:int):
+        """
+        REMOVE [hpos .. hpos+extent] from tape.
+        """
+        start = self.hpos
+        end = min(self.hpos + extent, self.tape.extent())
+        new_tape_lst = []
+        removed = []
+
+        for i in range(self.tape.extent()+1):
+            if start <= i <= end:
+                removed.append(self.tape.get(i))
+            else:
+                new_tape_lst.append(self.tape.get(i))
+
+        # rebuild tape
+        self.tape = ListTapeWrapper(new_tape_lst)
+        region_tape = ListTapeWrapper(removed)
+        return Region(region_tape, "extract", self)
+
+    def overlay_region(self, extent:int):
+        """
+        Region shares underlying tape cells (entangled).
+        """
+        start = self.hpos
+        end = min(self.hpos + extent, self.tape.extent())
+        # entangled: same tape wrapper, new TM_EA for region
+        # but region is returned as REGION object holding a dedicated wrapper clone
+        sub_cells = [self.tape.get(i) for i in range(start, end+1)]
+        region_tape = ListTapeWrapper(sub_cells)
+        return Region(region_tape, "overlay", self)
+
+    def insert_region(self, extent:int):
+        """
+        INSERT new blank cells to the right of the head.
+        """
+        start = self.hpos
+        # build blank region
+        blank = [None] * (extent+1)
+        region_tape = ListTapeWrapper(blank)
+        # splice into main tape
+        new_lst = []
+        for i in range(self.tape.extent()+1):
+            if i == start:
+                new_lst.extend(blank)
+            new_lst.append(self.tape.get(i))
+        self.tape = ListTapeWrapper(new_lst)
+        return Region(region_tape, "insert", self)
diff --git a/shared/authored/dw2/machines.py b/shared/authored/dw2/machines.py
new file mode 100644 (file)
index 0000000..2e793e6
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Mediator.py
+
+Implements:
+  - Mediator
+    copy_cell
+    copy_machine
+    step_all
+    assign_py_to_tm
+    assign_tm_to_py
+    rotate
+    shift
+"""
+
+from typing import Any, List
+from property_manager import property_manager, require_concept
+from TM_EA import TM, TM_EA
+
+
+class Mediator:
+
+    @staticmethod
+    def copy_cell(src:TM, dst:TM):
+        require_concept(src, "TM_A")
+        require_concept(dst, "TM_A")
+        v = src.write_cell_value()
+        dst.tape.set(dst.hpos, v)
+
+    @staticmethod
+    def copy_machine(src:TM, dsts:List[TM]):
+        for dst in dsts:
+            require_concept(dst, "TM_A")
+        require_concept(src, "TM_A")
+
+        # naive: copy entire region implied by src/dst extents
+        max_len = min(
+            src.tape.extent(),
+            min(dst.tape.extent() for dst in dsts)
+        )
+        for offset in range(max_len+1):
+            src.cue(offset)
+            for dst in dsts:
+                dst.cue(offset)
+                Mediator.copy_cell(src, dst)
+
+    @staticmethod
+    def step_all(tms:List[TM], n:int):
+        for tm in tms:
+            tm.step(n)
+
+    @staticmethod
+    def assign_py_to_tm(value:Any, tm:TM):
+        require_concept(tm, "TM_A")
+        tm.tape.set(tm.hpos, value)
+
+    @staticmethod
+    def assign_tm_to_py(tm:TM):
+        require_concept(tm, "TM_A")
+        return tm.write_cell_value()
+
+    # rotate: each tm's tape shifts into the next
+    @staticmethod
+    def rotate(tms:List[TM]):
+        if not tms:
+            return
+        # For simplicity, rotate cell 0 across all tapes
+        vals = [tm.tape.get(0) for tm in tms]
+        shifted = vals[-1:] + vals[:-1]
+        for tm, v in zip(tms, shifted):
+            tm.tape.set(0, v)
+
+    # shift: fill with None, drop values falling off
+    @staticmethod
+    def shift(tms:List[TM], n:int):
+        # only shift the first machine, others ignore
+        if not tms:
+            return
+        tm = tms[0]
+        ext = tm.tape.extent()
+        new_lst = [None]*(ext+1)
+        for i in range(ext+1):
+            j = i - n
+            if 0 <= j <= ext:
+                new_lst[i] = tm.tape.get(j)
+        tm.tape = type(tm.tape)(new_lst)
diff --git a/shared/authored/dw2/property_manager.py b/shared/authored/dw2/property_manager.py
new file mode 100644 (file)
index 0000000..3e9d681
--- /dev/null
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+property_manager.py
+
+Implements:
+  - PropertyManager
+  - SemanticConcept
+  - attach_properties
+  - get_properties
+  - property-based argument guard helpers
+
+Properties may be attached to ANY Python instance via object path.
+"""
+
+import inspect
+import types
+from typing import Dict, Any, Optional
+
+
+# ------------------------------------------------------------
+# Helper: get python object path
+# ------------------------------------------------------------
+def object_path(obj) -> str:
+    if isinstance(obj, (types.FunctionType, types.MethodType)):
+        mod = obj.__module__
+        if hasattr(obj, "__qualname__"):
+            return f"{mod}.{obj.__qualname__}"
+        return f"{mod}.{obj.__name__}"
+    cls = obj.__class__
+    return f"{cls.__module__}.{cls.__qualname__}"
+
+
+# ------------------------------------------------------------
+# SemanticConcept
+# ------------------------------------------------------------
+class SemanticConcept:
+    """
+    Holds:
+      - name_set:  dict[name] = { "doc":str,  "structure_fn":callable or None }
+      - membership: dict[py_path] = set(names)
+    """
+    def __init__(self):
+        self.name_set: Dict[str, Dict[str, Any]] = {}
+        self.membership: Dict[str, set[str]] = {}
+
+    # ------------------------------------------
+    def add_concept(self, name:str, doc:str="", structure_fn=None):
+        self.name_set[name] = {
+            "doc": doc,
+            "structure_fn": structure_fn
+        }
+
+    # ------------------------------------------
+    def add_member(self, py_path:str, concept_name:str):
+        if concept_name not in self.name_set:
+            raise KeyError(f"Concept '{concept_name}' not defined.")
+        s = self.membership.get(py_path, set())
+        s.add(concept_name)
+        self.membership[py_path] = s
+
+    # ------------------------------------------
+    def has_concept(self, py_path:str, concept_name:str) -> bool:
+        return concept_name in self.membership.get(py_path, set())
+
+    # ------------------------------------------
+    def structure_ok(self, concept_name:str, instance) -> bool:
+        """
+        If concept has a structure_fn, run it.
+        """
+        c = self.name_set[concept_name]
+        fn = c["structure_fn"]
+        if fn is None:
+            return True
+        return fn(instance)
+
+
+# ------------------------------------------------------------
+# PropertyManager (thin wrapper)
+# ------------------------------------------------------------
+class PropertyManager:
+    """
+    A single global instance is typical.
+    Holds a SemanticConcept instance and convenience methods.
+    """
+
+    def __init__(self):
+        self.sc = SemanticConcept()
+
+    def register_concept(self, name, doc="", structure_fn=None):
+        self.sc.add_concept(name, doc, structure_fn)
+
+    def add_membership(self, obj, concept_name:str):
+        p = object_path(obj)
+        self.sc.add_member(p, concept_name)
+
+    def get_memberships(self, obj) -> set[str]:
+        p = object_path(obj)
+        return self.sc.membership.get(p, set())
+
+    def has(self, obj, concept_name:str) -> bool:
+        return self.sc.has_concept(object_path(obj), concept_name)
+
+    def structure_ok(self, concept_name:str, obj) -> bool:
+        return self.sc.structure_ok(concept_name, obj)
+
+
+# global default property manager
+property_manager = PropertyManager()
+
+
+# ------------------------------------------------------------
+# public helpers
+# ------------------------------------------------------------
+def attach_properties(obj, **props):
+    """
+    Attach arbitrary named properties to an object instance.
+    (Not used for semantic concepts; purely freeform utility.)
+    """
+    p = object_path(obj)
+    existing = getattr(obj, "_rt_properties", {})
+    existing.update(props)
+    setattr(obj, "_rt_properties", existing)
+    return obj
+
+
+def get_properties(obj) -> dict:
+    return getattr(obj, "_rt_properties", {})
+
+
+# ------------------------------------------------------------
+# argument guard pattern
+# ------------------------------------------------------------
+def require_concept(obj, concept_name:str):
+    """
+    Require that obj belongs to the given semantic concept.
+    """
+    if not property_manager.has(obj, concept_name):
+        raise TypeError(
+            f"Object {obj!r} does not satisfy semantic concept '{concept_name}'."
+        )
+
+
+def require_any_of(obj, concept_names:list[str]):
+    """
+    Succeeds if obj belongs to ANY of the concepts.
+    """
+    for cname in concept_names:
+        if property_manager.has(obj, cname):
+            return
+    raise TypeError(
+        f"Object {obj!r} satisfies none of the required concepts: {concept_names}"
+    )