feat(pattern): griff-pattern — the std-only structural algebra (S16 Phase 1) - #113
Conversation
…ash-v1 (S16 Phase 1) Twenty-one failing tests and the API they call, with every body `unimplemented!()`: the tests must appear and fail, not fail to compile. What they pin, from docs/swang/spec.md: - a kernel is rectangular X/. — ragged rows, foreign characters, and emptiness are typed errors naming the offending cell (§1.6); - depth 0 is the kernel itself; an active parent expands into a kernel replica and an empty parent into an entirely empty block (§1.7); - budgets are required and fire *before* allocation, carrying the offending NodePath — 81 cells against a budget of 80 names the root (§1.4); - swang-prune-hash-v1 matches nine golden vectors computed by an independent BigInteger implementation of §1.8 before this crate existed, and the threshold is exactly floor(bps·2^64/10000) — 8000 bps is 14757395258967641292, 5000 bps is 2^63, 0 keeps nothing below the root, 10000 skips the test entirely; - at seed 17 and 5000 bps, child 0 (0xec2c… ≥ 2^63) prunes and its whole subtree stays silent two levels down, while child 2 (0x3a29… < 2^63) survives — the pruned-parent law made concrete; - row_major and snake reproduce the spec's §1.9 worked example (onsets 0 2 3 4 7 8 versus 0 2 4 5 7 8 from one kernel), and linearize preserves all 81 cells because silence is a slot, not an absence (§1.10); - two property tests hold the grid dimensions, the cell-preservation law, and the empty-parent law over random small kernels. The crate is std-only by contract (ADR-0029 §2): its Cargo.toml has an empty [dependencies] section on purpose. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
…itself The budget test claimed depth 2 needs 81 cells while the linearize test put 81 cells at depth 1, and the pruned-parent test addressed child 2's block at a column inside child 0's. With depth 0 defined as the kernel itself (spec §1.7), a depth-d grid carries d + 1 kernel factors per axis: depth 1 is 9×9 = 81 cells, depth 2 is 27×27. The budget test now breaches at depth 1, the property test asserts pow(depth + 1), and the prune test addresses child 2's block where it actually is — column 6 — asserting the full 9×9 silent subtree at depth 2. Still red: bodies remain unimplemented!(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
…6 Phase 1) The minimal implementation behind the red suite: - Kernel::from_rows walks characters once, naming the offending cell before anything allocates; - fractalize computes the whole-grid cost in u128 up front — depth 0 is the kernel, a depth-d grid carries d + 1 kernel factors — and answers each cell from its coordinate digits: most-significant digit first, each digit an active-kernel check, each proper prefix a swang-prune-hash-v1 test against the constant threshold floor(bps·2^64/10000); - the hash is mix64 (Stafford Mix13) folded from mix64(DOMAIN ^ seed), one child index at a time — it reproduces, bit for bit, the nine golden vectors computed by the independent BigInteger implementation before the crate compiled; - linearize reads rows straight or boustrophedon and keeps every cell, because silence is a slot; - no unwrap, no indexing, no floats, no usize in hashed state; the one arithmetic allow carries its reason (strides and dimensions are non-zero by construction). cargo test --workspace is green everywhere except the pre-existing missing_file_golden, which compares the OS's strerror text and fails on any non-English Windows locale regardless of branch; CI's Linux runner is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
📝 WalkthroughWalkthroughAdds a workspace-integrated ChangesPattern crate
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Kernel
participant fractalize
participant Expander
participant linearize
participant ActivitySequence
Kernel->>fractalize: kernel, depth, prune spec, budget
fractalize->>Expander: expand candidate cells
Expander->>Expander: evaluate activity and prune paths
fractalize-->>linearize: Expansion
linearize->>ActivitySequence: traversal-ordered cells
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab108bdd23
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Architecture/code review — changes required before mergeThe hard part is sound: I independently compared the coordinate-digit evaluator with a recursive substitution model over small rectangular kernels, depths 0–2, and several seed/density combinations. The resulting grids match, and the hash/threshold implementation agrees with the normative §1.8 vectors. The red → red-fix → green sequence is also materially honest. There are two code blockers and one phase-contract blocker. 1. Validate the complete kernel before allocatingCodex has already opened the correct inline thread. Please use a two-pass implementation:
Add a red test pinning shape-validation precedence for an input that is both ragged and contains a foreign character on the ragged row. The result should be 2. Every structural budget breach must carry
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
pattern/src/lib.rs (1)
109-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
is_active/active_countlogic betweenKernelandExpansion.
Kernel::is_active/Kernel::active_count(Lines 109-125) andExpansion::is_active/Expansion::active_count(Lines 219-235) are byte-for-byte identical implementations operating on the same{width, cells: Vec<bool>}shape. Consider extracting a small shared internal grid type (or a private trait with default methods givenwidth()/cells()accessors) that bothKernelandExpansiondelegate to, so the bounds-check logic (including the importantcol >= widthguard that prevents flat-index wraparound into the next row) only exists once and can't silently drift between the two copies in a future edit.♻️ Sketch of a shared grid helper
struct Grid { width: usize, height: usize, cells: Vec<bool>, } impl Grid { fn is_active(&self, row: usize, col: usize) -> bool { if col >= self.width { return false; } cell_index(self.width, row, col) .and_then(|index| self.cells.get(index)) .copied() .unwrap_or(false) } fn active_count(&self) -> usize { self.cells.iter().filter(|&&cell| cell).count() } }
KernelandExpansioncan then hold aGridfield (or newtype-wrap it) and forward their publicis_active/active_countto it.Also applies to: 219-235
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pattern/src/lib.rs` around lines 109 - 125, Extract the duplicated grid behavior from Kernel and Expansion into one private shared grid abstraction, such as Grid, containing the width and cells data and implementing is_active and active_count. Update both types’ public methods to delegate to this shared implementation while preserving the col >= width guard and existing out-of-range behavior; avoid maintaining separate copies of the bounds-check and counting logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@pattern/src/lib.rs`:
- Around line 109-125: Extract the duplicated grid behavior from Kernel and
Expansion into one private shared grid abstraction, such as Grid, containing the
width and cells data and implementing is_active and active_count. Update both
types’ public methods to delegate to this shared implementation while preserving
the col >= width guard and existing out-of-range behavior; avoid maintaining
separate copies of the bounds-check and counting logic.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7b3e7ed5-4362-4931-8e8d-a9e28659d166
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
Cargo.tomlpattern/Cargo.tomlpattern/proptest-regressions/lib.txtpattern/src/lib.rs
…ts path Two review findings from #113, pinned before they harden into historically-grown behavior: - a row that is both ragged and carries a foreign character must fail as RaggedKernel, because the spec validates the rectangle before any cell decodes (and before anything allocates) — today the character wins, which proves decoding runs first; - MaxDepthExceeded gains the NodePath every budget breach owes by contract; the up-front whole-expansion check names the root. The variant carries the field now, the construction site says unimplemented!() until green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
… carries a path The rectangle is judged in a first pass that stores nothing, so a row that is both ragged and foreign fails as RaggedKernel and no Vec exists for a kernel whose shape is already broken; cells decode in a second pass. MaxDepthExceeded fills the NodePath it now owes — the root, for the up-front whole-expansion check — and its Display says so. The prune-hash doc also stops overclaiming: it is evaluation-order- independent, not order-independent; the path's own element order is load-bearing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
…he tree implicit The stage plan promised Pattern/PatternTree/FractalSpec; the crate that passed review ships Kernel/Expansion/NodePath/PruneSpec — and no materialized tree at all, because the coordinate digits are the tree. Recognize that in ADR-0029 §2, the Phase 1 primitive list, and the decisions log, so the next agent extends the addressing scheme instead of summoning a second type family. thin moves out of Phase 1 and out of the specified v0.1 roster: its type contract stays fixed in spec §1.10, its selection rule is deliberately unspecified, and acceptance test 6 now guards the artifact's bar geometry instead of an operator that ships in no phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
Summary
The first implementation slice of S16 (ADR-0029): a new workspace member
griff-patternholding the pure structural pattern algebra —Kernel, boundedfractalize, path-addressed pruning (swang-prune-hash-v1),row_major/snaketraversals, andActivitySequence. Everything lowers-to-music lives elsewhere; this crate knows no MIDI, nogriff-core, no serde, no floats.Commit sequence (TDD per AGENTS.md)
540dde9) — 21 failing tests plus the API skeleton, all behavioral bodiesunimplemented!()(trivial accessors likeNodePath::as_slicewere plumbing, not algorithm). The nineswang-prune-hash-v1golden vectors were computed by an independent BigInteger implementation of spec §1.8 before the crate compiled.e2f7ba6) — the first red's depth arithmetic disagreed with itself (81 cells at depth 2 in one test, depth 1 in another) and mis-addressed a pruned block. With depth 0 = the kernel (spec §1.7), a depth-d grid carries d+1 kernel factors per axis.ab108bd) — the minimal implementation; all 21 tests pass, including the bit-for-bit match against the independent golden vectors.What the tests pin
X/., ragged/invalid/empty are typed errors naming the offending cell (spec §1.6);NodePath(§1.4);floor(bps·2^64/10000), edge laws at 0 and 10000 bps, pruned parent → silent 9×9 subtree two levels down, generation-seed independence by construction;0 2 3 4 7 8vs snake0 2 4 5 7 8from one kernel), andlinearizepreserves all 81 cells because silence is a slot (§1.10);Notes for review
cargo clippy --all-targets -- -D warningsis clean; the single arithmetic allow carries its reason.cargo test --workspaceis green except the pre-existingmissing_file_golden, which compares the OS strerror text and fails on any non-English Windows locale regardless of branch — Linux CI is unaffected. Worth a follow-up normalization, tracked separately.thinis deliberately absent: spec §1.10 fixes its type contract but not its selection rule; it gets its own red once the spec section lands (flagged during the design review).Refs #108, ADR-0029, S16 Phase 1.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NkqJUU6d1sW1RAfvyHrqVM
Summary by CodeRabbit
Review round 2 (comment 4972142215)
90d6828red →69f5b69green): two-pass kernel validation — the rectangle is judged in a pass that stores nothing, so a ragged-and-foreign row fails asRaggedKerneland nothing allocates for a broken shape. Codex's thread resolved.MaxDepthExceedednow carries theNodePathevery budget breach owes; the up-front check names the root.d823a7a, docs): ADR-0029 §2 and the Phase 1 primitive list now name the real types (Kernel/Expansion/NodePath/PruneSpec) and record that no materializedPatternTreeexists — the coordinate digits are the tree (decisions log).thinmoves out of Phase 1 and the specified v0.1 roster: its type contract stays in spec §1.10, its selection rule is deliberately unspecified; spec acceptance test 6 now guards the artifact's bar geometry instead.