diff --git a/.github/workflows/weave.yml b/.github/workflows/weave.yml index 51e5b87d..b5dfe1c0 100644 --- a/.github/workflows/weave.yml +++ b/.github/workflows/weave.yml @@ -4,10 +4,14 @@ on: paths: - 'research/weave/**' - '.github/workflows/weave.yml' + - 'stack-manifest.json' + - 'STACK_MANIFEST.md' push: paths: - 'research/weave/**' - '.github/workflows/weave.yml' + - 'stack-manifest.json' + - 'STACK_MANIFEST.md' workflow_dispatch: permissions: contents: read @@ -15,19 +19,47 @@ jobs: test: runs-on: ubuntu-latest steps: - - name: Checkout the event source with GitHub PR isolation - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Checkout event source with PR isolation + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - name: Verify exact event identity env: SOURCE_SHA: ${{ github.sha }} run: test "$(git rev-parse HEAD)" = "$SOURCE_SHA" - # Push validates the exact branch head; pull_request validates GitHub's - # isolated merge ref. Neither grants write credentials to tested code. - - name: Verify assembly and provisional transport, not native cipher security + - name: Checkout exact native geometry producer + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + repository: The-Interdependency/ucns + ref: 905e66964a495d7596a577bb64e4158db9465864 + path: .weave-inputs/ucns + persist-credentials: false + - name: Verify native cycle and existing regressions run: | set -euo pipefail + export WEAVE_SOURCES="$GITHUB_WORKSPACE/.weave-inputs" + test "$(git -C "$WEAVE_SOURCES/ucns" rev-parse HEAD)" = "905e66964a495d7596a577bb64e4158db9465864" python3 -VV PYTHONDONTWRITEBYTECODE=1 python3 research/weave/test.py --receipt "$RUNNER_TEMP/weave-check.json" cat "$RUNNER_TEMP/weave-check.json" + PYTHONDONTWRITEBYTECODE=1 python3 research/weave/cycle.py demo | tee "$RUNNER_TEMP/weave-demo.json" + - name: Package tracked research source and observed evidence + if: always() + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/weave-inspection" + git archive --format=zip --output="$RUNNER_TEMP/weave-inspection/stack.zip" HEAD research/weave tools stack-manifest.json STACK_MANIFEST.md AGENTS.md README.md docs + git -C .weave-inputs/ucns archive --format=zip --output="$RUNNER_TEMP/weave-inspection/ucns.zip" HEAD + git rev-parse HEAD > "$RUNNER_TEMP/weave-inspection/STACK_HEAD.txt" + git -C .weave-inputs/ucns rev-parse HEAD > "$RUNNER_TEMP/weave-inspection/UCNS_HEAD.txt" + for evidence in weave-check.json weave-demo.json; do + if test -f "$RUNNER_TEMP/$evidence"; then cp "$RUNNER_TEMP/$evidence" "$RUNNER_TEMP/weave-inspection/"; fi + done + - name: Retain exact-source research bundle, not a security release + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1, Node 24 + with: + name: weave-inspection-${{ github.sha }} + path: ${{ runner.temp }}/weave-inspection/ + if-no-files-found: error + retention-days: 7 diff --git a/AGENTS.md b/AGENTS.md index 2d6a98ca..7e8b2cad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -150,9 +150,10 @@ coherence; it does not replace workspace behavioral tests. independent repository/release authority boundary; exact UCNS affixiation geometry is unresolved. - URPCS is retired for specification divergence. Why GPT substituted the different architecture remains `hmmm`; its retained evidence is historical and implementation-local. -- Weave is specification-first research. Uneven partitions, stage-input - composition, schedule encoding, thread/corpus binding, hyperspace/gonol binding, - authentication/state, and the threat model remain `hmmm` until explicitly resolved. +- Weave remains research. Its explicit native binary sequence-cycle candidate + lives in `research/weave/native_binary.py`; UCNS geometry is consumed, not + redefined. UCHC binary graduation has not occurred. Private/public lifting, + authentication/state and full-cipher security remain `hmmm`. - The complete root `skill-lib/` snapshot refresh remains separate because the current provenance-bound fresh-making doctrine is newer than the local generator snapshot. - Project graduation automation remains unimplemented. diff --git a/README.md b/README.md index df73a786..87828ce7 100644 --- a/README.md +++ b/README.md @@ -305,3 +305,15 @@ and detailed design attribution remain unresolved; see Weave native public/private key derivation and source of asymmetry remain unresolved. Provenance metadata is not an implementation of that missing law. + +### Executable Weave sequence cycle + +`research/weave/CYCLE.md` documents the current corpus-normalization → repeated +sequence affixiation → prime-derived bit-interleave profile and exact inverse. +The binary origin/sequence candidate remains Stack-owned in +`research/weave/native_binary.py` and consumes unchanged native UCNS geometry. +UCHC supplies the origin/axis architecture reference; no new binary package has +graduated there. Private/public key derivation and security remain separate work. + +Run `WEAVE_SOURCES=/checkouts python research/weave/cycle.py demo` with the exact +UCNS source named in `research/weave/CYCLE_NATIVE.json`. diff --git a/STACK_MANIFEST.md b/STACK_MANIFEST.md index 0ef32727..dce11bf9 100644 --- a/STACK_MANIFEST.md +++ b/STACK_MANIFEST.md @@ -17,7 +17,7 @@ Provenance and authority-boundary record for `The-Interdependency/stack`. - English four-view complete-corpus audit UTC: `2026-09-22`, exact view source `1f9a35eb355296fc88d09784c7a3e2e95511ca31`; all 164,864 words, native and independent agreement. - Stack-manifest schema: `the-interdependency.stack-manifest` version `1.1.0` - Work-graph digest (SHA-256 over canonical `repositories` + `research_participants` + `boundaries` JSON): - `889a1234456c29c27473bfddb7808b209b2e1451da10e596fa9feea0f6ea4918` + `dc2cac5ac1d609f7f72434514ec95a55e8020b0b9326c17883b1ef25e86baaa3` - Weave workspace creation UTC: `2026-09-28`; new specification-first research starts from the intended multi-arity interleaving mechanism without URPCS inheritance - Machine-readable copy: [`stack-manifest.json`](stack-manifest.json) @@ -63,7 +63,9 @@ release identity. | `research/epac/` | `The-Interdependency/stack` | `0e8384bbb60e4c2189016a212bdd0030d04aed7d` | historical forge evidence; active implementation consumed from the independent EPAC release | no | | `research/epac-derived-carrier/` | `The-Interdependency/stack` | `545e135e2efbbcf29f033ab5530ac4876a68b718` | active stack-local carrier audit consuming exact EPAC source; no EPAC implementation/public-contract or scientific standing transfer | no | | `research/urpcs/` | `The-Interdependency/stack` | `a428a41a38a8b30bacb7025a4d54070b228a8089` | retired substituted-codec historical evidence only; no active URPCS authority or evidence transfer | no | -| `research/weave/` | `The-Interdependency/stack` | `1ba201449731337dc20c564788f4303c8910bfdd` | full gonol/private-key/thread/corpus/interleave/asymmetric construction research; native derivation unresolved; no URPCS implementation inheritance or security standing | no | +| `research/weave/` | `The-Interdependency/stack` | `1ba201449731337dc20c564788f4303c8910bfdd` | Stack-owned native binary sequence-cycle candidate; private/public derivation unresolved; no URPCS inheritance or security standing | no | +| `research/weave/` | `The-Interdependency/ucns` | `905e66964a495d7596a577bb64e4158db9465864` | native exact axis-circle and Mobius geometry; source-verified unchanged mathematical producer for native_binary.py | no | +| `research/weave/` | `The-Interdependency/uchc` | `4ad94e92be10d2c4c875a848addfda46f3c7cfdb` | origin/axis architecture and domain migration boundary; architectural reference only; speculative binary implementation remains in Stack until graduation gates complete | no | | `research/english-gonol/` | `The-Interdependency/stack` | `99b3598b02a6e683b3c84184d8ea443b12fc0e1a` | stack-local English lexical/gonol construction separated from EDCM; full pinned-corpus v2 build/replay survived; consumes UCNS geometry; EDCM may evaluate outputs but does not define construction | no | | `research/english-gonol/` | `The-Interdependency/edcm` | `0873c105799681ea1f7ccb3e619d1f7aebbd85e0` | exact repaired EDCM semantic metric-origin producer; preserves canonical O/L identities and proxy-alignment metadata without refreshing `libs/edcm` or transferring measurement authority | no | | `research/python-gonol/` | `The-Interdependency/stack` | `0e8384bbb60e4c2189016a212bdd0030d04aed7d` | stack-local bottom-up Python 3.12 source gonol construction; applies METAPAT affixiation semantics, consumes optional UCNS geometry, and transfers no language authority to UCNS or EDCM | no | @@ -237,7 +239,7 @@ independent repository, release, or security-reviewed cryptosystem. - English Gonol Construction remains stack-local research; independent repository/release authority and the exact UCNS displacement law have not been established. - Python Gonol Construction remains stack-local research; independent repository/release authority and exact UCNS affixiation geometry remain unresolved. - URPCS is retired for specification divergence; why GPT flattened/substituted the intended construction remains unresolved, and retained evidence is scoped only to the substituted implementation. -- Weave preserves the intended mechanism, while uneven partitioning, stage-input composition, schedule encoding, thread/corpus binding, hyperspace/gonol binding, authentication/state, and the threat model remain unresolved. +- Weave now runs one explicit native sequence-cycle profile with corpus normalization and prime-derived splits; cryptographic lifting, authentication/state and full-cipher security remain unresolved. - `skill-lib/` remains a special operational snapshot at stack root rather than following the ordinary `libs/` + `research/` pair. ## UCHC input candidate @@ -255,3 +257,17 @@ and detailed design attribution remain unresolved; see Weave native public/private key derivation and source of asymmetry remain unresolved. Provenance metadata is not an implementation of that missing law. + +## Weave native sequence-cycle candidate + +`research/weave/native_binary.py` is the Stack-owned binary-origin and sequence +closure implementation. It consumes the exact UCNS sources above and follows the +UCHC origin/axis architecture, without shipping an ungraduated UCHC domain. The +misplaced UCHC #7 proposal is retained only as source provenance in +`research/weave/NATIVE_BINARY_PROVENANCE.json`. No geometry authority transfers. + +The cycle normalizes once with corpus bytes, discovers repeated multi-byte +sequences, retains native whole/occurrence-circle relations, uses scoped numeral +references and prime-route bit partitions, and reverses the completed rounds. +See `research/weave/CYCLE.md` for the explicit profile, exact source lock, bounds +and all-cost accounting. This is construction/recovery, not established encryption. diff --git a/research/weave/ASSEMBLY.md b/research/weave/ASSEMBLY.md index 75c56991..715c170f 100644 --- a/research/weave/ASSEMBLY.md +++ b/research/weave/ASSEMBLY.md @@ -1,5 +1,11 @@ # Weave: full modular assembly v1 +**Historical transport/assembly scope.** This document describes the retained +transport experiment, not the current binary sequence cycle. Current executable +construction and ownership are in [CYCLE.md](CYCLE.md); current question +status is in [QUESTIONS.md](QUESTIONS.md). The older unresolved declarations below +apply to that assembly, not to already-implemented parts of the new cycle. + Status: ASSEMBLY IMPLEMENTED; COMPLETE ENCRYPTION NOT IMPLEMENTED. This is a source-bound composition contract and switchable runner, not a replacement diff --git a/research/weave/CYCLE.md b/research/weave/CYCLE.md new file mode 100644 index 00000000..e704297d --- /dev/null +++ b/research/weave/CYCLE.md @@ -0,0 +1,368 @@ +# Weave iterative sequence cycle v1 + +Status: executable construction and exact recovery. Public/private asymmetric +key generation and cryptographic security are not implemented by this profile. + +## The complete implemented path + +1. Normalize once using actual corpus bits. Preserve the exact original byte + length inside the normalized frame. +2. Discover repeated multi-byte sequences in the current byte stream. Select + nonoverlapping occurrences, retaining every residual byte. +3. Close the sequences through the Stack binary-domain candidate. The eighth/whole + circle holds native sequence attachments; seven occurrence circles hold count, + order and native positions. Sequential byte occurrences remain addressable axes. +4. Bind definitions through the existing length-bearing integer/Unicode numeral + layer. Emit the seven native occurrence-circle streams and the positions needed + to reconstruct source order. Include every definition and relation in accounting. +5. Execute the configured prime-index and embedded-prime-span route. Derive the + section arity and exact nonempty bit boundaries from that route and current size. +6. First/last interleave the bits within each section. Feed those actual rearranged + bytes into step 2 of the next round. + +Reverse performs the last bit permutation's inverse, native sequence-position +recovery, preceding rounds in reverse order, and corpus denormalization. The only +inputs are the final record, the same supplied profile/material, and pinned producer +sources. Original plaintext and an untransmitted encoder trace are not accepted. + +This implements the corrected normalization/affixiation/interleave cycle, not all +historical proposals or a disguised replacement cryptosystem. There is no +one-bit-per-circle mapping, two-bit substitution, fixed 16-bit byte codeword, +conventional cipher fallback, or inferred twofold expansion. + +## Explicit implementation profiles + +The following choices make the construction executable and are named/versioned, +not attributed to the user as universal laws: + +- **corpus-tail bucket normalization:** frame the original byte length and a corpus + source digest with the message, then fill the next strictly larger configured + bucket with actual cyclic corpus bits starting at the configured bit offset. + At least one filler byte is used. The source digest is not the corpus substitute: + the actual filler is checked during recovery. The public format is research only. +- **maximal-lcp-longest-first-v1:** suffix-array/LCP discovery, longest candidates + first, with earliest-source tie breaking and leftmost nonoverlapping selection. + Counts mean selected partition occurrences, not all overlapping substring matches. + No fixed chunk length or silent candidate truncation is introduced. This is not + a globally optimal compression claim. +- **native binary attachment:** The Stack candidate attaches each saved sequence at its first + selected occurrence's native byte axis. UCNS owns the exact `r/N` axis position + and complete 720-degree state. Source data, occurrences and native objects remain + inside the construction; hashes merely identify it. +- **circle assignment:** definition admission order indexes the profile's explicit + permutation of circles 1..7. Eight configured exact fractions govern native + circle-space displacement, including the whole circle at index 0. +- **prime-route-compositions-v1:** execute the full prime path, including every + source/span position and returned value. A prefix-bearing radix numeral of those + route tokens selects among explicit candidate arities and ranks an ordered + composition of the actual bit length. Different routes may select the same map; + no secret entropy is inferred from the size of the determinant. +- **end order:** `first-last` is explicit in the sample profile, matching the latest + clarified cycle. `last-first` remains supported and separate. Historical transport + ordering is not silently changed. + +All choices are visible in `profiles/cycle-v1.json`, the named implementation +profiles, or the native producer documentation. They can be replaced under a new +profile identity without redefining UCNS or UCHC geometry. + +## Native dependency boundary + +`CYCLE_NATIVE.json` v2 binds the exact local `native_binary.py` blob and its two +unchanged UCNS producer modules at `905e66964a495d7596a577bb64e4158db9465864`. +`cycle_native.py` executes a fresh copy of each verified buffer on every load; +mutable `sys.modules` cache slots are never accepted as source evidence. + +The binary-origin/sequence implementation remains in its owning Stack forge. +UCHC `4ad94e92be10d2c4c875a848addfda46f3c7cfdb` supplies the architecture and +migration boundary, not a graduated binary runtime. The premature UCHC #7 code +is retained as provenance in `NATIVE_BINARY_PROVENANCE.json` and replaced by the +repaired forge-owned candidate. No English/Python migration or UCNS authority moves. + +Actual UCNS `AxisCirclePosition` and `NativeMobiusState` objects construct and +recover positions. The eighth remains the whole/origin; seven circle streams +preserve sequence occurrence order. `SequenceTable` validates source, order, +definitions, every occurrence, and native positions including space offsets on +construction and before exporting a receipt or reconstruction. A frozen record +with a forged relation is rejected, including through `dataclasses.replace`. + +The v2 lock and binary-domain identity deliberately reject records from the old +ungraduated candidate. Rebuild research records using this exact profile/source. +Native representation is not authentication, a trapdoor, or a security claim. + +## Framing and full size accounting + +`WVC` + version byte 1 identifies the outer cycle record. It binds native-source lock, +profile identity, retained message-origin receipt, round count and final payload. +The original-length/corpus frame is inside the repeated transformations, not an +unreported external reconstruction file. + +Each `WAF` + version byte 1 affixiation record contains source byte length, a complete +numeral packet, seven occurrence counts and exact native source-position fractions. +The numeral occurrence stream is in native circle order, NOT plaintext order. +The next round consumes the entire preceding permuted record, including information +that will be needed to undo earlier rounds. No hidden sidecar or overwritten history. + +An attached Unicode symbol is a scoped reference, not one byte containing arbitrary +information. All its definitions and actual UTF-8 bytes are counted. Research +source/profile digests and structural rejection are not authentication. + +The three-round nonsecret demo measured 784 original bytes, normalized to 1,024: + +| Round | Input bytes | Definitions | Selected occurrences | Complete output bytes | +|---|---:|---:|---:|---:| +| 0 | 1,024 | 8 | 11 | 910 | +| 1 | 910 | 41 | 56 | 1,669 | +| 2 | 1,669 | 231 | 321 | 6,632 | + +The outer frame adds 103 bytes: total **6,735 bytes**, with exact recovery. +This example expands overall. Later rounds can create metadata cost larger than +any repeated-sequence saving. No claim that additional rounds always compress, +strengthen secrecy, or produce independent transformations is made. + +## Usage + +Python 3.12+; the consumer and native operations require only the standard library. +The source root contains the locked `ucns/` checkout. No UCHC runtime checkout is required. The runnable +bundle supplies exact, read-only minimal snapshots under `sources/`. + +```sh +cd research/weave +python cycle.py demo --sources /checkouts +python cycle.py forward input.bin output.wvc --corpus corpus.bin \ + --profile profiles/cycle-v1.json --sources /checkouts +python cycle.py reverse output.wvc recovered.bin --corpus corpus.bin \ + --profile profiles/cycle-v1.json --sources /checkouts +WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_cycle.py -v +WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-cycle-check.json +``` + +`--sources` defaults to `WEAVE_SOURCES`, then the local `sources/` folder. Output +files use exclusive creation; existing files are never overwritten. Actual corpus +bytes and the same profile are required for recovery. Neither should be mistaken +for an asymmetric private/public key pair. Do not use this research format for secrets. + +Default execution budgets: 256 KiB source message, 1 MiB intermediate records and +corpus, 32 rounds, 16 million candidate-occurrence visits, 131,072 native occurrences +per round, and 16 million aggregate scheduler prime-work units. Library callers +can supply `CycleLimits`. Guards produce visible refusal before exceeding the +admitted scope; no mid-run truncation is returned as success. These are operational +profiles, not mathematical limits or cryptographic security parameters. + +## Tests, integration, and rollback + +Thirty cycle tests cover all 8,191 binary-alphabet byte strings of lengths 0..12; +competing overlaps/residuals; all 4,083 small ranked compositions in the declared +range; all byte values; both end orders; effective native spaces; exact prime paths; +empty/random/multiround messages; a 65,536-byte roundtrip; malformed records/configs; +resource guards; and a fresh recovery process after original-file deletion. + +The numeral dependency's four live review findings are repaired: conflicting active +circle documentation, reset decode prime budgets, zero-denominator CLI handling, +and malformed-input versus resource-limit classification. Three new regressions +bring its suite to 18. The obsolete bit-per-circle modules and their 22 tests are +removed, with these sequence/native operations as replacement. Fifty-two original +transport/assembly tests retain their original limited scope. Full Weave inventory: +52 + 18 + 30 + 13 native-origin + 12 repair + 18 review-closure + 9 follow-up + 9 terminal-record + 3 header-admission + 4 numeral-replay + 8 cheap-admission regressions = 176. The original +13 native producer tests are retained in Stack as `tests/test_binary_origin.py`. +The repair regressions exercise forged closed records, fresh verified module +loading, atomic failed-write cleanup, empty-input attachments and manifest edges. + +The cycle is opt-in. The original transport experiment and the all-on native cipher +refusal remain separate; no missing asymmetric operator is silently filled. +Rollback removes the cycle modules/profile/dependency locks/workflow additions as +one change. Historical discarded encoders remain in Git, not in active imports. + +## hmmm + +Automatic discovery of short prime recipes for arbitrary literal sequences, an +actual private/public degeneration and recovery advantage, authentication/replay, +and security analysis of the completed cipher remain open. The named normalization, +selection, attachment and determinant profiles are implemented candidates, not +universal rules. This cycle now executes those choices and reports their real cost. + +## Output failure boundary + +Both `cycle.py` and `numeral.py` write a private temporary sibling, flush, fsync +and close it, then install it with an atomic no-clobber hard link. Write, flush, +fsync or close failure leaves no partial destination. Existing files and symlinks +are never overwritten. A filesystem without the required hard-link semantics +refuses explicitly. Abrupt process-crash cleanup and directory durability are +not guaranteed. Empty numeral input still validates its required attachments. + +## Final-review closure repairs + +The four later PR #77 findings are covered by `tests/test_review_closure.py`. +Accounting reuses its validated bit count; numeral CLI operations share one charged +prime-work engine through parsing, accounting, and recovery. Budget failure occurs +before publishing output. Native closure comparison checks the exact UCNS classes +from the origin's Geometry instance and compares only typed canonical fields, never +arbitrary object equality. This also rejects equality-spoofing scalar subclasses. +Validation calls the trusted record classes directly; per-instance methods cannot +replace the validation of a supplied record. + +The inverse rediscovers sequences under the declared selection profile and requires +both definitions and source order to match. Its rediscovery obeys the same explicit +per-round byte and candidate-visit limits as forward discovery. A different valid +partition is not silently attributed to the selected deterministic profile. + +Usage: `WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_review_closure.py -v`. +The profile, forward operations, UCNS producers, ownership and cryptographic standing +are unchanged. The local candidate source lock changes with the repaired source; +records bound to the previous lock must be regenerated rather than accepted under +false source provenance. This is stricter validation, not authentication. + +## Record-owned dispatch boundary + +Sequence-table exports call the trusted class validator rather than `self.validate`. +The numeral layer likewise invokes trusted Packet, Entry, BitBlock and PrimePath +operations while handling supplied records. Altered block fields are revalidated; +record-owned `validate`, `replay`, and `to_bytes` overrides cannot authorize or +change serialized content. Prime opcodes require exact string values. + +Six additional regressions cover the table/Entry follow-up findings and the same +pattern in enclosing packets, bit blocks and recipes. This boundary concerns +supplied data records; it does not sandbox hostile Python code that replaces the +trusted runtime classes or producer modules themselves. Runtime code is trusted. + +## Follow-up discovery and dispatch repairs + +`tests/test_final_findings.py` covers the five subsequent review findings. A capped +LCP candidate now expands to the full matching suffix interval before selection; +duplicate intervals are merged before applying the existing longest-first and +leftmost-nonoverlapping rules. For `00 01 00 01 00 01 01`, all three disjoint +`00 01` occurrences are retained, followed by the residual `01`. The selected +profile is unchanged; the earlier implementation omitted a qualifying occurrence. +The regression suite compares the LCP-derived candidate family against explicit +prefix matches on all 1,022 nonempty binary-alphabet strings of lengths 1..9. + +Native closure uses the trusted ByteOrigin byte-axis constructor and trusted +geometry/axis export methods, so a record-owned method cannot replace the origin +under validation. Profile identity revalidates scalar and nested round fields +and uses trusted serializers. The scheduler invokes trusted PrimePath replay, +including its aggregate budget, rather than a method on the supplied path. + +This cycle profile emits literal sequence definitions. Recovery now rejects a +recipe-backed substitute even when the recipe produces the same bytes. The +standalone numeral layer retains its explicit prime-recipe support; it has not +been removed or silently disabled there. Supporting such definitions in a cycle +requires a profile that actually emits them. The existing native-source lock +rotation refuses old records instead of attributing them to the repaired code. + +Run the added regressions with: + +```sh +WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_final_findings.py -v +``` + +The earlier follow-up checkpoint passed its then-current 152-test suite. The +three-round demo still costs 6,735 total bytes and recovers exactly. These repairs +change neither the UCNS producer pins nor the ownership or cryptographic standing of the construction. + +## Canonical rational, route and inverse validation + +The three terminal-review findings at `76508332f353` are exercised by +`tests/test_terminal_records.py`. Numeral attachments validate the exact integer +numerator and positive denominator and compare them with a newly normalized +Fraction before serialization. Altering an exact Fraction object's internals no +longer produces a record the canonical decoder would reject. Native coordinate +and space inputs receive the same canonical-field check before geometric work. + +`plan` validates a supplied Route by re-executing its PrimePath with trusted +methods and comparing the typed trace and determinant. Direct construction and +mutation do not create an evaluated route merely through the record's class. +Its optional `limits` and `engine` parameters account for this actual replay. +Both cycle directions share one scheduler engine across initial evaluation and +all subsequent route validations. The aggregate work allowance is not reset; +a workload that formerly fit only because revalidation was omitted may now +refuse within its declared budget. The split formula and valid results are unchanged. + +Native inverse recovery, its axis construction and coordinate replay call trusted +Geometry methods. The cycle's corresponding geometric exports do so as well. +Supplying replacement instance methods cannot license arbitrary coordinates. This +remains a data-record validation boundary, not protection against modification of +trusted Python classes or producer modules. + +Run the terminal-record and header-admission regressions with: + +```sh +WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_terminal_records.py -v +``` + +The native source lock rotates with this repair. Regenerate records tied to the +previous lock rather than treating them as evidence for the new source. UCNS +producer source, UCHC architectural reference and Stack ownership are unchanged. + +## Outer-header admission before expensive recovery + +Recovery parses the complete outer frame first: magic, fixed identity fields, +root, canonical round count, bounded payload length, and absence of trailing +bytes. It then validates the supplied profile and matches the source/profile +identities and round count. Only admitted framing reaches prime-route replay +or loading and execution of the pinned native producers. Valid records retain +the same single scheduler budget and reverse operations; no wire field, split +formula, producer pin, ownership or security claim changes. + +Three added tests in `tests/test_terminal_records.py` check every truncated +prefix, wrong magic, identity/count/length mismatches, noncanonical integers +and trailing bytes without invoking either expensive step. A complete header +still reaches real scheduler evaluation and its existing budget refusal. + +Usage: `WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_terminal_records.py -k HeaderAdmissionTests -v`. + +The outer-header repair passed its then-current **164-test** local suite with +zero failures, errors or skips. The current result is recorded below; neither the +152-test nor 164-test checkpoint is presented as the latest inventory. + + +## Single recipe replay during numeral decoding + +The review of `8bc5e1f7ce7f` found that a shared counter still charged decoding +for a redundant second recipe evaluation. A recipe needing five work units could +encode under `prime_work=5` but fail to decode under those identical limits. + +The decoder now reconstructs each recipe-backed block once using the trusted +PrimePath implementation and one aggregate engine. It then checks the packet's +complete typed structure, angular attachments, definition uniqueness, occurrences +and declared length without replaying those same definitions. No caller-supplied +bypass or reusable validation flag is introduced. Public validation, encoding, +accounting, occurrence export and restoration continue to revalidate supplied +objects and recipes; mutation after decoding is not trusted. + +Four regressions in `tests/test_numeral_replay.py` verify the exact same-budget +roundtrip, one replay per definition and true aggregate refusal, malformed recipe +record rejection, and revalidation after a decoded record is changed. The prior +budget tests now count necessary actual work rather than mandating duplicate work. +CLI inspect/recover still share one counter across their distinct operations. + +Usage: `python -m unittest discover -s tests -p test_numeral_replay.py -v`. + +The single-replay checkpoint passed **168 tests, zero failures, errors or skips**, +with unchanged sources during execution. The native source lock, profile, wire +format, UCNS producers and 6,735-byte exact-recovery demo are unchanged. This +numeral resource repair does not change Weave's cryptographic standing. Hosted +checks and current-head terminal review remain separate acceptance gates. + + +## Complete cheap admission and public helper validation + +The review at `07ac59976b9e` identified two remaining gaps. Numeral decoding now +parses every definition into decoder-owned pending data, validates all cheap entry +metadata and recipe shapes, uniqueness, references, declared length and trailing +bytes, and only then executes accepted prime recipes once under its aggregate +engine. No fabricated block values or persistent validation flags are used. Shared +scope/reference checks keep public Packet validation and wire admission aligned. +A mathematical property requiring actual prime evaluation still receives that +bounded evaluation; malformed metadata cannot consume that work first. + +Public normalize/denormalize operations validate Profile through the trusted class +before reading its fields. Affix/unaffix apply the equivalent check to RoundSpec +before discovery or decoding. This closes the same supplied-profile boundary for +all four public helpers, including frozen-record mutation and instance-method +shadowing. Valid formulas, wire format, native lock, producer ownership and the +6,735-byte exact-recovery demonstration remain unchanged. + +Run `WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_admission_order.py -v`. +Eight regressions cover early and late malformed definitions, recipe shape, +single-replay accounting, direct helper mutation/refusal and valid recovery. +Current suite inventory is **176**; run `test.py` for source-bound results. Earlier +counts in this document are explicitly historical checkpoints, not current results. diff --git a/research/weave/CYCLE_NATIVE.json b/research/weave/CYCLE_NATIVE.json new file mode 100644 index 00000000..0997b017 --- /dev/null +++ b/research/weave/CYCLE_NATIVE.json @@ -0,0 +1,18 @@ +{ + "schema": "weave.native-inputs/v2", + "ucns_commit": "905e66964a495d7596a577bb64e4158db9465864", + "ucns_sources": { + "src/ucns/axis_circle.py": "768777c8eca6e65537fdbab2003835d6f4f40569", + "src/ucns/direct_mobius.py": "14a4cee36b5bbfa72cf3c03703c427abdac7f33d" + }, + "binary_source": { + "owner": "The-Interdependency/stack", + "path": "native_binary.py", + "git_blob": "6a6ba9f57b83b62116a57113a5c6358fab7e7831", + "standing": "forge-owned candidate, not graduated" + }, + "uchc_architecture": { + "commit": "4ad94e92be10d2c4c875a848addfda46f3c7cfdb", + "relation": "origin/axis architecture and migration boundary; not a binary runtime dependency" + } +} diff --git a/research/weave/CYCLE_WORK_GRAPH.json b/research/weave/CYCLE_WORK_GRAPH.json new file mode 100644 index 00000000..897c1189 --- /dev/null +++ b/research/weave/CYCLE_WORK_GRAPH.json @@ -0,0 +1,50 @@ +{ + "schema": "the-interdependency.stack-manifest", + "version": "1.0.0", + "work_graph_sha256": "42bcc4c2b1b4e13aa42d71d2760e47762b65fe0c9f92fdd1fcd9808ba3bdb546", + "repositories": [ + { + "repository": "The-Interdependency/uchc", + "commit": "4ad94e92be10d2c4c875a848addfda46f3c7cfdb", + "authority": "origin/axis architecture and migration boundary", + "relation": "architectural reference only; no ungraduated binary runtime or public package" + }, + { + "repository": "The-Interdependency/ucns", + "commit": "905e66964a495d7596a577bb64e4158db9465864", + "authority": "native exact geometry", + "relation": "unchanged mathematical producer consumed by the new domain" + }, + { + "repository": "The-Interdependency/stack", + "commit": "8c737f1e129b90004e986fc74940990c507762f0", + "authority": "Weave binary-origin candidate, sequence selection and round composition", + "relation": "exact repair starting point; reviewed local native_binary.py is source-locked in CYCLE_NATIVE.json" + }, + { + "repository": "The-Interdependency/skill-lib", + "commit": "9867ab33877f2b1f50f8a501cf379aa99360bd52", + "authority": "build and evidence discipline", + "relation": "governing workflow source" + }, + { + "repository": "The-Interdependency/metapat", + "commit": "86415a5368c1a1417c2b6731f19967a6b1fce6bb", + "authority": "domain restraint and affixiation semantics", + "relation": "semantic consultation only; no security or geometry transfer" + } + ], + "boundaries": { + "authority_transfer": false, + "proof_status_transfer": false, + "measurement_status_transfer": false, + "semantic_mapping": "declared binary-domain candidate; no language or cryptographic equivalence", + "agent_scope": "cross-repository-work-graph", + "hmmm": [ + "Named normalization, selection, attachment and split profiles are executable candidates, not universal rules.", + "Public/private cryptographic lifting, authentication and full-cipher security remain unimplemented.", + "This graph changes no English/Python graduation or UCNS geometry authority.", + "Binary closure remains in the Stack forge; UCHC proposal #7 is provenance, not graduation or a runtime input." + ] + } +} diff --git a/research/weave/EIGHT_CIRCLE.md b/research/weave/EIGHT_CIRCLE.md index 8bcfd5e7..ca8cdcb9 100644 --- a/research/weave/EIGHT_CIRCLE.md +++ b/research/weave/EIGHT_CIRCLE.md @@ -1,145 +1,46 @@ -# Weave: byte-scoped whole-plus-seven repair +# Weave: superseded byte/star interpretation -Status: **structural repair implemented; native positional cipher not implemented**. -This replaces the incorrect active constructions from Stack PRs #73 and #74. -The next proposed answers are in [QUESTIONS.md](QUESTIONS.md); none is silently enabled. +**Historical only. The active cycle is [CYCLE.md](CYCLE.md).** -## Settled construction +The one-bit-per-circle interpretation from PR #76 is superseded by the user's +later sequence/occurrence clarification. A selected multi-byte sequence is saved +at an angle on the eighth/origin circle. A circle among the seven records its +occurrences and order. This is not an eight-circle allocation for every byte. +The eight circle count does not establish a 16-bit output codeword or total 2x size. -Each raw-byte message is an origin. Each byte occurrence participates at that origin -as one eight-bit unit, with one big circle G0 and seven small circles G1 through G7. -One source bit participates through each circle. The big circle is both a bit-bearing -member and the retained whole; its dynamic role must not be replaced by metadata, -a fixed reference, or a checksum. +The old `stages/eight_circle.py` and `stages/message_origin.py` structural records +and their 22 tests are removed. Native replacement is the Stack-owned binary-origin/sequence candidate +(`native_binary.py`), consumed by `cycle_native.py` and `cycle.py`. It uses +actual UCNS axis-circle and Mobius objects and performs exact coordinate recovery. +The associated bit-per-circle questions Q11/Q13/Q14 no longer control construction. -Every public two-circle view contains G0 and exactly one Gi, with 1 <= i <= 7. -There are seven possible whole-part views, not four disjoint pairs. This does not -require emitting all seven views, fix a visitation order, or assign one pair per bit. +Retained architectural distinctions: one whole plus seven occurrence circles; +720-degree complete return; key/profile-specific space relationships; byte-occurrence +identity; and the intended whole-plus-one public exposure. The last remains an +asymmetric research objective, not a property claimed by the current cycle record. -Every circle has the complete 720-degree return. Its relationship to space is -instantiated by the key set, not fixed by a universal placement formula. The full -key retains the eight-circle configuration; the public object exposes two circles -at a time. Deriving the actual cryptographic public object is distinct from enforcing -that pair shape. +## Evidence correction -The stated expansion remains **8 source bits -> 16 ciphertext bits per byte**. It is -a requirement, not an implemented serializer and not a consequence of the number of -circles alone. The removed per-bit duplication did not implement this requirement -for the correct reason. +PR #73's per-bit two-sheet encoder and PR #74's bit-axis feedback were wrong +implementations. Their public recovery attacks apply to those implementations, +not to the intended Weave construction. Changing data unavailable to a public +sender while holding every sender input fixed cannot demonstrate whether a private +key is necessary for recovery. That inference remains withdrawn. -## Executable repair +Historical source identities: -`stages/eight_circle.py` now has byte-level construction records: +- PR #73: `a3836f5632ed3babe1bc8c3dbebab60186c2bc78`; +- PR #74: `92ccda0620c06bc46654939c061ceb2d36476cb9`; +- PR #76 structural repair: `d0e76d58f832a5664578af39103cdd0697c091d3`. -- `Circle` stores a key-set circle binding and exact space coordinate. It is not a - replacement UCNS Gonol constructor. One scalar coordinate is not the complete - native relationship to space. -- `FullKeySet` accepts eight bindings and an explicit source-bit/circle assignment. - `degenerate(i)` can produce only `(G0, Gi)`; direct `PublicPair` construction also - rejects a missing whole, repeated whole, extra member, or malformed circle. -- `construct_byte` accepts a complete byte and eight caller-supplied exact positions, - preserving each source bit once. It emits a plaintext-bearing construction record, - **not ciphertext**. `source_value` reads that retained source, not a private inverse. -- Positions are stored as exact rational turns modulo two: a 360-degree displacement - stays distinct, while the complete 720-degree return agrees. No sheet threshold - converts bit values into public output codes. +These are historical provenance, not active capabilities or security evidence. -`stages/message_origin.py` now contains one ordered participant per **byte**. -`construct_at` attaches that complete byte's eight placements at its message-origin -occurrence. Repeated values preserve their separate occurrence indices. The supplied -origin name is a namespace, not content hashing, nonce generation, or a secrecy claim. +## Usage -The invented `CouplingKey`, public-feedback recurrence, per-bit `MessageAxis`, -`CoupledWitness`, bitwise `encode`/`decode`, and `public_recover` have been removed. -Their supported replacement is the corrected byte/star structure, not another cipher -substitute. The existing transport experiment and full-profile missing-operation -refusal remain separate and unchanged. - -## What this repair does not implement - -The real native origin-axis binding, full relational circle-space state, key generation, -whole-byte positional transformation, dynamic G0 evolution, cryptographic degeneration, -private reconstruction and 16-bit ciphertext layout are not implemented by these records. -No claim is made that merely requiring a `FullKeySet` argument makes its hidden circles -necessary for recovery. The next questions concern these actual operations. - -UCNS retains geometry authority. UCHC origin-axis architecture remains the intended -construction dependency; this repair does **not** claim to execute UCHC by copying a -language source or relabeling occurrence metadata. Existing Stack/UCHC implementation -and migration boundaries are unchanged. No `libs/` source or source pin is changed. - -## Corrected evidence scope - -PR #73 encoded each individual source bit into two public sheet bits. PR #74 then -added public feedback to that wrong unit. Those programs were publicly recoverable, -but neither represented the requested byte-scoped construction. Their failures are -not falsifications of Weave, and their successful tests did not establish that the -requested geometry or message-origin construction had been implemented. - -The former hidden-circle perturbation conclusion was also invalid. For a public-only -sender, encryption is a function of its public key, message and explicit public-side -inputs/randomness. Holding all those inputs fixed holds its output fixed regardless -of changes to unavailable private records. That observation cannot establish whether -private information is necessary for inversion. A correct test must examine recovery -and unauthorized inversion for the actual forward/recovery relation, accounting for -valid or equivalent keys rather than demanding arbitrary private changes alter a -public sender's output. - -Historical source remains in Git at: - -- PR #73 merge: `a3836f5632ed3babe1bc8c3dbebab60186c2bc78`; -- PR #74 merge and repair base: `92ccda0620c06bc46654939c061ceb2d36476cb9`. - -These are provenance for removed implementations, not live capability or security -claims. Their invalid observations are not copied into the current security results. - -## File plan and verification - -The repair changes only `research/weave`: the two structural modules, their two test -modules, the existing full-suite receipt runner, this document, the question register, -and the specification. It removes the stale `verify.py` command from the specification. -Roll back as one transaction; restoring a deprecated encoder is not an accepted -recovery path. Other Weave layers and independent stage switches remain intact. - -Focused coverage: all 256 byte values, all 40,320 source-bit/circle assignments, all -seven whole-plus-part views, direct-constructor validation, exact coordinates, source -occurrence preservation, and removal of the old callable cipher surfaces. These are -construction tests, not claims about attack resistance. Full expected test inventory: -52 existing methods plus 22 byte/star/message methods = 74. - -## Usage guidance - -From `research/weave/`: - -```python -from fractions import Fraction -from stages.eight_circle import Circle, FullKeySet -from stages.message_origin import construct_origin, construct_at - -# Explicit, nonsecret structural fixture: NOT key generation or encryption. -full = FullKeySet( - tuple(Circle(i, f"G{i}", Fraction(i, 9)) for i in range(8)), - (7, 2, 5, 0, 3, 1, 6, 4), -) -origin = construct_origin(b"AA", identity="example-message") -positions = tuple(Fraction(i, 7) for i in range(8)) -byte_state = construct_at(origin, 0, full, positions) -assert byte_state.source_value == 65 -assert full.degenerate(3).indices == (0, 3) -assert len(origin.bytesets) == 2 -``` - -```bash -python -m unittest discover -s tests -p 'test_eight_circle.py' -python -m unittest discover -s tests -p 'test_message_origin.py' -python test.py --receipt /tmp/weave-check.json -``` - -The repair follows the source-preservation, native-first metadata, removal and -verification boundaries of the loaded skill-lib instructions. Skill usage-counter -persistence is not available in this runtime; no exposure count is fabricated. +Use `python cycle.py demo --sources /checkouts` and the commands in `CYCLE.md`. +Do not import the removed bit-per-circle records or restore them as a fallback. ## hmmm -Q11-Q14 and carried-forward Q9 await approval. The concrete forward/private-recovery -operation remains research work, not something created by approving its description. +The native sequence cycle is executable. Public/private key generation and a +computational recovery advantage remain separate construction work. diff --git a/research/weave/IMPLEMENTED.md b/research/weave/IMPLEMENTED.md index beebe0ec..b3facf6a 100644 --- a/research/weave/IMPLEMENTED.md +++ b/research/weave/IMPLEMENTED.md @@ -1,5 +1,11 @@ # Weave — executable provisional stages +**Historical transport/assembly scope.** This document describes the retained +transport experiment, not the current binary sequence cycle. Current executable +construction and ownership are in [CYCLE.md](CYCLE.md); current question +status is in [QUESTIONS.md](QUESTIONS.md). The older unresolved declarations below +apply to that assembly, not to already-implemented parts of the new cycle. + **Delivered:** a five-stage transport experiment, each stage independently switchable, plus a source-pinned native-word adapter. **Not delivered:** a complete native asymmetric Weave cipher. The all-on native profile remains the default and reports unresolved diff --git a/research/weave/NATIVE_BINARY_PROVENANCE.json b/research/weave/NATIVE_BINARY_PROVENANCE.json new file mode 100644 index 00000000..09de2fa7 --- /dev/null +++ b/research/weave/NATIVE_BINARY_PROVENANCE.json @@ -0,0 +1,29 @@ +{ + "schema": "weave.binary-candidate-provenance/v1", + "source_repository": "The-Interdependency/uchc", + "source_commit": "65ac43d5d4cf79082c0836f8545adeb0242e88fc", + "source_path": "binary/uchc_binary/origin.py", + "source_git_blob": "300db7383b403acbff7ce9a94f7536ad047bc78b", + "source_proposal": "The-Interdependency/uchc#7", + "current_owner": "The-Interdependency/stack", + "current_path": "research/weave/native_binary.py", + "standing": "incubating", + "reason": "Premature UCHC placement is withdrawn; existing migration gates require the candidate to remain in the Stack forge. The native UCNS geometry is unchanged.", + "changes": [ + "Complete closed-table validation", + "Fresh verified-buffer module instances, never sys.modules cache reuse", + "Explicit source and authority records" + ], + "authority_transfer": false, + "hmmm": [ + "No domain graduation, public-key secrecy, or authentication is established." + ], + "license": "FSL-1.1-ALv2", + "license_url": "https://github.com/The-Interdependency/uchc/blob/65ac43d5d4cf79082c0836f8545adeb0242e88fc/LICENSE", + "copyright": "Copyright 2026 Erin Spencer", + "notice_url": "https://github.com/The-Interdependency/uchc/blob/65ac43d5d4cf79082c0836f8545adeb0242e88fc/NOTICE", + "license_scope": [ + "native_binary.py", + "tests/test_binary_origin.py" + ] +} diff --git a/research/weave/NUMERAL_CONSTRUCTION.md b/research/weave/NUMERAL_CONSTRUCTION.md new file mode 100644 index 00000000..a99ef972 --- /dev/null +++ b/research/weave/NUMERAL_CONSTRUCTION.md @@ -0,0 +1,166 @@ +# Weave numeral construction v1 + +Status: executable numerical representation and replay. The complete iterative +construction using this layer is now [CYCLE.md](CYCLE.md). Neither layer establishes +public/private encryption or universal compression. + +## Exact objects + +`BitBlock(value, length)` represents ordered bits as an arbitrary-size integer and +its exact bit length. Leading zeros remain part of identity. Byte conversion uses +MSB-first packing; unused low bits in a partial last byte must be zero. + +`Entry(symbol, block, angle, circle, recipe=None)` gives a nonempty block a scoped +Unicode private-use reference and exact supplied attachment. Angles are Fractions +in [0,2) turns and occurrence circles are 1..7. This standalone codec does not +invent native geometry. The cycle supplies attachments from real UCNS objects +through the Stack binary-origin candidate. + +`Packet(origin, round_id, entries, symbols)` contains definitions and ordered +references. The same symbol may denote different blocks in different scopes. +Definitions, source positions implicit in occurrence order, multiplicity, length +and attachment data remain recoverable without the original input. In the cycle, +references are instead organized into native circle order; the surrounding WAF +record carries native coordinates used to recover source order. + +Private-use scalars are aliases, not standardized Unicode numeric values. All +three private-use ranges are admitted. Their actual UTF-8 widths are counted: +three bytes in the BMP range and four in the supplementary ranges. Reference: +https://www.unicode.org/faq/private_use.html + +## Prime-path replay + +`PrimePath(seed, steps)` supports `('next',)` for the prime at the current prime's +index and `('span', base, start, width)` for an embedded prime-valued span in base +2 or 10. Both source and selected values must be prime. Span offsets are zero-based +from the left; source, base, start and width remain in the recipe, including leading +zero spans. Equal destinations do not erase distinct selection occurrences. + +```python +PrimePath(5381, (('span', 10, 0, 2), ('next',), ('next',))).replay() +# (5381, 53, 241, 1523) +``` + +Literal blocks may be composite. A supplied recipe must reproduce its bound integer +exactly; mismatch never silently becomes a literal. Exact trial division and sieving +perform the admitted work. No automatic compact-recipe finder or nineteenth prime +recursion computation is claimed. The cycle scheduler separately executes these +routes to determine interleave splits. + +Default adjustable `Limits`: 64 MiB output, 80 MiB wire, 137468 definitions, +1000000 references, prime index100000, prime value2^32-1, sieve4000000 cells, +256 steps per recipe, and16000000 aggregate prime-work units. Resource refusal +means the chosen execution profile cannot complete that work, not falsification. +Malformed immutable structures raise `Refused`, not `ResourceLimit`. Decode evaluates each recipe once under one charged prime engine. Final structural +validation reuses the values just constructed, without a redundant recipe replay. + +## Self-contained wire and accounting + +WNC plus version byte1 carries scope, round, reconstructed bit length, all +definitions and the actual UTF-8 reference stream. Each definition contains its +scalar, exact angle, circle and bit length, then either packed literal bytes or +an executable prime recipe. Canonical unsigned LEB128 metadata fields are limited +to64 bits; arbitrary-size literal values use their packed bytes instead. + +`accounting(packet)` reports header, definition, occurrence and total bytes. +The decoder rejects malformed UTF-8, noncanonical integers/fractions, undefined +references, duplicate symbols/angles, wrong lengths, truncation, trailing data, +invalid recipes and exceeded budgets. These structural checks are not authentication. + +Observed standalone examples, including all definitions and metadata: + +| Case | Original packed bytes | Complete packet bytes | +|---|---:|---:| +| One 8,388,608-bit literal block | 1,048,576 | 1,048,613 | +| Four references to that block | 4,194,304 | 1,048,622 | +| Recipe ending at1523 in16 bits | 2 | 40 | + +All recover exactly. The repeated large block saves space; the other examples expand. +A short symbol does not erase the reconstruction information it identifies. + +## Usage + +```sh +cd research/weave +python numeral.py demo +python numeral.py bind input.bin block.wnc --origin message-1 --angle 1/7 --circle 3 +python numeral.py recover block.wnc recovered.bin +python numeral.py inspect block.wnc +python -m unittest discover -s tests -p 'test_numeral*.py' -v +``` + +`bind` selects the entire supplied file as one block; automatic repeated-sequence +selection belongs to `affixiation.py`. `recover` reports the exact output bit length; +a partial final byte is right-padded with zeros. Existing output files are never +overwritten. Invalid CLI angles such as1/0 now produce concise status2 refusal. + +The original15 tests cover exhaustive8191 short blocks, large integers, partial-byte +concatenation, all private-use boundaries, exact prime paths, all seven circles, +malformed input, resource limits and source-deleted new-process recovery. Three +review regressions cover aggregate decode work and error classifications. The full +cycle suite retains all18 alongside its native composition tests. + +## hmmm + +Native attachment, automatic sequence discovery, corpus normalization and iterative +interleaving are connected in the separately named cycle profile. Automatic short +prime-recipe discovery and an asymmetric private reconstruction advantage remain +unimplemented. Reverting this codec requires retiring or rebinding its cycle consumer; +no discarded bit-per-circle implementation is a supported fallback. + +## Accounting and CLI work budget + +`accounting(packet, limits)` returns sizes from the same validation that constructs +its wire parts. It does not replay prime recipes a second time merely to obtain +source length. Each numeral CLI `bind`, `inspect`, or `recover` command shares one +charged prime-work engine across its component operations; it does not reset the +allowance between decode, accounting and restore. A command that exhausts its +budget refuses before output publication. Standalone library calls retain their +individual default budgets. The private `_engine` keyword is internal orchestration, +not serialized state or a bypass of validation. + +## Trusted record operations + +Serialization, accounting and reconstruction use the trusted Packet/Entry validation +and BitBlock/PrimePath implementations, not methods attached to supplied instances. +BitBlock fields are rechecked before use. A caller-provided override cannot turn an +invalid entry into an admitted packet or change a literal/recipe during serialization. +This is a data-record boundary, not a sandbox against replacement of the runtime. + + +## Same-budget recipe recovery + +A packet admitted by `encode(packet, limits)` no longer needs a second recipe-work +allowance merely to decode. During `decode`, trusted recipe replay constructs each +bound integer once, with one aggregate engine across definitions. Typed fields, +angles, unique symbols/attachments, references and output length are checked after +parsing without re-executing the recipes. For `PrimePath(101, ())`, encoding and +decoding each consume five units and both succeed at `prime_work=5`; both refuse +at four. Two such five-unit definitions consume ten, not five or twenty. + +This is local reuse of decoder-owned reconstruction, not cached authorization of +mutable data. Ordinary Packet/Entry validation still verifies supplied recipe/value +relations. Altering a decoded object's recipe or block cannot bypass later encoding, +accounting or restoration. Trusted class dispatch is preserved for structural checks. +Separate operations in one CLI call still share their actual total work allowance: +for the five-unit example, inspect uses ten units and recover uses fifteen. + +Run `python -m unittest discover -s tests -p test_numeral_replay.py -v` for four +focused witnesses. The binary wire format, recipe algebra and native dependencies +are unchanged. Successful reconstruction is not cryptographic decryption. + + +## Cheap admission before recipe work + +The decoder first parses deferred recipe definitions and literal blocks and +validates scope, exact attachments, nonempty lengths, unique symbols/angles, +all recipe shapes, occurrence references, total length and complete framing. +Only an admitted structure reaches prime evaluation. Recipe results must then fit +their declared lengths, with one evaluation per definition under the shared engine. +No metadata-invalid packet is mislabeled as a prime-work shortage merely because +its configured prime budget is small. Mathematical validity that depends on a +computed prime path still requires actual evaluation. + +The existing same-budget replay guarantee and post-decode mutation checks remain. +See `tests/test_admission_order.py` for early/late malformed input and unchanged +valid reconstruction witnesses. No wire format or native-source dependency changes. diff --git a/research/weave/QUESTIONS.md b/research/weave/QUESTIONS.md index 30a04f0e..f076a1e1 100644 --- a/research/weave/QUESTIONS.md +++ b/research/weave/QUESTIONS.md @@ -1,109 +1,61 @@ -# Weave: current questions and proposed answers +# Weave: current decisions and remaining questions -**Approval status: PENDING.** The proposed answers below are not implemented defaults. -The byte/star repair is authorized separately; it does not approve a new cipher. -Existing Q1-Q10 identities remain in the disposition register below. +The latest sequence/occurrence clarification controls the construction. Earlier +bit-per-circle and fixed-twofold-output assumptions are superseded, not pending +approval. The executable candidate cycle is documented in [CYCLE.md](CYCLE.md). -## Settled; do not ask again +## Established sequence -- The input is raw bytes, not Unicode or an English-word prerequisite. -- Each message is an origin; each byte has eight bits and an eight-circle construction. -- One big circle plus seven small circles remains eight. The big circle participates - dynamically and carries a bit; it is not merely an external reference. -- Public degeneration uses two circles at a time. One is always the big circle. - The earlier four-circle proposal and arbitrary/disjoint pairings are superseded. -- Every circle uses the complete 720-degree return. -- Circle-space relationships vary per key set; do not impose a universal relation. -- The stated ciphertext expansion is twofold. Do not obtain it by copying or - independently coding each bit twice. -- UCNS geometry and UCHC origin-axis relations retain their respective authorities. +Normalize message bit length with actual corpus material once. Parse repeated +multi-byte sequences, save each selected sequence at an angle on the eighth/origin +circle, and record occurrences and order on a circle among the seven. First/last +interleave the resulting bits. Repeat affixiation and interleaving; reverse in the +opposite order. Preserve residual material and each round's reconstruction information. -## Next question set for approval +The message is the origin; sequential byte occurrences are axes. Equal byte values +remain separate occurrences. UCNS owns geometry. Stack/Weave owns this binary-domain closure candidate, +selection, scheduling and cycle composition. UCHC supplies the origin/axis +architecture and migration boundary; no binary domain has graduated there. -### Q11 — Is the placement operation relocating values or changing them? +## Implemented candidate answers -**Probable answer:** Preserve each bit's value and occurrence identity in the -positional layer; transform its place and its relationships as part of the complete -byte. The ciphertext encodes the resulting relation jointly. Do not install a -separate two-symbol substitution for each bit. +These are explicit, versioned implementation choices, not newly inferred universal +laws or hidden approvals: corpus-tail bit-offset bucket filling; maximal-LCP +longest-first nonoverlapping sequence selection; first-occurrence native whole-circle +attachment; profile-defined occurrence-circle order and eight space offsets; exact +prime-route ranked-composition splits; and an explicit first-last end order. -**Still to construct:** the actual joint placement and its exact inverse. This answer -does not prohibit a separately authorized transformation in another Weave layer. +The sample profile can run without further policy decisions. Its complete forward +and inverse, failures, budgets and size accounting are in `CYCLE.md`. -### Q12 — What information does the public whole-plus-one pair retain? - -**Probable answer:** A forward positional relation derived from the complete -eight-circle configuration, with the inverse-completing relationships omitted. It is -not merely a raw copy of two otherwise independent private circle records. The sender -can operate with its public object; the full private configuration supplies recovery. - -**Still to construct:** a concrete degeneration and recovery operation. Reduced key -exposure is not itself evidence of computational difficulty or strongest security. - -### Q13 — What carries forward between successive byte constructions? - -**Probable answer:** The key-set geometry and an evolving message-scoped relational -state carry forward. Each byte keeps its own occurrence identity and eight placements. -G0 participates in each relation; the active small circle is selected within that -key-defined evolution, not by an added universal schedule, fixed four-pair partition, -or a rule that emits two public bits for every source bit. - -**Still to construct:** a deterministic forward/recovery state transition that uses -the established native relations. No numerical feedback formula is selected here. - -### Q14 — What do the additional eight ciphertext bits represent? - -**Probable answer:** One joint 16-bit relational codeword for the complete source -byte, rather than eight independent two-bit codes. Its additional capacity carries -recoverable placement/relation information; exact circle positions may be reconstructed -through the key rather than dumped as arbitrary coordinates into the wire format. - -**Still to construct:** the actual 16-bit mapping, its length accounting and unique -recovery. No field split, extra header, or claim of two possible lifts is assumed. -Two public circles alone do not mathematically force twofold expansion. - -### Q9 — Should identical messages under one key repeat the same trajectory? - -**Probable answer, carried forward:** No. Fresh per-message input should participate -in the origin/placement relation, and authorized recovery must obtain its needed -state. Any transmitted origin metadata must be accounted for within the agreed -length policy, not silently added after claiming exact twofold expansion. - -**Still to construct:** a concrete native variation mechanism and recovery path. -A namespace called `O_M`, a counter, or a public nonce alone is not a secrecy result. - -## Earlier questions: preserved disposition +## Preserved question identities | ID | Current disposition | |---|---| -| Q1 — Native plaintext admission | Raw-byte, byte-scoped shape is settled. This repair constructs occurrence/placement records, not native encryption. No text database is a prerequisite. Actual geometric transformation and serialization remain Q11/Q14. | -| Q2 — Private gonol/public counterpart | Eight full circles and G0-plus-one public shape are settled. The actual public/private transformation is Q12. | -| Q3 — Threads | Preserve complementary native contributions, source occurrences and cross-thread relationships. Existing lane routing is a transport trial, not the native relation. | -| Q4 — Corpus | Preserve actual selected material and its construction/recovery role. The existing left/right traversal formula remains an assistant-selected transport trial; native attachment is not settled by it. | -| Q5 — Join/final operation | Preserve explicit route execution and the literal final last/first interleave. The demonstrated cyclic join is not a private route generator or a new universal rule. | -| Q6 — Arity composition/remainders | Existing experiment uses sequential passes and quotient/remainder sections, preserving order, repetitions and empty sections. Keep its proposed policy separate from user-ratified general design. | -| Q7 — Sender boundary | Intended public-side sender must not require recipient private state. API separation alone is not a mathematical asymmetric construction. | -| Q8 — Control derivation | Key/message relation and inverse planning must provide controls before they are needed. No private encoder trace is silently supplied as ciphertext or an inverse plan. See Q12/Q13. | -| Q9 — Repeated-message variation | Carried forward above; the earlier proposed answer remains pending, not newly approved. | -| Q10 — Integrity/length/replay | Remains a distinct unresolved mechanism. Authentication is independently switchable and is not silently made a replacement for Weave or a newly mandatory layer by this repair. | - -## Removed premises - -The PR #73 per-bit sheet code and PR #74 bit-axis feedback code were specification -mismatches. Their attacks do not adjudicate the requested construction. The premise -that changing private-only data must change ciphertext while every sender input is -held fixed is also removed; it is not a valid private-recovery-dependence test. -See [EIGHT_CIRCLE.md](EIGHT_CIRCLE.md) for exact historical identities and repair scope. - -## Usage guidance - -Approve or amend by ID, for example `Q11 approve; Q12: ...`. -Approved descriptions guide construction; they do not constitute an implemented -algorithm, a proof, or permission to silently fill missing mathematics with a substitute. -No additional decision about a particular degree, space offset, or universal circle -visitation order is required merely to retain the repaired structure. +| Q1 — Native admission | Exact raw byte occurrences use the Stack binary-origin candidate and native UCNS axes. No text database prerequisite. | +| Q2 — Private gonol/public counterpart | Intended full configuration and whole-plus-one exposure retained; asymmetric derivation is not implemented by positional recovery. | +| Q3 — Threads | Seven occurrence-circle streams participate in current native recovery. No automatic equivalence to all historical complementary cryptographic-thread proposals is asserted. | +| Q4 — Corpus | Actual corpus bits normalize once in the explicit bucket profile and are verified on reverse. Earlier traversal trial is not substituted for this cycle. | +| Q5 — Join/final operation | The new iterative profile serializes native occurrence streams then interleaves each round. Earlier standalone transport joining and optional final whole pass retain their distinct historical scope. | +| Q6 — Arity/remainders | Executed prime routes determine ranked nonempty compositions of each current bitstream. The sample selects among 3/5/7; no universal schedule or round count is imposed. | +| Q7 — Sender boundary | Genuine asymmetric mode still requires a public-only sender and recipient private recovery relation. Current cycle uses the same supplied profile/material. | +| Q8 — Control derivation | The implemented round schedule is regenerated from its prime routes and each available intermediate length; no plaintext trace supplies inverse controls. This is not a private trapdoor. | +| Q9 — Repeated-message variation | Remains an unimplemented cryptographic design objective. Identical inputs/profile/material produce identical construction records in this deterministic research profile. | +| Q10 — Integrity/length/replay | Full byte accounting is implemented. Structural/source mismatch refusal is not authentication; authentication and replay remain separate unimplemented mechanisms. | +| Q11 — Bit placement/value | Superseded as a prerequisite: the corrected unit is a repeated multi-byte sequence with ordered occurrences, not independently positioned source bits. | +| Q12 — Public whole-plus-one relation | Still an asymmetric research boundary. The current self-contained positional record does not establish secret reconstruction. | +| Q13 — Eight placements per byte | Superseded. One retained message-origin and separate round-specific byte-axis frames now support the sequence cycle. | +| Q14 — Additional eight ciphertext bits | Superseded. No 16-bit-per-byte mapping or fixed 2x expansion follows from this construction; all records are counted. | + +## Usage + +Run `python cycle.py demo --sources /checkouts`. Change supported operating choices +through an explicit profile; do not silently change a named profile's meaning. +The current next mathematical question is the private/public relation, not another +request to decide what a byte means or where an arbitrary circle must be placed. ## hmmm -The actual positional cipher remains unimplemented. These are the next proposed -construction answers, not another round of declarations that it already works. +Short prime-recipe discovery, private/public asymmetry, meaningful secret key-space +analysis, authentication/replay and security evidence remain unfinished. Passing +construction/recovery tests does not settle those questions. diff --git a/research/weave/README.md b/research/weave/README.md index c27e0702..aa5e6eb5 100644 --- a/research/weave/README.md +++ b/research/weave/README.md @@ -1,138 +1,62 @@ # Weave -Standing: **stack-local research; specification-first; no security claim**. +Standing: **Stack-owned construction research; no established encryption security.** -[Executed source-bound results](REPORT.md) include literal-operation recovery, -schedule equivalences and a fixed-map attack. The complete design remains untested; -these results are not a whole-system verdict. +Weave preserves the intended system: native gonol/origin construction, private-gonol +recovery, reconstruction-related streams, selected corpus material, ordered +multi-arity bit interleaving, and an eventual asymmetric public/private relation. +The interleave is one operation, not the project identity. URPCS remains retired; +its substituted codec, implementation and security findings are not inherited. -Weave is the replacement research workspace for Erin Spencer's intended encryption -system after retirement of the substituted URPCS implementation. +## Current executable cycle -The system is **not** merely an interleaving permutation. The preserved architecture -contains multiple cooperating layers. Development may stage those layers for testing, -but no layer may be silently discarded or replaced by a familiar construction. +Normalize once with actual corpus bits. Discover repeated multi-byte sequences, +retain each sequence at an attachment on the eighth/whole circle and its ordered +occurrences among the other seven. Serialize exact numeral references and native +positions, derive bit divisions from executed prime-index and embedded-span paths, +first/last interleave, and repeat. Reverse from the final record, corpus, profile +and exact source dependencies without the original input or encoder trace. -## Preserved architecture +[The cycle contract](CYCLE.md) records the explicit candidate choices, all-cost +accounting, budgets and exact source lock. These choices are not universal laws. +[The numeral layer](NUMERAL_CONSTRUCTION.md) separately records length-bearing +integers, Unicode references and exact prime recipes. -### 1. Hyperspace / gonol plaintext construction +`native_binary.py` is the Stack-owned binary-origin/sequence candidate, consuming +actual source-verified UCNS `AxisCirclePosition` and `NativeMobiusState` objects. +UCHC supplies the origin/axis architecture reference; the premature UCHC #7 +implementation is retained only as provenance. Domain graduation has not occurred. +The eighth remains the whole/origin, not one bit of a byte. Old bit-per-circle +records and their misleading attack interpretations remain removed. -Plaintext is intended to be processed into a hyperspace gonol construction rather than -treated only as an opaque byte string. The information is represented as a nested gonol -set before or as part of encryption. +## Usage -Recovery is intended to require a **private gonol** associated with the private key. -The exact reversible binding and key representation remain unresolved and must be -specified rather than invented. +Python 3.12+ and the standard library, with the exact UCNS source in `/checkouts/ucns`: -### 2. Multiple threads - -The encrypted construction uses multiple threads/data streams, with arity at least -three and contemplated thread counts including three, five, seven, or more. - -Each thread must contribute to reconstruction. A single independently useful stream is -not the intended construction. - -### 3. Thread-associated corpus/material - -Threads may be associated with user-selected corpus/material such as literary works, -music, technical manuals, sounds, or other chosen source material. - -The corpus/material is intended to participate in reconstruction as semi-secret -context, not merely as documentation or a label. Its exact derivation and binding rule -remain to be frozen. - -### 4. Multi-arity bit interleaving - -The bit transform is one load-bearing layer inside Weave. - -For an explicitly declared ordered sequence of division arities: - -1. divide the working bit sequence into the declared number of sections; -2. within each section interleave inward from opposite ends: - last bit, first bit, next-to-last bit, next-from-first bit, and so on; -3. repeat for the declared arity sequence, examples already given including fifths, - sevenths, and thirds; -4. after the declared levels, interleave the resulting whole sequence in the same - opposite-end manner. - -Stage count is distinct from arity. “Arity three, minimum” does not establish a -minimum of three stages; the unsupported assistant-added restriction is removed. - -Knowing the division choices, their order, and the number of levels is part of the -reconstruction problem unless an explicit later law changes that role. Different -schedule descriptions can induce the same map; measure equivalence rather than -assuming schedule uniqueness. - -### 5. Keying / recovery structure - -The intended system ultimately requires asymmetric recovery structure with no silent -dependency on an external cryptographic system. The private side is intended to include -the private gonol and the structural information needed to reverse the construction. - -The exact public/private derivation law is not yet specified. That absence is `hmmm`, -not permission to substitute RSA, ECC, Diffie-Hellman, a conventional KEM, or URPCS. - -## Non-inheritance boundary - -`research/urpcs/` is historical evidence of a substituted GPT-produced construction. -No URPCS law, codec, test, result, or security conclusion is an implementation -dependency of Weave. - -URPCS may be consulted only as a specification-divergence witness: an example of what -must not happen again. - -## Development discipline - -The full architecture is preserved from the start, while implementation proceeds in -separable layers so each contribution can be falsified. The order listed below is an -assistant proposal, not a user-selected dependency law; actual dependencies must come -from the complete source-backed construction. - -1. Freeze the complete dataflow and the inverse dependencies among gonol construction, - threads, corpus/material, interleaving, and key structure. -2. Specify exact bit-level interleaving and inverse rules without changing the mechanism. -3. Specify thread formation and recombination. -4. Specify corpus/material derivation and binding. -5. Specify hyperspace/private-gonol construction and recovery. -6. Specify the asymmetric public/private relation. -7. Implement the smallest complete round-trip profile containing every required layer. -8. Build an independently structured decoder/recovery implementation from the written - contract. -9. Perform adversarial analysis before making any confidentiality or production claim. - -A reduced test harness may isolate one layer, but results from a reduced harness may not -be presented as results for Weave as a whole. - -## Usage guidance - -Start here: - -```bash -cat research/weave/SPECIFICATION.md -cat research/weave/BASE.json -``` - -Reproduce the executed component evidence with Python's standard library only: - -```bash +```sh cd research/weave -python probe.py > receipt.json -python probe.py --check receipt.json +WEAVE_SOURCES=/checkouts python cycle.py demo +python cycle.py forward input.bin output.wvc --corpus corpus.bin --sources /checkouts +python cycle.py reverse output.wvc recovered.bin --corpus corpus.bin --sources /checkouts +WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-check.json ``` -See [PLAN.md](PLAN.md) for the pre-execution scope and [REPORT.md](REPORT.md) for -results, mathematical derivations, exact assumptions and the expected receipt digest. +`CYCLE_NATIVE.json` binds the actual source blobs. The runnable bundle includes the +minimal exact UCNS files under `sources/`. Existing files are never overwritten; +ordinary write/flush/close failures leave no partial destination. The output is +an experimental representation, not safe storage for secrets. + +## Evidence boundaries -Do not use Weave to protect real secrets until an explicit security contract and -adversarial evidence justify that use. +`SPECIFICATION.md` preserves the full intended system and latest clarification. +`QUESTIONS.md` preserves question identities and distinguishes implemented candidate +answers from unfinished cryptography. `EIGHT_CIRCLE.md` is historical correction. +`ASSEMBLY.md`, `IMPLEMENTED.md`, `PLAN.md` and `REPORT.md` retain the earlier transport +experiment under its original scope. Its fixed-map attacks are not full-system verdicts. ## hmmm -Uneven section partition selection; exact ordering/composition of arity stages; thread -formation; corpus/material derivation; hyperspace/gonol encoding; private-gonol binding; -public/private key derivation; authentication, nonce/state, replay behavior; and the -threat model remain unresolved. They are preserved as required design boundaries, not -optional features and not invitations for model substitution. The original pre-substitution -thirteen-law conversation was not recovered; a missing retrieved source is not evidence -that the user never supplied the relation. +Actual private/public key derivation and recovery advantage, authentication/replay, +secret-key-space analysis, automatic compact prime-recipe discovery, and security +of the completed cryptosystem remain unfinished. Exact reconstruction and real +geometry do not themselves establish confidentiality or a fixed expansion ratio. diff --git a/research/weave/SPECIFICATION.md b/research/weave/SPECIFICATION.md index c1e5d014..db347b53 100644 --- a/research/weave/SPECIFICATION.md +++ b/research/weave/SPECIFICATION.md @@ -1,6 +1,11 @@ # Weave — preserved architecture and specification floor -Status: **PRE-SPECIFICATION / FULL-ARCHITECTURE PRESERVATION**. +Status: **CORRECTED SEQUENCE CYCLE IMPLEMENTED AS AN EXPLICIT PROFILE; ASYMMETRY UNIMPLEMENTED**. + +The latest sequence/occurrence clarification in section 5.1 controls over older +bit-per-circle interpretations. [CYCLE.md](CYCLE.md) records the executable native +normalization/affixiation/interleave cycle and all candidate choices. Other historical +layers below remain preserved objectives, not automatic claims about that profile. This document records the construction that must survive implementation. It separates fixed architecture from unresolved mechanics. An unresolved mechanic must remain @@ -32,7 +37,8 @@ These layers are analytically separable but belong to one intended cryptosystem. Let `S = s_0, s_1, ..., s_(n-1)`. -Define the preserved end-interleave ordering: +The historical transport experiment retains this last-first ordering. The current +cycle selects `first-last` explicitly in its profile and also supports `last-first`: ```text I(S) = s_(n-1), s_0, s_(n-2), s_1, ... @@ -77,7 +83,9 @@ no one thread == complete recoverable plaintext complete recovery requires the declared thread relation ``` -The exact split/recombination law remains unresolved. +The current cycle constructs seven native occurrence-circle streams and exactly +recombines their sequence positions. Their equivalence to every earlier +cryptographic-thread proposal is not asserted; that broader relation remains open. ## 4. Corpus/material participation @@ -87,7 +95,9 @@ contemplated include literary, musical, technical-manual, and sound material. The material is intended to participate in construction/recovery. Treating it as an unused label or merely hashing its filename does not satisfy this architecture. -Exact extraction, addressing, mixing, and recovery dependence remain unresolved. +The current cycle implements actual cyclic corpus-bit extraction at a configured +bit offset, bucket normalization, and exact reverse checks. The broader private +corpus/key binding remains unimplemented. ## 5. Hyperspace / gonol layer @@ -111,26 +121,38 @@ recovered plaintext must be specified explicitly before implementation can claim this layer. -## 5.1. Current byte/whole-part clarification - -The current raw-byte construction is message-scoped and byte-based: each message -is an origin, each byte has eight bits, and one big circle G0 plus seven small -circles G1..G7 supplies the byte's eight-circle structure. One bit participates -through each circle. G0 also remains the dynamic whole; it is not merely a -reference or checksum. Every public two-circle view is (G0, Gi), where 1 <= i <= 7. -Do not replace this with four disjoint pairs or two public sheet bits per source bit. - -Each circle has the complete 720-degree return and key-set-specific relationships -to space. The stated output expansion is 16 ciphertext bits per source byte; the -actual serialization and source of that expansion remain to be constructed. -No universal space-placement or active-small-circle schedule is imposed. - -[EIGHT_CIRCLE.md](EIGHT_CIRCLE.md) records the repair of PRs #73/#74 and its precise -implementation boundary. The current code constructs byte/placement records and -whole-plus-one key views, not a complete native positional cipher. The removed -per-bit encoders and public-feedback recurrence were specification mismatches; -their attacks do not falsify Weave. [QUESTIONS.md](QUESTIONS.md) contains the next -proposed answers for approval. All previously declared system layers remain. +## 5.1. Current sequence/occurrence and numeral clarification + +The latest user clarification supersedes the earlier one-bit-per-circle reading: +normalize message bit length with corpus material; parse repeated multi-byte +sequences; save each selected sequence at an angle on the eighth/origin circle; +a circle among the seven records its occurrence count and order; first/last +interleave the resulting bitstream; repeat parsing/affixiation and interleaving. +The message remains the origin and sequential byte occurrences remain individually +addressable. A saved multi-byte sequence is not reduced to a single source bit. + +The current numerical extension interprets an exact selected bit block as an +integer with its bit length preserved, then gives it a scoped Unicode reference. +A reference resolves to stored literal construction or an executable prime path. +Prime-index continuation and embedded prime spans preserve route and occurrence +information. [NUMERAL_CONSTRUCTION.md](NUMERAL_CONSTRUCTION.md) records its +implemented boundary, full size accounting, exact replay, and usage. + +The numeral module remains a representation layer, not replacement geometry. +The separate cycle now connects automatic repeated-sequence discovery, actual +corpus normalization, Stack-owned sequence closure using native UCNS geometry and coordinate recovery, +prime-route split derivation, and the repeated first/last bit interleave. See +[CYCLE.md](CYCLE.md) for the exact named profile, inverse, tests and byte accounting. +This implements the corrected sequence cycle, not public-key encryption. +No fixed total ciphertext expansion is inferred from a short Unicode reference. +The earlier twofold-length proposal requires accounting over the completed cycle. + +The existing 720-degree return and key-set-specific space relationships remain +preserved. Earlier whole-plus-one public-view research is not promoted into an +asymmetric result by this codec. [EIGHT_CIRCLE.md](EIGHT_CIRCLE.md) records the +prior byte/star repair; its bit-per-circle interpretation is historical and its obsolete modules/tests +are removed in favor of the native sequence cycle. No discarded PR #73/#74 +encoder or attack is revived. All other declared system layers remain. ## 6. Asymmetric relation @@ -210,14 +232,16 @@ python probe.py --check receipt.json ## hmmm -1. Uneven partition selection rule; the component runner accepts explicit positive - section lengths without selecting a remainder policy. -2. Whether each arity stage consumes the preceding stage output or composes independent - partitions before a later merge. The runner's sequential choice is an explicitly - labelled experiment, not a newly inferred user law. -3. Exact thread split/recombine law. -4. Corpus/material extraction and binding law. -5. Nested gonol representation and reversible serialization. +1. Prime-route ranked compositions implement uneven partitions for the named cycle + profile; other determinant profiles remain open. +2. The current cycle consumes preceding-round output, as clarified. Historical + alternatives are not current prerequisites or silently selected rules. +3. Seven native occurrence streams reconstruct exactly in the current profile; + broader cryptographic-thread dependence remains unestablished. +4. Corpus-tail normalization is implemented as an explicit profile; broader + cryptographic corpus binding remains open. +5. The native binary sequence profile now supplies scoped origin/axis closure and + reversible serialization; general nested-gonol and private-key use remain separate. 6. Private-gonol generation and binding. 7. Public/private key derivation and the exact source of asymmetry. 8. Authentication, nonces, state evolution, truncation/replay handling. diff --git a/research/weave/affixiation.py b/research/weave/affixiation.py new file mode 100644 index 00000000..047ae47a --- /dev/null +++ b/research/weave/affixiation.py @@ -0,0 +1,258 @@ +# === MODULE_BUILD === +# id: weave_sequence_discovery +# module_name: affixiation +# module_kind: engine +# summary: deterministic repeated-multibyte discovery and exact nonoverlapping partition for the Weave sequence cycle +# owner: Erin Spencer +# public_surface: Partition, discover +# internal_surface: suffix array, LCP intervals, occurrence selection +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_cycle.py +# rollout: explicitly selected maximal-lcp-longest-first-v1 profile +# rollback: remove this module and its round-runner binding +# unresolved: selection is a declared executable profile, not optimal compression or a universal affixiation rule +# === END MODULE_BUILD === +# === CONTRACTS === +# id: discovery_exact_partition +# given: an admitted byte stream +# then: selected repeated sequences and residual literals partition every source byte exactly once in order +# id: discovery_repeat_selection +# given: competing and overlapping repeated multi-byte candidates +# then: deterministic longest-first selection records nonoverlapping occurrences without erasing residual data +# id: discovery_resource_refusal +# given: input or candidate-visit work exceeding the configured budget +# then: refuse instead of silently truncating the candidate search +# id: discovery_closure_authority +# given: sequence participants selected by this module +# then: closure remains Stack-owned, consuming UCNS geometry; UCHC is the architecture reference +# === END CONTRACTS === +"""Usage: partition = discover(raw_bytes); partition.restore() == raw_bytes. + +This explicit profile selects maximal LCP candidates, longest first, breaking ties +by earliest source occurrence. When overlaps compete it retains leftmost available +nonoverlapping occurrences. Occurrence counts mean selected partition occurrences, +not every possible overlapping substring match. No maximum sequence-length heuristic +or fixed byte chunks are imposed. Residual runs remain exact literal definitions. +This module discovers participants. Stack-owned native_binary.py closes them +using native UCNS geometry; UCHC supplies the origin/axis architecture reference. +""" +from __future__ import annotations + +from dataclasses import dataclass, field +from numeral import Refused, ResourceLimit + +PROFILE = 'maximal-lcp-longest-first-v1' + + +@dataclass(frozen=True) +class Partition: + blocks: tuple[bytes, ...] = field(repr=False) + order: tuple[int, ...] + starts: tuple[int, ...] + candidate_visits: int + + def restore(self) -> bytes: + return b''.join(self.blocks[i] for i in self.order) + + @property + def repeat_ids(self) -> tuple[int, ...]: + counts = [0] * len(self.blocks) + for i in self.order: + counts[i] += 1 + return tuple(i for i, b in enumerate(self.blocks) if len(b) >= 2 and counts[i] >= 2) + + +def _suffix_array(data: bytes) -> list[int]: + n = len(data) + suffixes, ranks, width = list(range(n)), list(data), 1 + while width < n: + suffixes.sort(key=lambda i: (ranks[i], ranks[i+width] if i+width < n else -1)) + next_ranks = [0] * n + for j in range(1, n): + a, b = suffixes[j-1], suffixes[j] + different = ((ranks[a], ranks[a+width] if a+width < n else -1) != + (ranks[b], ranks[b+width] if b+width < n else -1)) + next_ranks[b] = next_ranks[a] + int(different) + ranks = next_ranks + if ranks[suffixes[-1]] == n-1: + break + width *= 2 + return suffixes + + +def _lcp(data: bytes, suffixes: list[int]) -> list[int]: + n, common = len(data), 0 + rank, values = [0]*n, [0]*n + for i, pos in enumerate(suffixes): + rank[pos] = i + for start in range(n): + i = rank[start] + if i == 0: + common = 0 + continue + other = suffixes[i-1] + while start+common < n and other+common < n and data[start+common] == data[other+common]: + common += 1 + values[i] = common + common = max(0, common-1) + return values + + +class _Ranges: + """Range min/max over suffix starts, with linear storage.""" + def __init__(self, values: list[int]): + self.size = 1 << max(0, (len(values)-1).bit_length()) + self.low = [len(values)] * (2*self.size) + self.high = [-1] * (2*self.size) + self.low[self.size:self.size+len(values)] = values + self.high[self.size:self.size+len(values)] = values + for i in range(self.size-1, 0, -1): + self.low[i] = min(self.low[2*i], self.low[2*i+1]) + self.high[i] = max(self.high[2*i], self.high[2*i+1]) + + def query(self, start: int, end: int) -> tuple[int, int]: + low, high = self.size, -1 + start, end = start+self.size, end+self.size + while start < end: + if start & 1: + low, high = min(low, self.low[start]), max(high, self.high[start]) + start += 1 + if end & 1: + end -= 1 + low, high = min(low, self.low[end]), max(high, self.high[end]) + start //= 2 + end //= 2 + return low, high + + +def _matching_interval(common: _Ranges, begin: int, end: int, + length: int, size: int) -> tuple[int, int]: + """Expand a suffix interval to all occurrences of its shortened prefix. + + Adjacent suffixes share a length-L prefix exactly when all intervening LCP + values are at least L. Range minima and binary search retain linear storage + and avoid copying/enumerating long prefixes to locate their full interval. + """ + low, high = 0, begin + while low < high: + middle = (low+high)//2 + if common.query(middle+1, begin+1)[0] >= length: + high = middle + else: + low = middle+1 + expanded_begin = low + low, high = end, size + while low < high: + middle = (low+high+1)//2 + if common.query(end, middle)[0] >= length: + low = middle + else: + high = middle-1 + return expanded_begin, low + + +class _Occupied: + def __init__(self, size: int): + self.tree = [0] * (size+1) + + def prefix(self, end: int) -> int: + total = 0 + while end: + total += self.tree[end] + end -= end & -end + return total + + def free(self, start: int, length: int) -> bool: + return self.prefix(start+length) == self.prefix(start) + + def mark(self, start: int, length: int) -> None: + # Each source byte is marked at most once during a complete run. + for pos in range(start+1, start+length+1): + while pos < len(self.tree): + self.tree[pos] += 1 + pos += pos & -pos + + +def discover(data: bytes, *, max_bytes: int = 1048576, + visit_budget: int = 16000000) -> Partition: + """Discover a complete deterministic partition or refuse its resource profile.""" + if type(data) is not bytes: + raise Refused('byte-aligned source required for sequence discovery') + if any(type(v) is not int or v < 1 for v in (max_bytes, visit_budget)): + raise Refused('positive integer discovery budgets required') + if len(data) > max_bytes: + raise ResourceLimit('sequence source exceeds discovery byte budget') + n = len(data) + if n == 0: + return Partition((), (), (), 0) + suffixes = _suffix_array(data) + common = _lcp(data, suffixes) + ranges = _Ranges(suffixes) + common_ranges = _Ranges(common) + stack, candidates = [], set() + for i in range(1, n+1): + depth = common[i] if i < n else 0 + left = i-1 + while stack and stack[-1][0] > depth: + length, begin = stack.pop() + first, last = ranges.query(begin, i) + capped = min(length, last-first) # At least two disjoint occurrences. + if capped >= 2: + start, end = begin, i + if capped != length: + start, end = _matching_interval(common_ranges, begin, i, capped, n) + first, _ = ranges.query(start, end) + candidates.add((-capped, first, start, end)) + left = begin + if depth and (not stack or stack[-1][0] < depth): + stack.append((depth, left)) + candidates = sorted(candidates) + occupied = _Occupied(n) + chosen, patterns = [], set() + covered = visits = 0 + for negative, first, begin, end in candidates: + length = -negative + if n-covered < 2*length: + continue + visits += end-begin + if visits > visit_budget: + raise ResourceLimit('candidate occurrence visits exceed discovery budget') + selected, previous_end = [], -1 + for start in sorted(suffixes[begin:end]): + if start >= previous_end and occupied.free(start, length): + selected.append(start) + previous_end = start+length + if len(selected) < 2: + continue + block = data[selected[0]:selected[0]+length] + if block in patterns: + continue + patterns.add(block) + for start in selected: + chosen.append((start, length)) + occupied.mark(start, length) + covered += length + if covered == n: + break + chosen.sort() + segments, offset = [], 0 + for start, length in chosen: + if start > offset: + segments.append((offset, start-offset)) + segments.append((start, length)) + offset = start+length + if offset < n: + segments.append((offset, n-offset)) + blocks, order, starts, ids = [], [], [], {} + for start, length in segments: + block = data[start:start+length] + if block not in ids: + ids[block] = len(blocks) + blocks.append(block) + order.append(ids[block]) + starts.append(start) + return Partition(tuple(blocks), tuple(order), tuple(starts), visits) diff --git a/research/weave/cycle.py b/research/weave/cycle.py new file mode 100644 index 00000000..9730d605 --- /dev/null +++ b/research/weave/cycle.py @@ -0,0 +1,533 @@ +# === MODULE_BUILD === +# id: weave_iterative_sequence_cycle +# module_name: cycle +# module_kind: engine +# summary: corpus normalization once followed by native repeated-sequence affixiation and prime-determined bit interleaving, with exact reverse recovery +# owner: Erin Spencer +# public_surface: CycleLimits, RoundSpec, Profile, normalize, denormalize, affix, unaffix, forward, reverse, main +# internal_surface: versioned research records and complete size accounting +# auth_boundary: none +# storage_boundary: write +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_cycle.py +# rollout: explicit research cycle API and CLI; no full-profile cipher promotion +# rollback: remove cycle, profile, tests, native binding and workflow additions +# requires: weave_numeral_construction, weave_sequence_discovery, weave_prime_schedule, weave_native_binary_binding +# unresolved: private/public key generation and cryptographic security; automatic short prime-recipe search +# === END MODULE_BUILD === +# === CONTRACTS === +# id: cycle_normalize_once +# given: exact message bytes and actual corpus bytes +# then: normalize once into a corpus-filled bit-length bucket with recoverable original length +# id: cycle_reversible_rounds +# given: an admitted profile with native inputs +# then: reverse every round from the final record, corpus and profile without the original message or encoder trace +# id: cycle_native_occurrence_recovery +# given: seven occurrence-circle streams and their native complete positions +# then: inverse native displacement restores ordered byte-sequence placement at the retained message-origin +# id: cycle_complete_accounting +# given: a complete cycle record +# then: count normalization, dictionaries, references, coordinates and outer framing without asserting a fixed expansion +# id: cycle_strict_refusal +# given: malformed records, mismatched configuration/material, or exhausted budgets +# then: refuse clearly without overwrite, partial recovery or silent truncation +# id: cycle_discovery_profile +# given: a decoded record claiming the selected deterministic discovery profile +# then: its definitions and occurrence order equal discovery on the restored bytes or recovery refuses +# === END CONTRACTS === +"""Usage: python cycle.py demo --sources /checkouts + python cycle.py forward INPUT OUTPUT --profile cycle-profile.json --corpus CORPUS --sources /checkouts + python cycle.py reverse INPUT OUTPUT --profile cycle-profile.json --corpus CORPUS --sources /checkouts + +/checkouts contains the source-locked ucns/ tree; CYCLE_NATIVE.json names +exact modules. No packages beyond Python's standard library are required by this +consumer. The runnable profile is explicit construction research, not secure storage. +The newest sequence cycle replaces the historical one-bit-per-circle interpretation. +""" +from __future__ import annotations + +import argparse +from collections import Counter +from dataclasses import dataclass, field +from fractions import Fraction +from hashlib import sha256 +from pathlib import Path +import json +from safe_output import write_new +import os + +from numeral import (BitBlock, Entry, Packet, PrimePath, Limits, Refused, ResourceLimit, + encode as encode_numeral, decode as decode_numeral, + accounting, _Reader, _uint, _blob, _Primes) +from affixiation import discover, PROFILE as SELECTION_PROFILE +from prime_schedule import Route, plan, interleave, PROFILE as SPLIT_PROFILE +from cycle_native import load_native + +ROOT = Path(__file__).resolve().parent +MAGIC, AFFIX_MAGIC, NORMAL_MAGIC = b'WVC\x01', b'WAF\x01', b'WNM\x01' +PROFILE_SCHEMA = 'weave.sequence-cycle-profile/v1' + + +def canonical(obj) -> bytes: + return json.dumps(obj, sort_keys=True, separators=(',', ':'), + ensure_ascii=False, allow_nan=False).encode('utf-8') + + +def strict_json(data: bytes): + def pairs(items): + result = {} + for key,value in items: + if key in result: + raise Refused('duplicate JSON key') + result[key] = value + return result + try: + return json.loads(data.decode('utf-8'), object_pairs_hook=pairs, + parse_constant=lambda s: (_ for _ in ()).throw(Refused('nonfinite JSON'))) + except (UnicodeError, ValueError, RecursionError) as exc: + if isinstance(exc, Refused): + raise + raise Refused('invalid profile JSON') from exc + + +def _fields(value, fields): + if type(value) is not dict or set(value) != set(fields): + raise Refused('profile fields do not match the selected schema') + + +def _positive(value, maximum: int, name: str, *, zero: bool = False): + if type(value) is not int or not (0 if zero else 1) <= value <= maximum: + raise Refused(f'{name} outside its admitted integer domain') + return value + + +@dataclass(frozen=True) +class CycleLimits: + input_bytes: int = 262144 + round_bytes: int = 1048576 + rounds: int = 32 + discovery_visits: int = 16000000 + prime_work: int = 16000000 + native_occurrences: int = 131072 + + def __post_init__(self): + if any(type(v) is not int or v < 1 for v in vars(self).values()): + raise Refused('positive integer execution budgets required') + + def numeral(self): + return Limits(output_bits=self.round_bytes*8, wire_bytes=self.round_bytes, + occurrences=self.round_bytes, prime_work=self.prime_work) + + +@dataclass(frozen=True) +class RoundSpec: + path: PrimePath + arities: tuple[int, ...] + circle_order: tuple[int, ...] + spaces: tuple[Fraction, ...] + end_order: str + + def __post_init__(self): + if type(self) is not RoundSpec: + raise Refused('exact RoundSpec required') + if type(self.path) is not PrimePath or type(self.path.steps) is not tuple: + raise Refused('exact immutable PrimePath required') + if type(self.path.seed) is not int or self.path.seed < 2: + raise Refused('prime seed must be an integer at least two') + for step in self.path.steps: + if type(step) is not tuple or not step or type(step[0]) is not str: + raise Refused('prime steps require immutable fields and exact opcode strings') + if step == ('next',): + continue + if (len(step) != 4 or step[0] != 'span' + or any(type(x) is not int for x in step[1:]) + or step[1] not in (2,10) or step[2] < 0 or step[3] < 1): + raise Refused('invalid prime-path step') + if (type(self.arities) is not tuple or not self.arities + or any(type(a) is not int or not 2 <= a <= 64 for a in self.arities) + or len(set(self.arities)) != len(self.arities)): + raise Refused('distinct candidate section arities must be integers 2..64') + if (type(self.circle_order) is not tuple or len(self.circle_order) != 7 + or any(type(c) is not int for c in self.circle_order) + or set(self.circle_order) != set(range(1,8))): + raise Refused('circle_order must be a permutation of 1..7') + if (type(self.spaces) is not tuple or len(self.spaces) != 8 + or any(type(x) is not Fraction + or type(x.numerator) is not int or type(x.denominator) is not int + or not 1 <= x.denominator < 2**32 + or not 0 <= x.numerator < 2*x.denominator + or (Fraction(x.numerator,x.denominator).numerator, + Fraction(x.numerator,x.denominator).denominator) + != (x.numerator,x.denominator) for x in self.spaces)): + raise Refused('eight exact canonical space turns with denominators below 2^32 required') + if type(self.end_order) is not str or self.end_order not in ('first-last','last-first'): + raise Refused('explicit first-last or last-first order required') + + def as_dict(self): + RoundSpec.__post_init__(self) + return {'prime_path': {'seed': self.path.seed, 'steps': [list(x) for x in self.path.steps]}, + 'arities': list(self.arities), 'circle_order': list(self.circle_order), + 'spaces': [[x.numerator,x.denominator] for x in self.spaces], + 'end_order': self.end_order} + + +@dataclass(frozen=True) +class Profile: + scope: str + bucket_bytes: int + corpus_bit_offset: int + rounds: tuple[RoundSpec, ...] + + def __post_init__(self): + if type(self) is not Profile: + raise Refused('exact Profile required') + if type(self.scope) is not str or not self.scope or len(self.scope.encode('utf-8')) > 1024: + raise Refused('nonempty UTF-8 message scope of at most 1024 bytes required') + _positive(self.bucket_bytes, 1048576, 'normalization bucket') + _positive(self.corpus_bit_offset, 2**64-1, 'corpus bit offset', zero=True) + if type(self.rounds) is not tuple or not self.rounds or any(type(r) is not RoundSpec for r in self.rounds): + raise Refused('nonempty immutable round specification required') + for spec in self.rounds: + RoundSpec.__post_init__(spec) + + def as_dict(self): + Profile.__post_init__(self) + return {'schema': PROFILE_SCHEMA, 'selection': SELECTION_PROFILE, 'split': SPLIT_PROFILE, + 'scope': self.scope, 'bucket_bytes': self.bucket_bytes, + 'corpus_bit_offset': self.corpus_bit_offset, 'rounds': [RoundSpec.as_dict(r) for r in self.rounds]} + + @property + def identity(self): + return sha256(canonical(Profile.as_dict(self))).digest() + + @classmethod + def read(cls, data: bytes, limits: CycleLimits = CycleLimits()): + if type(data) is not bytes or len(data) > 65536: + raise Refused('profile must be at most 65536 bytes') + obj = strict_json(data) + _fields(obj, ('schema','selection','split','scope','bucket_bytes','corpus_bit_offset','rounds')) + if obj['schema'] != PROFILE_SCHEMA or obj['selection'] != SELECTION_PROFILE or obj['split'] != SPLIT_PROFILE: + raise Refused('unsupported profile construction') + if type(obj['rounds']) is not list or not obj['rounds']: + raise Refused('round list required') + if len(obj['rounds']) > limits.rounds: + raise ResourceLimit('profile exceeds round budget') + rounds = [] + for row in obj['rounds']: + _fields(row, ('prime_path','arities','circle_order','spaces','end_order')) + path = row['prime_path'] + _fields(path, ('seed','steps')) + if type(path['steps']) is not list or any(type(x) is not list for x in path['steps']): + raise Refused('prime steps must be arrays') + for name in ('arities','circle_order','spaces'): + if type(row[name]) is not list: + raise Refused(f'{name} must be an array') + spaces = [] + for pair in row['spaces']: + if type(pair) is not list or len(pair) != 2 or any(type(x) is not int for x in pair): + raise Refused('space coordinate requires numerator/denominator integers') + if pair[0] < 0 or not 1 <= pair[1] < 2**32: + raise Refused('space coordinate outside rational wire domain') + value = Fraction(*pair) + if [value.numerator,value.denominator] != pair: + raise Refused('space fraction must be canonical') + spaces.append(value) + rounds.append(RoundSpec(PrimePath(path['seed'],tuple(tuple(s) for s in path['steps'])), + tuple(row['arities']),tuple(row['circle_order']),tuple(spaces),row['end_order'])) + return cls(obj['scope'],obj['bucket_bytes'],obj['corpus_bit_offset'],tuple(rounds)) + + +def _corpus_bytes(corpus: bytes, count: int, bit_offset: int) -> bytes: + if type(corpus) is not bytes or not corpus: + raise Refused('actual nonempty corpus bytes required') + start, shift = divmod(bit_offset % (8*len(corpus)), 8) + if shift == 0: + whole = corpus[start:] + corpus[:start] + return (whole*((count+len(whole)-1)//len(whole)))[:count] + return bytes(((corpus[(start+i)%len(corpus)] << shift) & 255) + | (corpus[(start+i+1)%len(corpus)] >> (8-shift)) for i in range(count)) + + +def normalize(message: bytes, corpus: bytes, profile: Profile, + limits: CycleLimits = CycleLimits()) -> bytes: + """Selected corpus-tail bucket profile; original length remains inside the cycle.""" + Profile.__post_init__(profile) + if type(message) is not bytes: + raise Refused('exact message bytes required') + if len(message) > limits.input_bytes: + raise ResourceLimit('message exceeds input byte budget') + if type(corpus) is not bytes or not corpus: + raise Refused('actual nonempty corpus bytes required') + if len(corpus) > limits.round_bytes: + raise ResourceLimit('corpus exceeds input material budget') + header = NORMAL_MAGIC + len(message).to_bytes(8,'big') + sha256(corpus).digest() + occupied = len(header)+len(message) + target = (occupied//profile.bucket_bytes+1)*profile.bucket_bytes + if target > limits.round_bytes: + raise ResourceLimit('normalized frame exceeds round byte budget') + return header + message + _corpus_bytes(corpus,target-occupied,profile.corpus_bit_offset) + + +def denormalize(data: bytes, corpus: bytes, profile: Profile, + limits: CycleLimits = CycleLimits()) -> bytes: + Profile.__post_init__(profile) + if type(data) is not bytes or len(data) < 45 or data[:4] != NORMAL_MAGIC: + raise Refused('invalid normalized source frame') + if type(corpus) is not bytes or not corpus: + raise Refused('actual nonempty corpus required') + length = int.from_bytes(data[4:12],'big') + if length > limits.input_bytes or len(data) > limits.round_bytes or len(corpus) > limits.round_bytes: + raise ResourceLimit('normalization recovery exceeds byte budget') + occupied = 44+length + if occupied >= len(data) or len(data) != (occupied//profile.bucket_bytes+1)*profile.bucket_bytes: + raise Refused('normalized bucket or original length mismatch') + if data[12:44] != sha256(corpus).digest(): + raise Refused('corpus source identity differs') + if data[occupied:] != _corpus_bytes(corpus,len(data)-occupied,profile.corpus_bit_offset): + raise Refused('corpus normalization content differs') + return data[44:occupied] + + +def _symbol(index: int) -> str: + if index < 6400: + return chr(0xE000+index) + index -= 6400 + if index < 65534: + return chr(0xF0000+index) + index -= 65534 + if index < 65534: + return chr(0x100000+index) + raise ResourceLimit('private-use symbol inventory exhausted') + + +def _fraction(reader: _Reader) -> Fraction: + numerator, denominator = reader.uint(),reader.uint() + if denominator == 0: + raise Refused('zero coordinate denominator') + value = Fraction(numerator,denominator) + if not 0 <= value < 2 or (value.numerator,value.denominator) != (numerator,denominator): + raise Refused('noncanonical complete-circle position') + return value + + +def affix(data: bytes, *, api, geometry, scope: str, root: str, round_id: int, + spec: RoundSpec, limits: CycleLimits = CycleLimits()): + RoundSpec.__post_init__(spec) + partition = discover(data,max_bytes=limits.round_bytes,visit_budget=limits.discovery_visits) + if len(partition.order) > limits.native_occurrences: + raise ResourceLimit('native occurrence count exceeds execution budget') + origin = api.ByteOrigin(data,scope,round_id,geometry,root) + circles = tuple(spec.circle_order[i%7] for i in range(len(partition.blocks))) + table = api.close_sequences(origin,partition.blocks,partition.order,circles,spec.spaces) + entries = tuple(Entry(_symbol(i),BitBlock.from_bytes(d.data),api.Geometry.lift(geometry,d.state),circles[i]) + for i,d in enumerate(table.definitions)) + wire_items = api.SequenceTable.wire_occurrences(table) + symbols = ''.join(entries[i].symbol for i,_ in wire_items) + packet = Packet(origin.identity,round_id,entries,symbols) + encoded = encode_numeral(packet,limits.numeral()) + counts = tuple(sum(o.circle==c for _,o in wire_items) for c in range(1,8)) + out = bytearray(AFFIX_MAGIC + _uint(len(data)) + _blob(encoded)) + for count in counts: + out.extend(_uint(count)) + for _,occurrence in wire_items: + position = api.Geometry.lift(geometry,occurrence.source_state) + out.extend(_uint(position.numerator)+_uint(position.denominator)) + if len(out) > limits.round_bytes: + raise ResourceLimit('affixiation coordinates exceed round byte budget') + if len(out) > limits.round_bytes: + raise ResourceLimit('affixiation frame exceeds round byte budget') + counts_by_id = Counter(partition.order) + stats = {'input_bytes':len(data),'definitions':len(entries), + 'repeated_definitions':len(partition.repeat_ids),'occurrences':len(symbols), + 'repeated_occurrences':sum(counts_by_id[i] for i in partition.repeat_ids), + 'longest_definition_bytes':max(map(len,partition.blocks),default=0), + 'discovery_candidate_visits':partition.candidate_visits, + 'numeral_bytes':len(encoded),'native_coordinate_and_frame_bytes':len(out)-len(encoded), + 'output_bytes':len(out)} + return bytes(out),stats + + +def unaffix(data: bytes, *, api, geometry, scope: str, root: str, round_id: int, + spec: RoundSpec, limits: CycleLimits = CycleLimits()) -> bytes: + RoundSpec.__post_init__(spec) + if type(data) is not bytes: + raise Refused('affixiation input must be bytes') + if len(data) > limits.round_bytes: + raise ResourceLimit('affixiation input exceeds byte budget') + reader = _Reader(data) + if reader.take(4) != AFFIX_MAGIC: + raise Refused('unrecognized affixiation frame') + source_length = reader.uint() + if source_length > limits.round_bytes: + raise ResourceLimit('affixiation source length exceeds byte budget') + packet = decode_numeral(reader.blob(limits.round_bytes),limits.numeral()) + if any(entry.recipe is not None for entry in packet.entries): + raise Refused('cycle profile requires literal definitions, not prime recipes') + if packet.round_id != round_id: + raise Refused('round identity mismatch') + # decode_numeral already validates/replays recipes under one budget. Do not + # reset that budget by replaying them again just to sum the output length. + by_symbol = {e.symbol: e for e in packet.entries} + if sum(by_symbol[s].block.length for s in packet.symbols) != source_length*8: + raise Refused('affixiation source bit length mismatch') + if len(packet.symbols) > limits.native_occurrences: + raise ResourceLimit('native occurrence count exceeds execution budget') + if any(e.block.length % 8 for e in packet.entries): + raise Refused('cycle affixiation definitions must be byte aligned') + counts = tuple(reader.uint() for _ in range(7)) + if sum(counts) != len(packet.symbols) or len(packet.symbols) > source_length: + raise Refused('circle counts disagree with occurrence stream') + positions = tuple(_fraction(reader) for _ in packet.symbols) + if reader.pos != len(data): + raise Refused('trailing affixiation bytes') + ids = {e.symbol:i for i,e in enumerate(packet.entries)} + if tuple(e.symbol for e in packet.entries) != tuple(_symbol(i) for i in range(len(packet.entries))): + raise Refused('cycle reference inventory is not canonical') + circles = tuple(e.circle for e in packet.entries) + if circles != tuple(spec.circle_order[i%7] for i in range(len(packet.entries))): + raise Refused('definition circle assignments disagree with profile') + table = api.recover_sequences(geometry,scope=scope,message_origin=root,round_id=round_id, + origin_identity=packet.origin,byte_length=source_length, + blocks=tuple(e.block.to_bytes() for e in packet.entries),circles=circles,spaces=spec.spaces, + counts=counts,wire_order=tuple(ids[s] for s in packet.symbols),source_turns=positions, + max_bytes=limits.round_bytes) + if tuple(api.Geometry.lift(geometry,d.state) for d in table.definitions) != tuple(e.angle for e in packet.entries): + raise Refused('whole-circle sequence attachments do not reconstruct') + restored = api.SequenceTable.restore(table) + selected = discover(restored, max_bytes=limits.round_bytes, + visit_budget=limits.discovery_visits) + if (selected.blocks != tuple(d.data for d in table.definitions) + or selected.order != table.order): + raise Refused('reconstructed partition disagrees with the discovery profile') + return restored + + +def _prepared(profile: Profile, limits: CycleLimits): + Profile.__post_init__(profile) + if len(profile.rounds) > limits.rounds: + raise ResourceLimit('round count exceeds execution budget') + engine = _Primes(limits.numeral()) + routes = tuple(Route.evaluate(s.path,limits.numeral(),engine=engine) for s in profile.rounds) + return routes, engine + + +def _native_identity() -> bytes: + return sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).digest() + + +def forward(message: bytes, corpus: bytes, profile: Profile, sources: str | Path, + limits: CycleLimits = CycleLimits()): + """Run the stated cycle; return its full research record and size-only report.""" + routes, engine = _prepared(profile,limits) + api,geometry = load_native(sources) + data = normalize(message,corpus,profile,limits) + root = api.ByteOrigin(data,profile.scope,0,geometry).message_origin + normalized_size = len(data) + rows = [] + for i,(spec,route) in enumerate(zip(profile.rounds,routes)): + packed,stats = affix(data,api=api,geometry=geometry,scope=profile.scope,root=root, + round_id=i,spec=spec,limits=limits) + split = plan(len(packed)*8,route,spec.arities,limits.numeral(),engine=engine) + data = interleave(packed,split,end_order=spec.end_order) + rows.append({'round':i,**stats,'permutation_bit_count':split.bit_length}) + header = MAGIC + _native_identity() + profile.identity + bytes.fromhex(root) + _uint(len(rows)) + wire = header + _blob(data) + if len(wire) > limits.round_bytes+128: + raise ResourceLimit('cycle record exceeds final byte budget') + report = {'schema':'weave.sequence-cycle-evidence/v1','classification':'CONSTRUCTION_EXPERIMENT', + 'source_bytes':len(message),'corpus_bytes':len(corpus),'normalized_bytes':normalized_size, + 'rounds':rows,'final_payload_bytes':len(data),'outer_frame_bytes':len(wire)-len(data), + 'total_bytes':len(wire),'security':'not established; no asymmetric key generation'} + return wire,report + + +def reverse(wire: bytes, corpus: bytes, profile: Profile, sources: str | Path, + limits: CycleLimits = CycleLimits()) -> bytes: + """Recover from final bytes, corpus, profile and fixed producer sources only.""" + if type(wire) is not bytes: + raise Refused('cycle record must be bytes') + if len(wire) > limits.round_bytes+128: + raise ResourceLimit('cycle record exceeds final byte budget') + # Admit the complete cheap outer frame before prime replay or native code. + # Tiny malformed inputs must not consume the scheduler's full work budget. + reader = _Reader(wire) + if reader.take(4) != MAGIC: + raise Refused('unrecognized cycle record') + source_identity = reader.take(32) + profile_identity = reader.take(32) + root = reader.take(32).hex() + round_count = reader.uint() + data = reader.blob(limits.round_bytes) + if reader.pos != len(wire): + raise Refused('trailing cycle bytes') + Profile.__post_init__(profile) + if source_identity != _native_identity() or profile_identity != profile.identity: + raise Refused('native source lock or profile identity mismatch') + if round_count != len(profile.rounds): + raise Refused('cycle round count mismatch') + routes, engine = _prepared(profile,limits) + api,geometry = load_native(sources) + for i in range(len(profile.rounds)-1,-1,-1): + spec,route = profile.rounds[i],routes[i] + split = plan(len(data)*8,route,spec.arities,limits.numeral(),engine=engine) + packed = interleave(data,split,inverse=True,end_order=spec.end_order) + data = unaffix(packed,api=api,geometry=geometry,scope=profile.scope,root=root, + round_id=i,spec=spec,limits=limits) + if api.ByteOrigin(data,profile.scope,0,geometry).message_origin != root: + raise Refused('message-origin root does not reconstruct') + return denormalize(data,corpus,profile,limits) + + +def _read(path: Path, limit: int) -> bytes: + with path.open('rb') as source: + data = source.read(limit+1) + if len(data) > limit: + raise ResourceLimit(f'file exceeds byte budget: {path.name}') + return data + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('command',choices=('demo','forward','reverse')) + parser.add_argument('input',nargs='?',type=Path) + parser.add_argument('output',nargs='?',type=Path) + parser.add_argument('--profile',type=Path,default=ROOT/'profiles/cycle-v1.json') + parser.add_argument('--corpus',type=Path) + parser.add_argument('--sources',type=Path,default=Path(os.environ.get('WEAVE_SOURCES',ROOT/'sources'))) + args = parser.parse_args() + try: + limits = CycleLimits() + profile = Profile.read(_read(args.profile,65536),limits) + if args.command == 'demo': + message = b'Weave repeats byte sequences. '*24 + bytes(range(64)) + corpus = bytes(range(256)) + b'nonsecret corpus material' + wire,report = forward(message,corpus,profile,args.sources,limits) + restored = reverse(wire,corpus,profile,args.sources,limits) + if restored != message: + raise Refused('demo failed exact recovery') + report['exact_recovery'] = True + print(json.dumps(report,indent=2)) + return 0 + if args.input is None or args.output is None or args.corpus is None: + raise Refused('input, output, and --corpus are required') + if args.output.exists(): + raise Refused('output already exists; refusing overwrite') + corpus = _read(args.corpus,limits.round_bytes) + if args.command == 'forward': + data = _read(args.input,limits.input_bytes) + output,report = forward(data,corpus,profile,args.sources,limits) + else: + data = _read(args.input,limits.round_bytes+128) + output = reverse(data,corpus,profile,args.sources,limits) + report = {'output_bytes':len(output),'standing':'exact construction recovery; not security evidence'} + write_new(args.output, output) + print(json.dumps(report,indent=2)) + return 0 + except (OSError, ValueError, UnicodeError) as exc: + parser.exit(2,f'refused: {exc}\n') + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/research/weave/cycle_native.py b/research/weave/cycle_native.py new file mode 100644 index 00000000..8d011a15 --- /dev/null +++ b/research/weave/cycle_native.py @@ -0,0 +1,70 @@ +# === MODULE_BUILD === +# id: weave_native_binary_binding +# module_name: cycle_native +# module_kind: adapter +# summary: loads this forge's exact binary candidate and its unchanged native UCNS producers +# owner: Erin Spencer +# public_surface: load_native +# internal_surface: fresh checked-buffer module loading +# auth_boundary: none +# storage_boundary: read +# network_boundary: none +# user_data_boundary: none +# tests: tests/test_cycle.py, tests/test_cycle_repairs.py +# rollout: research only; no UCHC package, graduation, or geometry replacement +# rollback: remove with the native binary cycle +# === END MODULE_BUILD === +# === CONTRACTS === +# id: cycle_native_source_pinned +# given: the local source lock and a supplied UCNS checkout +# then: execute freshly verified native buffers or refuse before processing data +# === END CONTRACTS === +"""Usage: api, geometry = load_native('/directory-containing-ucns'). + +The binary construction is Stack-owned research. UCHC supplies its architecture +reference, not a falsely graduated runtime package. UCNS remains geometry owner. +There is no cache reuse, network fallback, or fabricated native object. +""" +from hashlib import sha1 +from pathlib import Path +from types import ModuleType +from uuid import uuid4 +import json +import sys +from numeral import Refused + +ROOT = Path(__file__).resolve().parent + + +def load_native(sources: str | Path): + lock = json.loads((ROOT/'CYCLE_NATIVE.json').read_text()) + if lock.get('schema') != 'weave.native-inputs/v2': + raise Refused('unsupported native source lock') + entry = lock['binary_source'] + if entry['path'] != 'native_binary.py' or entry['owner'] != 'The-Interdependency/stack': + raise Refused('binary candidate must remain in its declared forge') + path = ROOT/entry['path'] + try: + with path.open('rb') as source: + data = source.read(65537) + except OSError as exc: + raise Refused(f'native binary source unavailable: {path}') from exc + actual = sha1(b'blob '+str(len(data)).encode()+b'\0'+data).hexdigest() + if len(data)>65536 or actual != entry['git_blob']: + raise Refused('native binary source does not match CYCLE_NATIVE.json') + name = '_weave_binary_' + actual + '_' + uuid4().hex + api = ModuleType(name) + api.__file__ = str(path) + sys.modules[name] = api + try: + exec(compile(data,str(path),'exec'),api.__dict__) + finally: + if sys.modules.get(name) is api: + del sys.modules[name] + if api.UCNS_COMMIT != lock['ucns_commit'] or api.UCNS_BLOBS != lock['ucns_sources']: + raise Refused('candidate and consumer native dependency locks disagree') + try: + geometry = api.Geometry(Path(sources)/'ucns') + except api.BinaryError as exc: + raise Refused(str(exc)) from exc + return api, geometry diff --git a/research/weave/native_binary.py b/research/weave/native_binary.py new file mode 100644 index 00000000..6a6ba9f5 --- /dev/null +++ b/research/weave/native_binary.py @@ -0,0 +1,507 @@ +# === MODULE_BUILD === +# id: weave_binary_origin_affixiation +# module_name: native_binary +# module_kind: candidate +# summary: raw-byte origins and closed repeated-sequence participation using source-pinned native UCNS axis and Mobius constructors +# owner: Erin Spencer +# public_surface: Geometry, ByteOrigin, Occurrence, ClosedSequence, SequenceTable, close_sequences, recover_sequences +# internal_surface: exact source loader and canonical construction receipts +# auth_boundary: none +# storage_boundary: read +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_binary_origin.py +# rollout: Stack-owned binary-domain candidate; no UCHC graduation or public package +# rollback: remove with the Weave cycle; original misplaced proposal is provenance only +# requires: ucns_axis_circle_position_candidate, ucns_native_mobius_geometry +# unresolved: cryptographic public/private lift and cross-origin metric geometry are not supplied +# === END MODULE_BUILD === +# === CONTRACTS === +# id: binary_origin_byte_axes +# given: exact raw bytes at a message-origin and round +# then: every byte occurrence is addressable by its native UCNS axis without collapsing equal values +# id: binary_sequence_closure +# given: unique byte-sequence definitions and an ordered partition of a source +# then: closure retains sequence values, byte order, multiplicity, and both source and occurrence-circle attachments +# id: binary_native_geometry +# given: the exact declared UCNS source modules and supplied space relations +# then: native axis and Mobius objects perform all geometric construction; 360 and 720 returns remain distinct +# id: binary_coordinate_recovery +# given: definitions, seven ordered circle streams and their native complete positions +# then: inverse native displacement recovers exact source placement without an original-message copy +# id: binary_source_refusal +# given: missing or changed UCNS source or invalid occurrence/partition inputs +# then: refuse instead of substituting geometry or silently repairing data +# === END CONTRACTS === +"""Binary-domain origin and sequence closure, not a cipher or language model. + +Usage: geometry = Geometry('/checkout/ucns') + origin = ByteOrigin(b'ABxABy', 'message-1', 0, geometry) + table = close_sequences(origin, (b'AB', b'x', b'y'), (0,1,0,2), + (1,2,3), (Fraction(0),) * 8) + assert table.restore() == b'ABxABy' + +The caller owns segmentation and circle/space selection. This producer owns raw +byte participation and closure; UCNS owns every geometric object. Source receipts +identify constructions; they are not substitutes for the retained bytes, secret +keys, authentication, or mathematical proof. Byte axes are determinable on demand. +The eighth/whole circle uses index 0 in this API; occurrence circles are 1..7. +""" +from __future__ import annotations + +from dataclasses import dataclass, field +from fractions import Fraction +from hashlib import sha1, sha256 +from pathlib import Path +from types import ModuleType +import json +import re +import sys +from uuid import uuid4 + +SCHEMA = 'weave.binary-sequence-origin' +VERSION = '0.2.0' +UCNS_COMMIT = '905e66964a495d7596a577bb64e4158db9465864' +UCNS_BLOBS = { + 'src/ucns/axis_circle.py': '768777c8eca6e65537fdbab2003835d6f4f40569', + 'src/ucns/direct_mobius.py': '14a4cee36b5bbfa72cf3c03703c427abdac7f33d', +} + + +class BinaryError(ValueError): + """Invalid native source, byte-origin, or sequence closure.""" + + +def canonical(obj) -> bytes: + return json.dumps(obj, sort_keys=True, separators=(',', ':'), + ensure_ascii=False, allow_nan=False).encode('utf-8') + + +def _digest(obj) -> str: + return sha256(canonical(obj)).hexdigest() + + +def _natural(value, name: str) -> int: + if type(value) is not int or value < 0: + raise BinaryError(f'{name} must be a nonnegative integer') + return value + + +def _fraction_fields(value: Fraction) -> tuple[int, int]: + """Extract builtin fields only; hostile equality must never authorize a record.""" + if type(value) is not Fraction: + raise BinaryError('native coordinate requires an exact Fraction') + try: + numerator, denominator = value.numerator, value.denominator + except AttributeError as exc: + raise BinaryError('native coordinate is missing rational fields') from exc + if type(numerator) is not int or type(denominator) is not int or denominator <= 0: + raise BinaryError('native coordinate requires exact Fraction fields') + normalized = Fraction(numerator, denominator) + if (normalized.numerator, normalized.denominator) != (numerator, denominator): + raise BinaryError('native coordinate requires canonical Fraction fields') + return numerator, denominator + + +def _sha(value: str) -> str: + if type(value) is not str or re.fullmatch('[0-9a-f]{64}', value) is None: + raise BinaryError('exact lowercase SHA-256 identity required') + return value + + +def _load_exact(path: Path, expected: str) -> ModuleType: + try: + with path.open('rb') as source: + data = source.read(65537) + except OSError as exc: + raise BinaryError(f'UCNS source unavailable: {path}') from exc + actual = sha1(b'blob ' + str(len(data)).encode() + b'\0' + data).hexdigest() + if len(data) > 65536 or actual != expected: + raise BinaryError(f'UCNS source identity mismatch: {path}') + # Each instance executes its own checked bytes. Never reuse a mutable public + # sys.modules slot or an earlier Geometry instance's instrumented functions. + name = '_weave_ucns_' + expected + '_' + uuid4().hex + module = ModuleType(name) + module.__file__ = str(path) + sys.modules[name] = module # dataclass annotation resolution during execution + try: + exec(compile(data, str(path), 'exec'), module.__dict__) + finally: + if sys.modules.get(name) is module: + del sys.modules[name] + return module + + + +class Geometry: + """Read the two exact UCNS modules; no network, package shadow, or fallback.""" + def __init__(self, ucns_root: str | Path): + root = Path(ucns_root) + self.axis_module = _load_exact(root/'src/ucns/axis_circle.py', + UCNS_BLOBS['src/ucns/axis_circle.py']) + self.mobius_module = _load_exact(root/'src/ucns/direct_mobius.py', + UCNS_BLOBS['src/ucns/direct_mobius.py']) + + def axis(self, origin_sha256: str, count: int, ordinal: int): + return self.axis_module.build_axis_circle_position( + origin_sha256=origin_sha256, axis_count=count, axis_ordinal=ordinal) + + def placed(self, axis, space: Fraction): + _fraction_fields(space) + Geometry._axis_fields(self, axis) + return self.mobius_module.native_mobius_state(axis.turn - space) + + def recover_axis(self, origin: str, count: int, position: Fraction, space: Fraction): + """Invert the native frame displacement and recover its exact source axis.""" + numerator, denominator = _fraction_fields(position) + if not 0 <= numerator < 2*denominator: + raise BinaryError('canonical complete position in [0,2) required') + _fraction_fields(space) + state = self.mobius_module.native_mobius_state(position).advance(space) + ordinal = state.phase_turns * count + if state.frame.sign != 1 or ordinal.denominator != 1: + raise BinaryError('position does not lift to a source byte axis') + return Geometry.axis(self, origin, count, ordinal.numerator) + + def _axis_fields(self, axis) -> tuple: + """Inspect the exact class from this verified producer instance, then fields. + + Comparison to a newly constructed native axis owns geometric validation; + these guards prevent Python equality overrides from bypassing that comparison. + """ + if type(axis) is not self.axis_module.AxisCirclePosition: + raise BinaryError('axis must be an exact native object from this Geometry') + try: + return (_sha(axis.origin_sha256), _natural(axis.axis_count, 'axis count'), + _natural(axis.axis_ordinal, 'axis ordinal'), _fraction_fields(axis.turn), + _sha(axis.identity_sha256)) + except AttributeError as exc: + raise BinaryError('native axis is missing required fields') from exc + + def _state_fields(self, state) -> tuple: + """Native frame identity and typed scalars, without user-defined equality.""" + if type(state) is not self.mobius_module.NativeMobiusState: + raise BinaryError('state must be an exact native object from this Geometry') + try: + frames = self.mobius_module.NativeMobiusFrame + if state.frame is frames.POSITIVE: + frame = 0 + elif state.frame is frames.REVERSED: + frame = 1 + else: + raise BinaryError('state must carry this Geometry native frame') + phase = _fraction_fields(state.phase_turns) + if not 0 <= phase[0] < phase[1]: + raise BinaryError('native state phase is not canonical') + return phase, frame + except AttributeError as exc: + raise BinaryError('native state is missing required fields') from exc + + def lift(self, state) -> Fraction: + """Serialize the native complete frame in [0,2), not just visible phase.""" + phase, frame = Geometry._state_fields(self, state) + return Fraction(*phase) + frame + + +@dataclass(frozen=True) +class ByteOrigin: + source: bytes = field(repr=False) + scope: str + round_id: int + geometry: Geometry = field(repr=False, compare=False) + message_origin: str | None = None + + def __post_init__(self): + if type(self.source) is not bytes: + raise BinaryError('exact raw bytes required') + if type(self.scope) is not str or not self.scope or len(self.scope.encode('utf-8')) > 1024: + raise BinaryError('nonempty message scope of at most 1024 UTF-8 bytes required') + _natural(self.round_id, 'round') + if type(self.geometry) is not Geometry: + raise BinaryError('native Geometry required') + source_digest = sha256(self.source).hexdigest() + if self.round_id == 0: + root = _digest({'schema': SCHEMA, 'version': VERSION, 'scope': self.scope, + 'byte_length': len(self.source), 'source_sha256': source_digest}) + if self.message_origin is not None and self.message_origin != root: + raise BinaryError('initial message-origin does not identify the source') + object.__setattr__(self, 'message_origin', root) + else: + _sha(self.message_origin) + # Memoize the source digest once. Axis queries must not re-hash the source. + identity = _digest({'schema': SCHEMA, 'version': VERSION, 'scope': self.scope, + 'message_origin': self.message_origin, 'round': self.round_id, + 'byte_length': len(self.source), 'source_sha256': source_digest}) + object.__setattr__(self, '_identity', identity) + + @property + def identity(self) -> str: + return self._identity + + def byte_axis(self, offset: int): + _natural(offset, 'byte offset') + if offset >= len(self.source): + raise BinaryError('byte offset outside this round') + return Geometry.axis(self.geometry, self.identity, len(self.source), offset) + + +@dataclass(frozen=True) +class Occurrence: + source_offset: int + circle: int + ordinal: int + source_axis: object + circle_axis: object + state: object + source_state: object + + def __post_init__(self): + _natural(self.source_offset, 'source offset') + _natural(self.ordinal, 'circle ordinal') + if type(self.circle) is not int or not 1 <= self.circle <= 7: + raise BinaryError('occurrence circle must be 1..7') + + +@dataclass(frozen=True) +class ClosedSequence: + data: bytes = field(repr=False) + attachment_axis: object + state: object + occurrences: tuple[Occurrence, ...] + + def __post_init__(self): + if type(self.data) is not bytes or not self.data: + raise BinaryError('closed sequence requires nonempty exact bytes') + if (type(self.occurrences) is not tuple or not self.occurrences + or any(type(o) is not Occurrence for o in self.occurrences)): + raise BinaryError('closed sequence requires immutable occurrence records') + + @property + def repeated(self) -> bool: + return len(self.data) >= 2 and len(self.occurrences) >= 2 + + +@dataclass(frozen=True) +class SequenceTable: + origin: ByteOrigin + definitions: tuple[ClosedSequence, ...] + order: tuple[int, ...] + spaces: tuple[Fraction, ...] + + def __post_init__(self): + SequenceTable.validate(self) + + def validate(self) -> None: + """Reconstruct and compare the entire closed relation, not its type label. + + Direct constructors and dataclasses.replace must satisfy the same source, + ordering, native axis, space-displacement and occurrence invariants as + close_sequences. Frozen does not mean intrinsically consistent. + """ + if type(self) is not SequenceTable: + raise BinaryError('exact SequenceTable required') + if type(self.origin) is not ByteOrigin: + raise BinaryError('closed table requires a native byte-origin') + if (type(self.definitions) is not tuple + or any(type(d) is not ClosedSequence for d in self.definitions)): + raise BinaryError('immutable closed definitions required') + origin = self.origin + reidentified = ByteOrigin(origin.source, origin.scope, origin.round_id, + origin.geometry, origin.message_origin) + _sha(origin.message_origin) + if _sha(origin.identity) != reidentified.identity: + raise BinaryError('origin identity no longer binds its source') + for definition in self.definitions: + ClosedSequence.__post_init__(definition) + for occurrence in definition.occurrences: + Occurrence.__post_init__(occurrence) + blocks = tuple(d.data for d in self.definitions) + circles = tuple(d.occurrences[0].circle for d in self.definitions) + expected = _closed_definitions(origin, blocks, self.order, circles, self.spaces) + def fields(definitions): + geometry = origin.geometry + return tuple((d.data, Geometry._axis_fields(geometry, d.attachment_axis), + Geometry._state_fields(geometry, d.state), + tuple((o.source_offset, o.circle, o.ordinal, + Geometry._axis_fields(geometry, o.source_axis), + Geometry._axis_fields(geometry, o.circle_axis), + Geometry._state_fields(geometry, o.state), + Geometry._state_fields(geometry, o.source_state)) + for o in d.occurrences)) for d in definitions) + # Only builtin tuples/bytes/strings/integers reach equality. A foreign + # object or native subclass cannot license itself with __eq__ == True. + if fields(self.definitions) != fields(expected): + raise BinaryError('closed native relations disagree with source or placement') + + def restore(self) -> bytes: + """Read definitions and occurrence order, not origin.source.""" + SequenceTable.validate(self) + return b''.join(self.definitions[i].data for i in self.order) + + def wire_occurrences(self) -> tuple[tuple[int, Occurrence], ...]: + """Seven circle streams, each ordered by its native complete local position.""" + SequenceTable.validate(self) + items = [(i, o) for i, d in enumerate(self.definitions) for o in d.occurrences] + return tuple(sorted(items, key=lambda item: ( + item[1].circle, Geometry.lift(self.origin.geometry, item[1].state)))) + + def receipt(self) -> dict: + SequenceTable.validate(self) + return {'schema': SCHEMA, 'version': VERSION, 'origin': self.origin.identity, + 'message_origin': self.origin.message_origin, 'round': self.origin.round_id, + 'spaces': [str(space) for space in self.spaces], + 'definitions': [ + {'length': len(d.data), 'source_sha256': sha256(d.data).hexdigest(), + 'attachment': self.origin.geometry.axis_module.AxisCirclePosition.as_dict(d.attachment_axis), + 'complete_turn': str(Geometry.lift(self.origin.geometry, d.state)), + 'occurrences': [ + {'source_offset': o.source_offset, 'circle': o.circle, + 'ordinal': o.ordinal, 'source_axis': self.origin.geometry.axis_module.AxisCirclePosition.as_dict(o.source_axis), + 'circle_axis': self.origin.geometry.axis_module.AxisCirclePosition.as_dict(o.circle_axis), + 'complete_turn': str(Geometry.lift(self.origin.geometry, o.state)), + 'source_turn': str(Geometry.lift(self.origin.geometry, o.source_state))} + for o in d.occurrences]} + for d in self.definitions], 'order': list(self.order)} + + +def _closed_definitions(origin: ByteOrigin, blocks: tuple[bytes, ...], + order: tuple[int, ...], circles: tuple[int, ...], + spaces: tuple[Fraction, ...]) -> tuple[ClosedSequence, ...]: + """Close an exact ordered partition using native whole/occurrence attachments. + + The caller supplies selection and assignments. The explicit binary-domain + attachment rule uses the first selected occurrence's native byte axis for a + sequence's whole-circle attachment; occurrence circles retain source offsets + and ordered native local axes. No cross-origin angle or inner product is claimed. + """ + if type(origin) is not ByteOrigin: + raise BinaryError('ByteOrigin required') + if any(type(v) is not tuple for v in (blocks, order, circles, spaces)): + raise BinaryError('immutable partition inputs required') + if len(circles) != len(blocks) or len(spaces) != 8: + raise BinaryError('one circle per definition and exactly eight space relationships required') + for space in spaces: + _fraction_fields(space) + if any(type(b) is not bytes or not b for b in blocks) or len(set(blocks)) != len(blocks): + raise BinaryError('definitions must be unique nonempty byte sequences') + if any(type(c) is not int or not 1 <= c <= 7 for c in circles): + raise BinaryError('occurrence circle must be 1..7') + if len(blocks) > len(origin.source) or len(order) > len(origin.source): + raise BinaryError('partition cardinality exceeds source byte count') + slots = [[] for _ in range(8)] + uses = [[] for _ in blocks] + offset = 0 + for index in order: + if type(index) is not int or not 0 <= index < len(blocks): + raise BinaryError('undefined sequence reference') + block = blocks[index] + if offset + len(block) > len(origin.source) or origin.source[offset:offset+len(block)] != block: + raise BinaryError('partition does not reconstruct the declared source') + circle = circles[index] + ordinal = len(slots[circle]) + slots[circle].append((offset, len(block))) + uses[index].append((offset, circle, ordinal)) + offset += len(block) + if offset != len(origin.source) or any(not group for group in uses): + raise BinaryError('partition omits source bytes or contains unused definitions') + circle_origins = [_digest({'origin': origin.identity, 'circle': i, 'slots': slots[i]}) + for i in range(8)] + definitions = [] + for index, block in enumerate(blocks): + attachment = ByteOrigin.byte_axis(origin, uses[index][0][0]) + occurrences = [] + for start, circle, ordinal in uses[index]: + source_axis = ByteOrigin.byte_axis(origin, start) + local_axis = Geometry.axis(origin.geometry, circle_origins[circle], len(slots[circle]), ordinal) + occurrences.append(Occurrence(start, circle, ordinal, source_axis, local_axis, + Geometry.placed(origin.geometry, local_axis, spaces[circle]), + Geometry.placed(origin.geometry, source_axis, spaces[circle]))) + definitions.append(ClosedSequence(block, attachment, + Geometry.placed(origin.geometry, attachment, spaces[0]), tuple(occurrences))) + return tuple(definitions) + + +def close_sequences(origin: ByteOrigin, blocks: tuple[bytes, ...], + order: tuple[int, ...], circles: tuple[int, ...], + spaces: tuple[Fraction, ...]) -> SequenceTable: + """Construct and validate the complete source-bound native relation. + + Usage: close_sequences(origin, blocks, order, circles, spaces). Selection and + the eight space relations are caller inputs; UCNS constructs all geometry. + """ + definitions = _closed_definitions(origin, blocks, order, circles, spaces) + return SequenceTable(origin, definitions, order, spaces) + + +def recover_sequences(geometry: Geometry, *, scope: str, message_origin: str, + round_id: int, origin_identity: str, byte_length: int, + blocks: tuple[bytes, ...], circles: tuple[int, ...], + spaces: tuple[Fraction, ...], counts: tuple[int, ...], + wire_order: tuple[int, ...], source_turns: tuple[Fraction, ...], + max_bytes: int = 1048576) -> SequenceTable: + """Recover source placement from seven native circle streams, then close again. + + No original message, plaintext occurrence list, or encoder trace is accepted. + The transmitted complete positions and supplied circle-space relationships + determine source axes. This is a reversible positional representation, not a + secret-key advantage or authenticated decryption. + """ + if type(geometry) is not Geometry: + raise BinaryError('native Geometry required') + _sha(origin_identity) + _sha(message_origin) + _natural(byte_length, 'source byte length') + if type(max_bytes) is not int or max_bytes < 1 or byte_length > max_bytes: + raise BinaryError('source byte length exceeds reconstruction budget') + if any(type(v) is not tuple for v in (blocks, circles, spaces, counts, wire_order, source_turns)): + raise BinaryError('immutable reconstruction records required') + if len(circles) != len(blocks) or len(spaces) != 8 or len(counts) != 7: + raise BinaryError('invalid circle reconstruction cardinality') + if any(type(b) is not bytes or not b for b in blocks): + raise BinaryError('nonempty byte definitions required') + if any(type(c) is not int or not 1 <= c <= 7 for c in circles): + raise BinaryError('occurrence circle must be 1..7') + for space in spaces: + _fraction_fields(space) + if any(type(n) is not int or n < 0 for n in counts): + raise BinaryError('nonnegative circle occurrence counts required') + if sum(counts) != len(wire_order) or len(source_turns) != len(wire_order): + raise BinaryError('circle counts and coordinate records disagree') + if len(wire_order) > byte_length or len(blocks) > byte_length: + raise BinaryError('reconstruction cardinality exceeds byte length') + positions, cursor, total = [], 0, 0 + for circle, count in enumerate(counts, 1): + for _ in range(count): + index, turn = wire_order[cursor], source_turns[cursor] + if type(index) is not int or not 0 <= index < len(blocks) or circles[index] != circle: + raise BinaryError('reference is not on its declared occurrence circle') + axis = Geometry.recover_axis(geometry, origin_identity, byte_length, turn, spaces[circle]) + positions.append((axis.axis_ordinal, index)) + total += len(blocks[index]) + if total > byte_length: + raise BinaryError('definitions exceed reconstructed byte length') + cursor += 1 + positions.sort() + offset, pieces, order = 0, [], [] + for start, index in positions: + if start != offset: + raise BinaryError('source positions overlap or leave a gap') + pieces.append(blocks[index]) + order.append(index) + offset += len(blocks[index]) + if offset != byte_length: + raise BinaryError('source positions do not cover the declared stream') + origin = ByteOrigin(b''.join(pieces), scope, round_id, geometry, message_origin) + if origin.identity != origin_identity: + raise BinaryError('reconstructed origin identity differs') + table = close_sequences(origin, blocks, tuple(order), circles, spaces) + replay = SequenceTable.wire_occurrences(table) + if tuple(i for i, _ in replay) != wire_order or tuple(Geometry.lift(geometry, o.source_state) for _, o in replay) != source_turns: + raise BinaryError('circle streams do not replay in canonical geometric order') + return table + + +__all__ = ['SCHEMA', 'VERSION', 'UCNS_COMMIT', 'UCNS_BLOBS', 'BinaryError', + 'Geometry', 'ByteOrigin', 'Occurrence', 'ClosedSequence', + 'SequenceTable', 'close_sequences', 'recover_sequences'] diff --git a/research/weave/numeral.py b/research/weave/numeral.py new file mode 100644 index 00000000..1e9b7f12 --- /dev/null +++ b/research/weave/numeral.py @@ -0,0 +1,648 @@ +# === MODULE_BUILD === +# id: weave_numeral_construction +# module_name: numeral +# module_kind: experiment +# summary: exact length-bearing bit blocks, executable prime paths, and scoped Unicode references with self-contained recovery and full wire-size accounting +# owner: Erin Spencer +# public_surface: BitBlock, PrimePath, Entry, Packet, Limits, encode, decode, main +# internal_surface: canonical wire reader and bounded exact prime evaluator +# auth_boundary: none +# storage_boundary: write +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_numeral.py +# rollout: explicit research API and CLI; not installed as Weave encryption +# rollback: remove numeral.py, its tests, and its documentation link +# unresolved: native gonol attachment and automatic prime-recipe discovery are not supplied by this representation layer +# === END MODULE_BUILD === +# === CONTRACTS === +# id: numeral_exact_recovery +# given: admitted blocks including leading zero bits and partial bytes +# then: decoding the self-contained packet restores each value, length, occurrence order, and supplied attachment +# id: numeral_recipe_replay +# given: an admitted prime-index and embedded-span recipe +# then: exact evaluation reproduces the bound block or refuses without substituting a literal +# id: numeral_scoped_symbols +# given: a Unicode symbol in a message-origin and round scope +# then: it resolves uniquely within that scope; no standard Unicode numeric meaning is claimed +# id: numeral_accounting +# given: a serialized packet +# then: header, definitions, and occurrence stream sizes sum to all transmitted bytes +# id: numeral_strict_input +# given: malformed, noncanonical, ambiguous, or over-budget input +# then: reject before unsafe allocation or prime work; budget refusal is not mathematical falsification +# === END CONTRACTS === +"""Weave numeral construction, not encryption or a competing gonol constructor. + +Usage: python numeral.py demo + python numeral.py bind INPUT OUTPUT --origin message-1 --angle 1/7 --circle 3 + python numeral.py recover INPUT OUTPUT + python numeral.py inspect INPUT + +A Unicode symbol names a length-bearing integer or an executable prime path. +The packet includes definitions: there is no hidden dictionary or original-message +lookup. Angles/occurrence-circle assignments are caller-supplied attachment data, +not geometry generated here. No automatic splitting, corpus selection, interleave, +private/public key derivation, authentication, or native UCNS/UCHC claim is made. +""" +from __future__ import annotations + +import argparse +from dataclasses import dataclass, field +from fractions import Fraction +from math import isqrt +from pathlib import Path +import json +from safe_output import write_new + +MAGIC = b"WNC\x01" + + +class Refused(ValueError): + """Malformed or inconsistent construction.""" + + +class ResourceLimit(Refused): + """This execution profile cannot admit the requested work.""" + + +@dataclass(frozen=True) +class Limits: + """Operational budgets, adjustable by the caller; not mathematical bounds.""" + output_bits: int = 64 * 1024 * 1024 * 8 + wire_bytes: int = 80 * 1024 * 1024 + entries: int = 137468 + occurrences: int = 1000000 + prime_index: int = 100000 + prime_value: int = 2**32 - 1 + sieve_bytes: int = 4000000 + recipe_steps: int = 256 + prime_work: int = 16000000 + + def __post_init__(self): + if any(type(v) is not int or v < 1 for v in vars(self).values()): + raise Refused("budgets must be positive integers") + + +def _nat(value: int) -> int: + if type(value) is not int or value < 0: + raise Refused("nonnegative integer required; Booleans are not integers here") + return value + + +@dataclass(frozen=True) +class BitBlock: + """Exact ordered bits as (integer, length); the length is part of identity.""" + value: int = field(repr=False) + length: int + + def __post_init__(self): + _nat(self.value) + _nat(self.length) + if self.value.bit_length() > self.length: + raise Refused("integer does not fit declared bit length") + + @classmethod + def from_bytes(cls, data: bytes, length: int | None = None) -> BitBlock: + """MSB-first input; any unused low bits in the last byte must be zero.""" + if type(data) is not bytes: + raise Refused("exact bytes required") + length = len(data) * 8 if length is None else _nat(length) + if len(data) != (length + 7) // 8: + raise Refused("byte count does not match bit length") + padding = (-length) % 8 + value = int.from_bytes(data, "big") + if padding and value & ((1 << padding) - 1): + raise Refused("nonzero unused padding bits") + return cls(value >> padding, length) + + def to_bytes(self) -> bytes: + """Return MSB-first bytes, with zero low padding for a partial last byte.""" + return (self.value << ((-self.length) % 8)).to_bytes((self.length + 7) // 8, "big") + + +@dataclass(frozen=True) +class PrimePath: + """Exact recipe: ('next',) or ('span', base, start, width). + + Spans are contiguous, zero-based from the left, in canonical base 2 or 10 + notation. Both the source and selected value must be prime. A leading-zero + span keeps its original start/width in the recipe, not just its numeric value. + These are supported candidate operations, not a universal Weave routing rule. + """ + seed: int + steps: tuple[tuple, ...] + + def _validate_fields(self, limits: Limits) -> None: + """Validate the whole recipe shape without executing prime operations.""" + if type(self) is not PrimePath or _nat(self.seed) < 2: + raise Refused("exact PrimePath with a seed at least two required") + if type(self.steps) is not tuple: + raise Refused("recipe steps must be an immutable tuple") + if len(self.steps) > limits.recipe_steps: + raise ResourceLimit("recipe step budget exceeded") + for step in self.steps: + if type(step) is not tuple or not step or type(step[0]) is not str: + raise Refused("invalid recipe step") + if step == ("next",): + continue + if len(step) != 4 or step[0] != "span": + raise Refused("unknown recipe operation") + _, base, start, width = step + if type(base) is not int or base not in (2, 10): + raise Refused("span base must be 2 or 10") + _nat(start) + if _nat(width) == 0: + raise Refused("empty span") + + def replay(self, limits: Limits = Limits(), *, _engine=None) -> tuple[int, ...]: + PrimePath._validate_fields(self, limits) + primes = _engine if _engine is not None else _Primes(limits) + primes.charge(len(self.steps) + 1) + value = self.seed + primes.require(value) + trace = [value] + for step in self.steps: + if step == ("next",): + value = primes.nth(value) + else: + _, base, start, width = step + digits = format(value, "b") if base == 2 else str(value) + if start + width > len(digits): + raise Refused("span outside source numeral") + value = int(digits[start:start + width], base) + primes.require(value) + trace.append(value) + return tuple(trace) + + +class _Primes: + def __init__(self, limits: Limits): + self.limits = limits + self.table: list[int] = [] + self.work = 0 + + def charge(self, amount: int) -> None: + self.work += amount + if self.work > self.limits.prime_work: + raise ResourceLimit("aggregate prime work exceeds execution budget") + + def require(self, value: int) -> None: + if value > self.limits.prime_value: + raise ResourceLimit("exact primality work exceeds prime-value budget") + if value < 2 or (value != 2 and value % 2 == 0): + raise Refused("selected value is not prime") + self.charge(len(range(3, isqrt(value) + 1, 2))) + for divisor in range(3, isqrt(value) + 1, 2): + if value % divisor == 0: + raise Refused("selected value is not prime") + + def nth(self, index: int) -> int: + if index > self.limits.prime_index: + raise ResourceLimit("nth-prime index exceeds execution budget") + if index <= len(self.table): + return self.table[index - 1] + bound = max(16, index * 20) + while True: + bound = min(bound, self.limits.prime_value) + if bound + 1 > self.limits.sieve_bytes: + raise ResourceLimit("prime sieve exceeds allocation budget") + self.charge(bound + 1) + sieve = bytearray(b"\x01") * (bound + 1) + sieve[0:2] = b"\x00\x00" + for divisor in range(2, isqrt(bound) + 1): + if sieve[divisor]: + start = divisor * divisor + sieve[start:bound + 1:divisor] = b"\x00" * ((bound - start) // divisor + 1) + self.table = [i for i in range(2, bound + 1) if sieve[i]] + if len(self.table) >= index: + return self.table[index - 1] + if bound == self.limits.prime_value: + raise ResourceLimit("nth prime exceeds prime-value budget") + bound *= 2 + + +def _symbol(symbol: str) -> None: + if type(symbol) is not str or len(symbol) != 1: + raise Refused("one Unicode scalar required") + cp = ord(symbol) + if not (0xE000 <= cp <= 0xF8FF or 0xF0000 <= cp <= 0xFFFFD + or 0x100000 <= cp <= 0x10FFFD): + raise Refused("symbol must be a Unicode private-use scalar") + + +def _validate_attachment(angle: Fraction, circle: int) -> None: + if type(angle) is not Fraction: + raise Refused("exact supplied angle must be a Fraction") + try: + numerator, denominator = angle.numerator, angle.denominator + except AttributeError as exc: + raise Refused("angle is missing its rational fields") from exc + if (type(numerator) is not int or type(denominator) is not int + or denominator <= 0 or not 0 <= numerator < 2*denominator): + raise Refused("exact angle fields must describe [0,2) turns") + canonical_angle = Fraction(numerator, denominator) + if (canonical_angle.numerator, canonical_angle.denominator) != (numerator, denominator): + raise Refused("angle fraction must have canonical fields") + if type(circle) is not int or not 1 <= circle <= 7: + raise Refused("occurrence circle must be one of the seven") + + +@dataclass(frozen=True) +class Entry: + symbol: str + block: BitBlock = field(repr=False) + angle: Fraction + circle: int + recipe: PrimePath | None = field(default=None, repr=False) + + def _validate_fields(self, limits: Limits) -> None: + """Check representation fields; this alone does not establish recipe truth.""" + if type(self) is not Entry: + raise Refused("exact Entry required") + _symbol(self.symbol) + if type(self.block) is not BitBlock: + raise Refused("a definition must contain an exact BitBlock") + BitBlock.__post_init__(self.block) + if self.block.length == 0: + raise Refused("a definition must contain a nonempty BitBlock") + if self.block.length > limits.output_bits: + raise ResourceLimit("definition exceeds bit budget") + _validate_attachment(self.angle, self.circle) + if self.recipe is not None: + PrimePath._validate_fields(self.recipe, limits) + + def validate(self, limits: Limits, *, _engine=None) -> None: + """Validate supplied fields and independently replay any supplied recipe.""" + Entry._validate_fields(self, limits) + if self.recipe is not None: + if PrimePath.replay(self.recipe, limits, _engine=_engine)[-1] != self.block.value: + raise Refused("recipe does not construct its bound integer") + + +def _validate_scope(origin: str, round_id: int) -> None: + if type(origin) is not str or not origin or len(origin.encode("utf-8")) > 1024: + raise Refused("nonempty UTF-8 origin scope of at most 1024 bytes required") + _nat(round_id) + + +def _reference_length(symbols: str, lengths: dict[str, int], limits: Limits) -> int: + """Validate references against admitted lengths without requiring recipe values.""" + if type(symbols) is not str: + raise Refused("occurrence stream must be a string") + if len(symbols) > limits.occurrences: + raise ResourceLimit("occurrence stream exceeds execution budget") + total = 0 + for symbol in symbols: + if symbol not in lengths: + raise Refused("undefined symbol") + total += lengths[symbol] + if total > limits.output_bits: + raise ResourceLimit("reconstructed stream exceeds bit budget") + return total + + +@dataclass(frozen=True) +class Packet: + """One scoped definition table and ordered occurrence stream. + + origin is a caller's scope identifier, not a fabricated UCNS origin object. + The same glyph may legitimately denote another block in another scope. + """ + origin: str + round_id: int + entries: tuple[Entry, ...] + symbols: str + + def _validate_fields(self, limits: Limits) -> int: + """Check typed structure and count bits, without claiming recipe validation. + + Decoder-owned recipes already produced their bound blocks by trusted replay. + Other public operations must use validate(), which replays supplied recipes. + """ + if type(self) is not Packet: + raise Refused("exact Packet required") + _validate_scope(self.origin, self.round_id) + if type(self.entries) is not tuple: + raise Refused("definitions must be an immutable tuple") + if len(self.entries) > limits.entries: + raise ResourceLimit("definition count exceeds execution budget") + mapping = {} + angles = set() + for entry in self.entries: + Entry._validate_fields(entry, limits) + if entry.symbol in mapping or entry.angle in angles: + raise Refused("duplicate symbol or angular attachment in this scope") + mapping[entry.symbol] = entry.block.length + angles.add(entry.angle) + return _reference_length(self.symbols, mapping, limits) + + def validate(self, limits: Limits = Limits(), *, _engine=None) -> int: + """Validate all supplied data, including one recipe replay per definition.""" + total = Packet._validate_fields(self, limits) + primes = _engine if _engine is not None else _Primes(limits) + for entry in self.entries: + Entry.validate(entry, limits, _engine=primes) + return total + + def occurrences(self, limits: Limits = Limits()) -> tuple[tuple[str, int, int, int], ...]: + """(symbol, bit offset, ordinal on its occurrence circle, circle).""" + Packet.validate(self, limits) + mapping = {entry.symbol: entry for entry in self.entries} + counts = [0] * 8 + offset = 0 + result = [] + for symbol in self.symbols: + entry = mapping[symbol] + result.append((symbol, offset, counts[entry.circle], entry.circle)) + counts[entry.circle] += 1 + offset += entry.block.length + return tuple(result) + + def restore(self, limits: Limits = Limits(), *, _engine=None) -> BitBlock: + """Reconstruct in linear output-byte work; no original input or hidden table.""" + total = Packet.validate(self, limits, _engine=_engine) + mapping = {entry.symbol: entry.block for entry in self.entries} + out = bytearray((total + 7) // 8) + offset = 0 + for symbol in self.symbols: + block = mapping[symbol] + raw = BitBlock.to_bytes(block) + byte, shift = divmod(offset, 8) + if shift == 0: + out[byte:byte + len(raw)] = raw + else: + for i, value in enumerate(raw): + out[byte + i] |= value >> shift + if byte + i + 1 < len(out): + out[byte + i + 1] |= (value << (8 - shift)) & 255 + offset += block.length + return BitBlock.from_bytes(bytes(out), total) + + +def _uint(value: int) -> bytes: + _nat(value) + if value >= 2**64: + raise Refused("wire integer field exceeds 64 bits; literal block values use bytes") + out = bytearray() + while value >= 128: + out.append((value & 127) | 128) + value >>= 7 + out.append(value) + return bytes(out) + + +def _blob(data: bytes) -> bytes: + return _uint(len(data)) + data + + +class _Reader: + def __init__(self, data: bytes): + self.data, self.pos = data, 0 + + def take(self, count: int) -> bytes: + if count > len(self.data) - self.pos: + raise Refused("truncated packet") + result = self.data[self.pos:self.pos + count] + self.pos += count + return result + + def uint(self) -> int: + start = self.pos + value = 0 + for shift in range(0, 70, 7): + byte = self.take(1)[0] + value |= (byte & 127) << shift + if byte < 128: + if value >= 2**64 or self.data[start:self.pos] != _uint(value): + raise Refused("noncanonical integer field") + return value + raise Refused("oversized integer field") + + def blob(self, maximum: int) -> bytes: + count = self.uint() + if count > maximum: + raise ResourceLimit("field exceeds byte budget") + return self.take(count) + + +def _parts(packet: Packet, limits: Limits, *, _engine=None) -> tuple[int, bytes, bytes, bytes]: + """Return the validated bit count with wire parts; never replay it for accounting.""" + total = Packet.validate(packet, limits, _engine=_engine) + header = MAGIC + _blob(packet.origin.encode("utf-8")) + _uint(packet.round_id) + _uint(total) + count = _uint(len(packet.entries)) + # Size the entire representation before allocating literal output buffers. + # In particular, unused definitions cannot evade the output-length budget. + stream_size = sum(3 if ord(c) <= 0xFFFF else 4 for c in packet.symbols) + projected = len(header) + len(count) + len(_uint(stream_size)) + stream_size + pieces = [] + for entry in packet.entries: + prefix = (_blob(entry.symbol.encode("utf-8")) + + _uint(entry.angle.numerator) + _uint(entry.angle.denominator) + + _uint(entry.circle) + _uint(entry.block.length)) + if entry.recipe is None: + prefix += b"\x00" + payload_size = (entry.block.length + 7) // 8 + pieces.append((prefix, entry.block)) + else: + recipe = entry.recipe + prefix += b"\x01" + _uint(recipe.seed) + _uint(len(recipe.steps)) + for step in recipe.steps: + if step == ("next",): + prefix += b"\x00" + else: + prefix += b"\x01" + b"".join(_uint(v) for v in step[1:]) + payload_size = 0 + pieces.append((prefix, None)) + projected += len(prefix) + payload_size + if projected > limits.wire_bytes: + raise ResourceLimit("serialized packet exceeds byte budget") + if projected > limits.wire_bytes: + raise ResourceLimit("serialized packet exceeds byte budget") + definitions = count + b"".join(prefix + (BitBlock.to_bytes(block) if block is not None else b"") + for prefix, block in pieces) + stream = _blob(packet.symbols.encode("utf-8")) + return total, header, definitions, stream + + + +def encode(packet: Packet, limits: Limits = Limits(), *, _engine=None) -> bytes: + """Serialize all reconstruction information, including the definition table.""" + _, header, table, stream = _parts(packet, limits, _engine=_engine) + return header + table + stream + + +def decode(data: bytes, limits: Limits = Limits(), *, _engine=None) -> Packet: + """Admit the entire cheap wire structure, then replay accepted recipes once.""" + if type(data) is not bytes: + raise Refused("packet must be bytes") + if len(data) > limits.wire_bytes: + raise ResourceLimit("packet exceeds byte budget") + reader = _Reader(data) + if reader.take(len(MAGIC)) != MAGIC: + raise Refused("unknown packet version") + try: + origin = reader.blob(1024).decode("utf-8") + round_id, declared = reader.uint(), reader.uint() + _validate_scope(origin, round_id) + if declared > limits.output_bits: + raise ResourceLimit("declared output exceeds bit budget") + count = reader.uint() + if count > limits.entries: + raise ResourceLimit("definition count exceeds budget") + pending = [] + lengths, angles = {}, set() + for _ in range(count): + symbol = reader.blob(4).decode("utf-8") + _symbol(symbol) + numerator, denominator = reader.uint(), reader.uint() + if denominator == 0: + raise Refused("zero angle denominator") + angle = Fraction(numerator, denominator) + if (angle.numerator, angle.denominator) != (numerator, denominator): + raise Refused("angle fraction is not canonical") + circle, length = reader.uint(), reader.uint() + _validate_attachment(angle, circle) + if length == 0: + raise Refused("a definition must contain a nonempty BitBlock") + if length > limits.output_bits: + raise ResourceLimit("definition exceeds bit budget") + if symbol in lengths or angle in angles: + raise Refused("duplicate symbol or angular attachment in this scope") + lengths[symbol] = length + angles.add(angle) + tag = reader.take(1)[0] + if tag == 0: + payload = BitBlock.from_bytes(reader.take((length + 7) // 8), length) + elif tag == 1: + seed, step_count = reader.uint(), reader.uint() + if step_count > limits.recipe_steps: + raise ResourceLimit("recipe step count exceeds budget") + steps = [] + for _ in range(step_count): + kind = reader.take(1)[0] + if kind == 0: + steps.append(("next",)) + elif kind == 1: + steps.append(("span", reader.uint(), reader.uint(), reader.uint())) + else: + raise Refused("unknown recipe opcode") + payload = PrimePath(seed, tuple(steps)) + PrimePath._validate_fields(payload, limits) + else: + raise Refused("unknown definition tag") + # Decoder-owned pending data, not an Entry with a fabricated bit value. + pending.append((symbol, length, angle, circle, payload)) + symbols = reader.blob(limits.occurrences * 4).decode("utf-8") + except UnicodeError as exc: + raise Refused("invalid UTF-8") from exc + if reader.pos != len(data): + raise Refused("trailing packet data") + if _reference_length(symbols, lengths, limits) != declared: + raise Refused("declared output length disagrees with occurrences") + # All cheap fields, every recipe shape and the complete occurrence stream are + # admitted before any primality test or nth-prime calculation is attempted. + primes = _engine if _engine is not None else _Primes(limits) + entries = [] + for symbol, length, angle, circle, payload in pending: + recipe = payload if type(payload) is PrimePath else None + block = (BitBlock(PrimePath.replay(recipe, limits, _engine=primes)[-1], length) + if recipe is not None else payload) + entries.append(Entry(symbol, block, angle, circle, recipe)) + packet = Packet(origin, round_id, tuple(entries), symbols) + # Check actual constructed blocks without a second recipe replay. No cached + # authorization escapes: later public operations revalidate supplied records. + if Packet._validate_fields(packet, limits) != declared: + raise Refused("declared output length disagrees with occurrences") + return packet + + +def accounting(packet: Packet, limits: Limits = Limits(), *, _engine=None) -> dict[str, int]: + """Count all serialized bytes under a single validation/prime-work budget.""" + bits, header, table, stream = _parts(packet, limits, _engine=_engine) + return {"source_bits": bits, "source_packed_bytes": (bits + 7) // 8, + "header_bytes": len(header), "definition_bytes": len(table), + "occurrence_bytes": len(stream), "total_bytes": len(header) + len(table) + len(stream), + "symbol_utf8_bytes": len(packet.symbols.encode("utf-8"))} + + +def demo() -> dict: + """Measured nonsecret examples; no secret key or personal input.""" + block = BitBlock.from_bytes(bytes(range(256)) * 4096) + entry = Entry(chr(0xE000), block, Fraction(1, 7), 3) + single = Packet("demo-large", 0, (entry,), entry.symbol) + repeated = Packet("demo-large", 0, (entry,), entry.symbol * 4) + recipe = PrimePath(5381, (("span", 10, 0, 2), ("next",), ("next",))) + r_entry = Entry(chr(0xE001), BitBlock(1523, 16), Fraction(2, 7), 4, recipe) + replay = Packet("demo-prime", 0, (r_entry,), r_entry.symbol) + result = {} + for name, packet in (("single_large_block", single), ("four_repetitions", repeated), ("prime_path", replay)): + wire = encode(packet) + recovered = decode(wire).restore() + result[name] = {**accounting(packet), "exact_recovery": recovered == packet.restore()} + result["prime_path"]["trace"] = list(recipe.replay()) + result["standing"] = "representation-and-replay only; not encryption" + return result + + +def _angle_argument(text: str) -> Fraction: + """Translate malformed CLI fractions into argparse's status-2 refusal.""" + try: + return Fraction(text) + except (ValueError, ZeroDivisionError) as exc: + raise argparse.ArgumentTypeError("angle must be an exact finite fraction") from exc + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + sub.add_parser("demo") + bind = sub.add_parser("bind") + bind.add_argument("input", type=Path) + bind.add_argument("output", type=Path) + bind.add_argument("--origin", required=True) + bind.add_argument("--angle", required=True, type=_angle_argument) + bind.add_argument("--circle", required=True, type=int) + recover = sub.add_parser("recover") + recover.add_argument("input", type=Path) + recover.add_argument("output", type=Path) + inspect = sub.add_parser("inspect") + inspect.add_argument("input", type=Path) + args = parser.parse_args() + try: + if args.command == "demo": + result = demo() + else: + # One CLI operation has one budget across decode/account/recover. + limits = Limits() + engine = _Primes(limits) + limit = limits.output_bits // 8 if args.command == "bind" else limits.wire_bytes + if args.input.stat().st_size > limit: + raise ResourceLimit("input file exceeds execution budget") + with args.input.open("rb") as source: + raw = source.read(limit + 1) + if len(raw) > limit: + raise ResourceLimit("input grew beyond execution budget") + if args.command == "bind": + _validate_attachment(args.angle, args.circle) + entry = Entry(chr(0xE000), BitBlock.from_bytes(raw), args.angle, args.circle) + packet = Packet(args.origin, 0, (entry,) if raw else (), entry.symbol if raw else "") + wire = encode(packet, limits, _engine=engine) + result = accounting(packet, limits, _engine=engine) + write_new(args.output, wire) + else: + packet = decode(raw, limits, _engine=engine) + result = accounting(packet, limits, _engine=engine) + if args.command == "recover": + restored = packet.restore(limits, _engine=engine) + write_new(args.output, restored.to_bytes()) + result["output_bit_length"] = restored.length + print(json.dumps(result, indent=2)) + return 0 + except (OSError, Refused, UnicodeError) as exc: + parser.exit(2, f"refused: {exc}\n") + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/research/weave/prime_schedule.py b/research/weave/prime_schedule.py new file mode 100644 index 00000000..667ff6af --- /dev/null +++ b/research/weave/prime_schedule.py @@ -0,0 +1,163 @@ +# === MODULE_BUILD === +# id: weave_prime_schedule +# module_name: prime_schedule +# module_kind: engine +# summary: executed prime-index and embedded-span routes determine nonempty bit partitions and reversible first/last interleaving +# owner: Erin Spencer +# public_surface: Route, SplitPlan, plan, interleave +# internal_surface: exact composition unranking +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_cycle.py +# rollout: explicit prime-route-compositions-v1 profile only +# rollback: remove scheduler and its cycle bindings +# requires: weave_numeral_construction +# unresolved: distinct routes can induce identical maps; no key entropy or asymmetry claim +# === END MODULE_BUILD === +# === CONTRACTS === +# id: prime_split_replay +# given: a verified prime route, admissible arities and current bit length +# then: exact deterministic nonempty sections cover the stream and reproduce without source plaintext +# id: first_last_inverse +# given: admitted sections and an explicit first-last or last-first order +# then: every source bit appears once and reversing the operation restores exact bytes +# === END CONTRACTS === +"""Usage: route = Route.evaluate(PrimePath(53, (('next',),))) + split = plan(128, route, (3,5,7)) + assert interleave(interleave(data, split), split, inverse=True) == data + +This is a named candidate determinant, not a universal arity law. The actual +prime-index/span route is evaluated, including source, base, span and step order. +A prefix-bearing radix numeral of that route selects an arity and a ranked +composition. Routes remain distinct records even when their resulting maps agree. +""" +from __future__ import annotations +from dataclasses import dataclass +from math import comb +from numeral import PrimePath, Limits, Refused + +PROFILE = 'prime-route-compositions-v1' + + +@dataclass(frozen=True) +class Route: + path: PrimePath + trace: tuple[int, ...] + determinant: int + + def validate(self, limits: Limits = Limits(), *, engine=None) -> int: + """Re-execute the claimed relation before planning from a supplied record. + + The optional engine is trusted execution context. Cycle callers share it + with initial evaluation and every round, so validation cannot reset the + scheduler's aggregate work allowance. No record-owned method is invoked. + """ + if (type(self) is not Route or type(self.path) is not PrimePath + or type(self.trace) is not tuple or not self.trace + or any(type(v) is not int or v < 2 for v in self.trace) + or type(self.determinant) is not int or self.determinant < 1): + raise Refused('route requires exact path, trace and determinant fields') + expected = Route.evaluate(self.path, limits, engine=engine) + if self.trace != expected.trace or self.determinant != expected.determinant: + raise Refused('route fields do not reproduce the claimed prime path') + return expected.determinant + + @classmethod + def evaluate(cls, path: PrimePath, limits: Limits = Limits(), *, engine=None): + if type(path) is not PrimePath: + raise Refused('PrimePath required') + trace = PrimePath.replay(path, limits, _engine=engine) + tokens = [path.seed, len(path.steps)] + for step, value in zip(path.steps, trace[1:]): + tokens.extend((0, value) if step == ('next',) else (1, *step[1:], value)) + base = max(tokens)+2 + determinant = 1 # Retains leading zero tokens and sequence length. + for token in tokens: + determinant = determinant*base + token + return cls(path, trace, determinant) + + +@dataclass(frozen=True) +class SplitPlan: + bit_length: int + lengths: tuple[int, ...] + rank: int + + +def _composition(n: int, arity: int, rank: int) -> tuple[int, ...]: + """Unrank ordered nonempty sections via lexicographic gap combinations.""" + total = comb(n-1, arity-1) + if not 0 <= rank < total: + raise Refused('composition rank outside domain') + gaps, minimum, remaining = [], 1, arity-1 + while remaining: + low, high = minimum, n-remaining + all_remaining = comb(n-minimum, remaining) + while low < high: + middle = (low+high+1)//2 + skipped = all_remaining - comb(n-middle, remaining) + if skipped <= rank: + low = middle + else: + high = middle-1 + selected = low + rank -= all_remaining - comb(n-selected, remaining) + gaps.append(selected) + minimum, remaining = selected+1, remaining-1 + points = (0, *gaps, n) + return tuple(b-a for a,b in zip(points, points[1:])) + + +def plan(bit_length: int, route: Route, arities: tuple[int, ...], + limits: Limits = Limits(), *, engine=None) -> SplitPlan: + """Validate the route relation under the supplied budget, then derive splits.""" + if type(bit_length) is not int or bit_length < 1: + raise Refused('positive bit length required') + if type(route) is not Route: + raise Refused('executed Route required') + if (type(arities) is not tuple or not arities + or any(type(a) is not int or not 2 <= a <= min(64,bit_length) for a in arities) + or len(set(arities)) != len(arities)): + raise Refused('distinct admissible arities must be integers 2..min(64, bit_length)') + determinant = Route.validate(route, limits, engine=engine) + arity = arities[determinant % len(arities)] + rank = (determinant//len(arities)) % comb(bit_length-1, arity-1) + return SplitPlan(bit_length, _composition(bit_length, arity, rank), rank) + + +def interleave(data: bytes, split: SplitPlan, *, inverse: bool = False, + end_order: str = 'first-last') -> bytes: + """Rearrange bits; do not reinterpret the output as bytes until the pass ends.""" + if type(data) is not bytes or type(split) is not SplitPlan: + raise Refused('bytes and SplitPlan required') + if type(inverse) is not bool or type(end_order) is not str or end_order not in ('first-last','last-first'): + raise Refused('explicit Boolean inverse and supported end order required') + if (type(split.bit_length) is not int or split.bit_length != len(data)*8 + or type(split.lengths) is not tuple or not split.lengths + or any(type(n) is not int or n <= 0 for n in split.lengths) + or sum(split.lengths) != split.bit_length): + raise Refused('sections must cover every input bit exactly once') + out = bytearray(len(data)) + offset = 0 + for length in split.lengths: + left, right = offset, offset+length-1 + first = end_order == 'first-last' + for destination in range(offset, offset+length): + if first: + source = left + left += 1 + else: + source = right + right -= 1 + first = not first + if inverse: + read, write = destination, source + else: + read, write = source, destination + bit = (data[read//8] >> (7-read%8)) & 1 + out[write//8] |= bit << (7-write%8) + offset += length + return bytes(out) diff --git a/research/weave/profiles/cycle-v1.json b/research/weave/profiles/cycle-v1.json new file mode 100644 index 00000000..372d1420 --- /dev/null +++ b/research/weave/profiles/cycle-v1.json @@ -0,0 +1,40 @@ +{ + "schema": "weave.sequence-cycle-profile/v1", + "selection": "maximal-lcp-longest-first-v1", + "split": "prime-route-compositions-v1", + "scope": "weave-cycle-demo", + "bucket_bytes": 256, + "corpus_bit_offset": 5, + "rounds": [ + { + "prime_path": { + "seed": 5381, + "steps": [["span", 10, 0, 2], ["next"], ["next"]] + }, + "arities": [3, 5, 7], + "circle_order": [3, 1, 7, 2, 6, 4, 5], + "spaces": [[0, 1], [1, 9], [2, 9], [1, 3], [4, 9], [5, 9], [2, 3], [7, 9]], + "end_order": "first-last" + }, + { + "prime_path": { + "seed": 2, + "steps": [["next"], ["next"], ["next"], ["next"], ["next"], ["next"], ["next"]] + }, + "arities": [3, 5, 7], + "circle_order": [3, 1, 7, 2, 6, 4, 5], + "spaces": [[2, 9], [1, 3], [4, 9], [5, 9], [2, 3], [7, 9], [8, 9], [1, 1]], + "end_order": "first-last" + }, + { + "prime_path": { + "seed": 313, + "steps": [["span", 10, 2, 1], ["next"], ["next"]] + }, + "arities": [3, 5, 7], + "circle_order": [3, 1, 7, 2, 6, 4, 5], + "spaces": [[4, 9], [5, 9], [2, 3], [7, 9], [8, 9], [1, 1], [10, 9], [11, 9]], + "end_order": "first-last" + } + ] +} diff --git a/research/weave/safe_output.py b/research/weave/safe_output.py new file mode 100644 index 00000000..2c84a5d4 --- /dev/null +++ b/research/weave/safe_output.py @@ -0,0 +1,50 @@ +# === MODULE_BUILD === +# id: weave_atomic_output +# module_name: safe_output +# module_kind: io +# summary: no-clobber publication of a fully written and closed research output +# owner: Erin Spencer +# public_surface: write_new +# storage_boundary: write +# network_boundary: none +# tests: tests/test_cycle_repairs.py +# rollback: retain equivalent no-partial-output and no-overwrite guarantees +# === END MODULE_BUILD === +# === CONTRACTS === +# id: weave_output_atomic_no_clobber +# given: bytes and a target path on a hard-link-capable filesystem +# then: install only after successful write, flush, fsync and close; never overwrite an existing path +# === END CONTRACTS === +"""Usage: write_new(Path('result.wvc'), complete_record). + +A temporary sibling is created with owner-only permissions. Successful close +precedes atomic no-clobber linking. Write/flush/fsync/close failures leave no +partial destination. Unsupported filesystem semantics refuse; they do not fall +back to an overwrite-prone rename. Process-crash cleanup/directory durability +are not guaranteed by this helper; ordinary failure cleans its temporary file. +""" +import os +from pathlib import Path +import tempfile + + +def write_new(path: Path, data: bytes) -> None: + if type(data) is not bytes: + raise TypeError('complete bytes required') + path = Path(path) + fd, name = tempfile.mkstemp(prefix='.'+path.name+'.', suffix='.tmp', dir=path.parent) + temporary = Path(name) + try: + try: + target = os.fdopen(fd, 'wb') + except BaseException: + os.close(fd) + raise + with target: + if target.write(data) != len(data): + raise OSError('short output write') + target.flush() + os.fsync(target.fileno()) + os.link(temporary, path) # atomic create; existing files/symlinks fail + finally: + temporary.unlink(missing_ok=True) diff --git a/research/weave/stages/eight_circle.py b/research/weave/stages/eight_circle.py deleted file mode 100644 index 16f4a5ec..00000000 --- a/research/weave/stages/eight_circle.py +++ /dev/null @@ -1,202 +0,0 @@ -# === MODULE_BUILD === -# id: weave_byte_circle_structure -# module_name: eight_circle -# module_kind: schema -# summary: byte-scoped bit placements and whole-plus-one key views, without a substitute cipher -# owner: Erin Spencer -# public_surface: Circle, PublicPair, FullKeySet, BitPlacement, ByteConstruction, construct_byte -# internal_surface: strict input validation -# auth_boundary: none -# storage_boundary: none -# network_boundary: none -# user_data_boundary: read -# admin_only: false -# tests: tests/test_eight_circle.py -# rollout: replaces the incorrect PR 73 per-bit sheet encoder -# rollback: revert this repair as one transaction; do not reactivate the rejected cipher -# unresolved: approved native positional transform and ciphertext serialization -# === END MODULE_BUILD === -# === CONTRACTS === -# id: weave_byte_has_eight_circle_placements -# given: one byte and an explicit bit-to-circle assignment -# then: each source bit occurs exactly once among G0 through G7 -# class: correctness -# id: weave_public_pair_always_contains_whole -# given: a full eight-circle key-set record -# then: every public view is G0 plus exactly one of G1 through G7 -# class: correctness -# id: weave_positions_are_not_bit_codes -# given: exact key-set and placement coordinates -# then: their 720-degree coordinates are retained without converting each source bit into two sheet bits -# class: boundary -# === END CONTRACTS === -"""Byte structure, not a cipher or a replacement UCNS Gonol constructor. - -Usage: construct_byte(0xA5, full, positions, origin_id="message-1", byte_index=0). -The caller supplies eight exact positions and an explicit bit-to-circle assignment. -Positions are data in turns (360 degrees); 720 degrees is the complete return. -No formula deriving positions, native geometry, or ciphertext is invented here. -PublicPair is a key-view boundary, never a two-bit encoding of each source bit. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from fractions import Fraction - -TURN_720 = Fraction(2) - - -def _index(value: int, upper: int, name: str) -> None: - if type(value) is not int or not 0 <= value < upper: - raise ValueError(f"{name} must be an integer in [0, {upper - 1}]") - - -def _identity(value: str) -> None: - if type(value) is not str or not value.strip(): - raise ValueError("a nonempty identity is required") - - -def _phase(value: Fraction) -> Fraction: - if type(value) is not Fraction: - raise TypeError("positions must be exact Fraction values") - return value % TURN_720 - - -def _assignment(values: tuple[int, ...]) -> None: - if type(values) is not tuple or len(values) != 8: - raise ValueError("an explicit eight-member assignment is required") - for value in values: - _index(value, 8, "assignment member") - if set(values) != set(range(8)): - raise ValueError("every source bit and circle must occur exactly once") - - -@dataclass(frozen=True) -class Circle: - """Key-set circle binding; index 0 is the whole, indices 1..7 are parts. - - This stores the circle's exact space coordinate, not its complete native - space relation. A scalar offset does not implement that relation. - """ - index: int - identity: str - space: Fraction = field(repr=False) - - def __post_init__(self) -> None: - _index(self.index, 8, "circle index") - _identity(self.identity) - object.__setattr__(self, "space", _phase(self.space)) - - -@dataclass(frozen=True) -class PublicPair: - """Only the whole and one part; no full-key or plaintext reference.""" - circles: tuple[Circle, Circle] = field(repr=False) - - def __post_init__(self) -> None: - if (type(self.circles) is not tuple or len(self.circles) != 2 - or any(type(c) is not Circle for c in self.circles)): - raise ValueError("exactly two Circle records are required") - whole, part = self.circles - if whole.index != 0 or part.index == 0 or whole.identity == part.identity: - raise ValueError("public pair must be G0 followed by one distinct small circle") - - @property - def indices(self) -> tuple[int, int]: - return self.circles[0].index, self.circles[1].index - - -@dataclass(frozen=True) -class FullKeySet: - """Eight circle bindings plus caller-supplied assignment, not KeyGen. - - bit_to_circle[i] locates the source byte's ith bit (MSB first). No default - assignment, active-part sequence, or private-to-public transform is selected. - """ - circles: tuple[Circle, ...] = field(repr=False) - bit_to_circle: tuple[int, ...] = field(repr=False) - - def __post_init__(self) -> None: - if (type(self.circles) is not tuple or len(self.circles) != 8 - or any(type(c) is not Circle for c in self.circles)): - raise ValueError("the full record requires exactly eight Circle records") - if tuple(c.index for c in self.circles) != tuple(range(8)): - raise ValueError("store the whole first, followed by parts 1 through 7") - if len({c.identity for c in self.circles}) != 8: - raise ValueError("circle identities must be distinct") - _assignment(self.bit_to_circle) - - def degenerate(self, part_index: int) -> PublicPair: - """Select the established pair shape; this does not derive a trapdoor.""" - _index(part_index, 8, "part index") - if part_index == 0: - raise ValueError("select one small circle, not a second whole") - return PublicPair((self.circles[0], self.circles[part_index])) - - -@dataclass(frozen=True) -class BitPlacement: - """One source-bit occurrence assigned to one circle within a byte.""" - source_bit_index: int - circle_index: int - bit: int = field(repr=False) - position: Fraction = field(repr=False) - - def __post_init__(self) -> None: - _index(self.source_bit_index, 8, "source bit index") - _index(self.circle_index, 8, "circle index") - _index(self.bit, 2, "bit") - object.__setattr__(self, "position", _phase(self.position)) - - -@dataclass(frozen=True) -class ByteConstruction: - """One byte's eight placements at its message-origin occurrence. - - Source recovery here reads the retained construction. It is NOT decryption. - The whole carries one bit and remains addressable in every whole-part relation. - Evolution of its state is not implemented by an invented public-feedback rule. - """ - origin_id: str - byte_index: int - placements: tuple[BitPlacement, ...] = field(repr=False) - - def __post_init__(self) -> None: - _identity(self.origin_id) - if type(self.byte_index) is not int or self.byte_index < 0: - raise ValueError("byte index must be a nonnegative integer") - if (type(self.placements) is not tuple or len(self.placements) != 8 - or any(type(p) is not BitPlacement for p in self.placements)): - raise ValueError("one byte requires exactly eight BitPlacement records") - if tuple(p.circle_index for p in self.placements) != tuple(range(8)): - raise ValueError("each circle must carry exactly one placement") - _assignment(tuple(p.source_bit_index for p in self.placements)) - - @property - def source_value(self) -> int: - return sum(p.bit << (7 - p.source_bit_index) for p in self.placements) - - -def construct_byte(value: int, full: FullKeySet, positions: tuple[Fraction, ...], - *, origin_id: str, byte_index: int) -> ByteConstruction: - """Attach all eight bits together using supplied positions in circle order. - - Caller-supplied data is not an approved transform. This function emits a - plaintext-bearing construction record, never ciphertext or a public packet. - """ - _index(value, 256, "byte") - if type(full) is not FullKeySet: - raise TypeError("a full key-set record is required") - if type(positions) is not tuple or len(positions) != 8: - raise ValueError("supply all eight circle positions for the byte") - phases = tuple(_phase(p) for p in positions) - by_circle = sorted( - (circle_index, source_index) - for source_index, circle_index in enumerate(full.bit_to_circle) - ) - placements = tuple( - BitPlacement(source_index, circle_index, (value >> (7 - source_index)) & 1, - phases[circle_index]) - for circle_index, source_index in by_circle - ) - return ByteConstruction(origin_id, byte_index, placements) diff --git a/research/weave/stages/message_origin.py b/research/weave/stages/message_origin.py deleted file mode 100644 index 75679652..00000000 --- a/research/weave/stages/message_origin.py +++ /dev/null @@ -1,100 +0,0 @@ -# === MODULE_BUILD === -# id: weave_message_byte_origin -# module_name: message_origin -# module_kind: schema -# summary: one message origin containing ordered byte occurrences with eight-circle attachments -# owner: Erin Spencer -# public_surface: ByteOccurrence, MessageOrigin, construct_origin, construct_at -# internal_surface: strict occurrence and scope validation -# auth_boundary: none -# storage_boundary: none -# network_boundary: none -# user_data_boundary: read -# admin_only: false -# tests: tests/test_message_origin.py -# rollout: replaces PR 74 bit-axis feedback substitution -# rollback: revert this repair as one transaction without restoring the rejected cipher -# unresolved: native UCHC binding and full-byte keyed positional evolution -# === END MODULE_BUILD === -# === CONTRACTS === -# id: weave_message_origin_contains_bytes -# given: an admitted raw-byte message -# then: its origin contains one ordered occurrence per byte, not eight independent message axes per byte -# class: correctness -# id: weave_byte_occurrences_preserve_scope -# given: repeated byte values at one origin or equal messages at distinct named origins -# then: occurrence order and supplied origin identity remain distinct and recoverable -# class: correctness -# === END CONTRACTS === -"""Message -> byte occurrences -> eight-circle placements. - -Usage: origin = construct_origin(b"AA", identity="message-1") - byte_state = construct_at(origin, 0, full_key_record, eight_positions) - -These are Weave construction records, not a copied UCHC constructor or a new -UCNS geometry. An origin identifier scopes occurrences; it is not a hash of the -message, a nonce, or a source of cryptographic secrecy. Native origin/axis -integration and dynamic positional encryption remain unimplemented. -""" -from __future__ import annotations - -from dataclasses import dataclass, field -from fractions import Fraction -from .eight_circle import ByteConstruction, FullKeySet, construct_byte, _identity, _index - - -@dataclass(frozen=True) -class ByteOccurrence: - origin_id: str - index: int - value: int = field(repr=False) - - def __post_init__(self) -> None: - _identity(self.origin_id) - if type(self.index) is not int or self.index < 0: - raise ValueError("byte occurrence index must be nonnegative") - _index(self.value, 256, "byte") - - -@dataclass(frozen=True) -class MessageOrigin: - identity: str - bytesets: tuple[ByteOccurrence, ...] = field(repr=False) - - def __post_init__(self) -> None: - _identity(self.identity) - if (type(self.bytesets) is not tuple - or any(type(b) is not ByteOccurrence for b in self.bytesets)): - raise TypeError("message participants must be a tuple of byte occurrences") - for index, occurrence in enumerate(self.bytesets): - if occurrence.index != index or occurrence.origin_id != self.identity: - raise ValueError("byte occurrence order and origin must match this message") - - @property - def byte_length(self) -> int: - return len(self.bytesets) - - def recover_bytes(self) -> bytes: - """Read the exact retained source construction, not decrypt a ciphertext.""" - return bytes(b.value for b in self.bytesets) - - -def construct_origin(data: bytes, *, identity: str) -> MessageOrigin: - """Admit each raw byte once at its caller-named message origin.""" - if type(data) is not bytes: - raise TypeError("raw bytes required; no text normalization") - _identity(identity) - return MessageOrigin(identity, tuple( - ByteOccurrence(identity, index, value) for index, value in enumerate(data) - )) - - -def construct_at(origin: MessageOrigin, byte_index: int, full: FullKeySet, - positions: tuple[Fraction, ...]) -> ByteConstruction: - """Attach one whole byte, carrying its source occurrence into all placements.""" - if type(origin) is not MessageOrigin: - raise TypeError("a MessageOrigin is required") - _index(byte_index, origin.byte_length, "byte index") - occurrence = origin.bytesets[byte_index] - return construct_byte(occurrence.value, full, positions, - origin_id=origin.identity, byte_index=occurrence.index) diff --git a/research/weave/test.py b/research/weave/test.py index 38acb35b..7d2941ab 100644 --- a/research/weave/test.py +++ b/research/weave/test.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -"""Run the full Weave workspace suite and emit a source-bound receipt. +"""Run the full repository Weave suite and emit a source-bound receipt. -Usage: python test.py --receipt /tmp/weave-check.json -Transport behavior, byte/star construction records and their boundaries are tested. -Native positional encryption and native corpus replay remain explicitly unexecuted. +Usage: WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-check.json +The exact source lock is required for the native cycle tests. All remaining old +transport tests retain their scope; no cycle result establishes cipher security. +This runner belongs to the full Stack checkout, not the smaller standalone bundle. """ import argparse import hashlib @@ -16,12 +17,14 @@ from assembly import Pipeline ROOT = Path(__file__).resolve().parent +EXPECTED_TESTS = 176 # 168 existing + 8 cheap-admission/public-helper regressions. def snapshot(): paths = list(ROOT.rglob('*.py')) + list((ROOT/'profiles').glob('*.json')) + paths += [ROOT/'CYCLE_NATIVE.json', ROOT/'CYCLE_WORK_GRAPH.json'] return {str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest() - for p in sorted(paths) if '__pycache__' not in p.parts} + for p in sorted(set(paths)) if '__pycache__' not in p.parts and 'sources' not in p.parts} def main(): @@ -33,51 +36,60 @@ def main(): result = unittest.TextTestRunner(stream=report, verbosity=2).run( unittest.defaultTestLoader.discover(str(ROOT/'tests'))) after = snapshot() - # 52 unchanged transport/assembly methods + 12 byte/star + 10 message methods. passed = (result.wasSuccessful() and not result.skipped and not result.expectedFailures and not result.unexpectedSuccesses - and result.testsRun == 74 and before == after) + and result.testsRun == EXPECTED_TESTS and before == after) receipt = { - 'schema':'weave.transport-evidence/v1', - 'status':'PASSED' if passed else 'FAILED', - 'python':platform.python_version(), - 'tests':result.testsRun, - 'failures':len(result.failures),'errors':len(result.errors),'skipped':len(result.skipped), - 'source_unchanged':before == after, - 'expected_test_methods':74, - 'coverage_status':'WITNESSED' if passed else 'NOT_ACCEPTED', - 'source_sha256':hashlib.sha256(json.dumps(before,sort_keys=True,separators=(',',':')).encode()).hexdigest(), - 'source_files':before, - 'coverage':{'binary_messages':8191,'binary_lengths_inclusive':[0,12], - 'assembly_switch_masks':512,'transport_switch_masks':32, - 'valid_transport_masks':28,'incompatible_transport_masks':4, - 'largest_roundtrip_bytes':65536,'separate_process_roundtrip_bytes':1026, - 'byte_values':256,'bit_to_circle_assignments':40320, - 'whole_part_key_views':7,'bit_placements_per_byte':8, - 'message_participants_per_byte':1}, - 'declared_ciphertext_bits_per_byte':16, - 'positional_cipher':'NOT_IMPLEMENTED: byte/star structure repaired; transform proposals await approval', - 'full_weave':Pipeline().plan(), - 'native_corpus_replay':'NOT_EXECUTED: no constructed database materialized by this suite', - 'native_adapter_tests':'missing-input/source-identity refusal only; not successful real-corpus replay', - 'security_observations':[ - 'fixed-map recovery succeeds against the older transport candidate at 137 bits in 8 queries', - 'wrong material can yield wrong plaintext without an authentication error' + 'schema': 'weave.sequence-cycle-suite/v1', + 'status': 'PASSED' if passed else 'FAILED', + 'python': platform.python_version(), + 'tests': result.testsRun, + 'failures': len(result.failures), 'errors': len(result.errors), 'skipped': len(result.skipped), + 'expected_test_methods': EXPECTED_TESTS, + 'source_unchanged': before == after, + 'source_sha256': hashlib.sha256(json.dumps(before, sort_keys=True, separators=(',', ':')).encode()).hexdigest(), + 'source_files': before, + 'native_sources': json.loads((ROOT/'CYCLE_NATIVE.json').read_text()), + 'work_graph_sha256': json.loads((ROOT/'CYCLE_WORK_GRAPH.json').read_text())['work_graph_sha256'], + 'coverage_status': 'WITNESSED' if passed else 'NOT_ACCEPTED', + 'coverage': { + 'old_transport_binary_messages': 8191, 'assembly_switch_masks': 512, + 'transport_switch_masks': 32, 'valid_transport_masks': 28, + 'incompatible_transport_masks': 4, 'old_transport_largest_roundtrip_bytes': 65536, + 'numeral_short_bitblocks': 8191, 'numeral_large_block_bits': 8388608, + 'numeral_repeated_roundtrip_bytes': 4194304, 'numeral_review_regressions': 3, + 'discovery_binary_alphabet_byte_strings': 8191, + 'small_compositions': 4083, 'cycle_largest_roundtrip_bytes': 65536, + 'cycle_sample_rounds': 3, 'native_occurrence_circles': 7, + 'prime_jump_trace': [5381, 53, 241, 1523], + 'native_origin_methods': 13, 'cycle_repair_methods': 12, + 'review_closure_methods': 18, 'final_findings_methods': 9, + 'terminal_record_methods': 9, 'header_admission_methods': 3, + 'numeral_single_replay_methods': 4, 'cheap_admission_methods': 8 + }, + 'cycle': 'EXPLICIT_CANDIDATE: corpus normalization once; native sequence affixiation and prime-derived bit interleave; exact reverse', + 'full_weave': Pipeline().plan(), + 'asymmetric_cipher': 'NOT_IMPLEMENTED: cycle/profile recovery is not a public/private trapdoor', + 'native_language_corpus_replay': 'NOT_EXECUTED: binary construction does not use or replace a language corpus', + 'security_observations': [ + 'The older fixed transport map remains recoverable in its declared attack experiment.', + 'The cycle is deterministic from the same source, corpus and profile; syntax/digest checks are not authentication.' ], - 'retired_substitutions':{ - 'stack_pr_73':'REMOVED: per-bit two-sheet encoding was not the specified byte construction', - 'stack_pr_74':'REMOVED: bit-axis public feedback was not the specified whole-byte coupling', - 'evidence_scope':'their attacks concern those rejected implementations, not Weave', - 'private_perturbation_correction':'unchanged public encryption inputs cannot establish private-key noncausality' + 'retired_substitutions': { + 'stack_pr_73': 'REMOVED: per-bit public sheet substitution', + 'stack_pr_74': 'REMOVED: bit-axis public feedback', + 'stack_pr_76': 'SUPERSEDED: one-bit-per-circle byte records replaced by native sequence-cycle construction', + 'evidence_scope': 'Rejected encoders do not falsify the corrected sequence construction.' }, - 'nonclaim':'Construction-source recovery is not decryption. No native Weave security result.' + 'nonclaim': 'No fixed total expansion, universal compression, cryptographic strength, or language-graduation result.' } sys.stderr.write(report.getvalue()) if args.receipt: - args.receipt.parent.mkdir(parents=True,exist_ok=True) - args.receipt.write_text(json.dumps(receipt,indent=2)+'\n') - print(json.dumps({key:receipt[key] for key in ('status','tests','failures','errors','skipped','source_sha256')})) + args.receipt.parent.mkdir(parents=True, exist_ok=True) + args.receipt.write_text(json.dumps(receipt, indent=2)+'\n') + print(json.dumps({k: receipt[k] for k in ('status','tests','failures','errors','skipped','source_sha256')})) return 0 if passed else 1 -if __name__=='__main__':raise SystemExit(main()) +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/research/weave/tests/test_admission_order.py b/research/weave/tests/test_admission_order.py new file mode 100644 index 00000000..38a25f04 --- /dev/null +++ b/research/weave/tests/test_admission_order.py @@ -0,0 +1,159 @@ +# === CHECKS === +# id: numeral_metadata_before_prime_work +# proves: numeral_strict_input +# call: self::test_recipe_metadata_rejected_before_any_replay +# id: numeral_late_metadata_before_prime_work +# proves: numeral_strict_input +# call: self::test_late_entry_failure_does_not_replay_earlier_recipe +# id: numeral_shapes_before_prime_work +# proves: numeral_strict_input, numeral_recipe_replay +# call: self::test_all_recipe_shapes_checked_before_replay +# id: numeral_deferred_same_budget +# proves: numeral_exact_recovery, numeral_recipe_replay +# call: self::test_deferred_decode_replays_once_at_same_limit +# id: normalization_validates_public_profile +# proves: cycle_normalize_once, cycle_strict_refusal +# call: self::test_direct_normalization_rejects_mutated_profiles +# id: normalization_trusted_validation +# proves: cycle_strict_refusal +# call: self::test_normalization_uses_class_validator +# id: affixiation_validates_public_round +# proves: cycle_strict_refusal +# call: self::test_public_affixiation_rejects_mutated_round_before_work +# id: public_helpers_preserve_valid_cycle +# proves: cycle_normalize_once, cycle_reversible_rounds +# call: self::test_valid_helpers_preserve_cycle_output +# === END CHECKS === +"""Run: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_admission_order.py -v. + +Cheap complete input admission precedes prime work. Public normalization and +round helpers validate caller-supplied profiles through trusted class operations. +Nonsecret fixtures only; no mutation of trusted runtime code outside scoped mocks. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +from unittest.mock import patch +import os +import unittest + +import cycle +import native_binary as native +import numeral as n + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ['WEAVE_SOURCES']) +A, B = chr(0xE000), chr(0xE001) + + +def definition(symbol=A, angle=(1,7), circle=1, length=16, seed=101, steps=()): + encoded = (n._blob(symbol.encode()) + n._uint(angle[0])+n._uint(angle[1]) + + n._uint(circle)+n._uint(length)+b'\x01'+n._uint(seed)+n._uint(len(steps))) + for step in steps: + if step == ('next',): encoded += b'\x00' + else: encoded += b'\x01' + b''.join(n._uint(x) for x in step[1:]) + return encoded + + +def wire(rows, symbols=A, declared=16, origin=b'admission'): + return (n.MAGIC+n._blob(origin)+n._uint(0)+n._uint(declared)+n._uint(len(rows)) + + b''.join(rows)+n._blob(symbols.encode())) + + +class AdmissionOrderTests(unittest.TestCase): + def assert_cheap_refusal(self, data): + with patch.object(n.PrimePath,'replay',side_effect=AssertionError('recipe ran before admission')) as replay: + with self.assertRaises(n.Refused) as failure: + n.decode(data,n.Limits(prime_work=4)) + self.assertNotIsInstance(failure.exception,n.ResourceLimit) + replay.assert_not_called() + + def test_recipe_metadata_rejected_before_any_replay(self): + cases=(wire([definition(circle=0)]),wire([definition(circle=8)]), + wire([definition(angle=(2,1))]),wire([definition(angle=(2,2))]), + wire([definition(length=0)]),wire([definition()],origin=b''), + wire([definition()],symbols=B),wire([definition()],declared=17), + wire([definition()])+b'extra',wire([definition()])[:-1]) + for i,data in enumerate(cases): + with self.subTest(case=i):self.assert_cheap_refusal(data) + + def test_late_entry_failure_does_not_replay_earlier_recipe(self): + cases=(definition(symbol=B,circle=0),definition(symbol=B,angle=(2,1)), + definition(symbol=A,angle=(2,7)),definition(symbol=B,angle=(1,7)), + definition(symbol=B,angle=(2,7),length=0)) + for i,second in enumerate(cases): + with self.subTest(case=i):self.assert_cheap_refusal(wire([definition(),second],symbols=A+B,declared=32)) + self.assert_cheap_refusal(wire([definition(),definition(symbol=B,angle=(2,7))],symbols=A+B,declared=31)) + + def test_all_recipe_shapes_checked_before_replay(self): + cases=(dict(seed=1),dict(steps=(('span',3,0,1),)),dict(steps=(('span',10,0,0),))) + for i,changes in enumerate(cases): + data=wire([definition(),definition(symbol=B,angle=(2,7),**changes)],symbols=A+B,declared=32) + with self.subTest(case=i):self.assert_cheap_refusal(data) + # A malformed late step in a public recipe also refuses before the first + # expensive nth-prime operation or prime-work charge. + path=n.PrimePath(101,(('next',),('span',3,0,1))) + with patch.object(n._Primes,'charge',side_effect=AssertionError('charged before shape admission')): + with self.assertRaises(n.Refused) as failure:n.PrimePath.replay(path,n.Limits(prime_work=1)) + self.assertNotIsInstance(failure.exception,n.ResourceLimit) + + def test_deferred_decode_replays_once_at_same_limit(self): + limits=n.Limits(prime_work=10) + data=wire([definition(),definition(symbol=B,angle=(2,7),seed=103)],symbols=A+B,declared=32) + engine=n._Primes(limits) + with patch.object(n.PrimePath,'replay',autospec=True,side_effect=n.PrimePath.replay) as replay: + packet=n.decode(data,limits,_engine=engine) + self.assertEqual(replay.call_count,2) + self.assertEqual(engine.work,10) + self.assertEqual(n.encode(packet,limits),data) + self.assertEqual(n.Packet.restore(packet,limits).to_bytes(),b'\x00e\x00g') + with self.assertRaises(n.ResourceLimit):n.decode(data,n.Limits(prime_work=9)) + + def profile(self): + return cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) + + def test_direct_normalization_rejects_mutated_profiles(self): + original=self.profile();frame=cycle.normalize(b'AB',b'corpus',original) + for field,value in (('bucket_bytes',0),('bucket_bytes',True),('corpus_bit_offset',True), + ('corpus_bit_offset',-1),('scope',''),('rounds',[])): + p=self.profile();object.__setattr__(p,field,value) + with self.subTest(field=field),self.assertRaises(n.Refused):cycle.normalize(b'AB',b'corpus',p) + with self.subTest(field=field),self.assertRaises(n.Refused):cycle.denormalize(frame,b'corpus',p) + p=self.profile();object.__setattr__(p.rounds[0],'circle_order',(1,)*7) + with self.assertRaises(n.Refused):cycle.normalize(b'AB',b'corpus',p) + with self.assertRaises(n.Refused):cycle.denormalize(frame,b'corpus',p) + + def test_normalization_uses_class_validator(self): + p=self.profile();called=[] + object.__setattr__(p,'__post_init__',lambda:called.append(True)) + frame=cycle.normalize(b'AB',b'corpus',p) + self.assertEqual(cycle.denormalize(frame,b'corpus',p),b'AB') + self.assertEqual(called,[]) + object.__setattr__(p,'corpus_bit_offset',True) + for operation,args in ((cycle.normalize,(b'AB',b'corpus',p)),(cycle.denormalize,(frame,b'corpus',p))): + with self.assertRaises(n.Refused):operation(*args) + self.assertEqual(called,[]) + + def test_public_affixiation_rejects_mutated_round_before_work(self): + for field,value in (('circle_order',(1,)*7),('spaces',()),('arities',(True,)),('end_order','unknown')): + spec=self.profile().rounds[0];object.__setattr__(spec,field,value) + kwargs=dict(api=None,geometry=None,scope='x',root='0'*64,round_id=0,spec=spec) + with self.subTest(field=field),patch.object(cycle,'discover',side_effect=AssertionError('discovery before round admission')): + with self.assertRaises(n.Refused):cycle.affix(b'ABAB',**kwargs) + with self.subTest(field=field),patch.object(cycle,'decode_numeral',side_effect=AssertionError('decode before round admission')): + with self.assertRaises(n.Refused):cycle.unaffix(cycle.AFFIX_MAGIC+b'\x00',**kwargs) + + def test_valid_helpers_preserve_cycle_output(self): + p=self.profile();g=native.Geometry(SOURCES/'ucns') + for offset in (0,1,7,8,19): + profile=replace(p,corpus_bit_offset=offset) + for data in (b'',b'ABxABy',bytes(range(256))): + frame=cycle.normalize(data,b'corpus',profile) + self.assertEqual(cycle.denormalize(frame,b'corpus',profile),data) + data=b'ABxABy';root=native.ByteOrigin(data,p.scope,0,g).message_origin + kwargs=dict(api=native,geometry=g,scope=p.scope,root=root,round_id=0,spec=p.rounds[0]) + record,_=cycle.affix(data,**kwargs) + self.assertEqual(cycle.unaffix(record,**kwargs),data) + + +if __name__=='__main__':unittest.main() diff --git a/research/weave/tests/test_binary_origin.py b/research/weave/tests/test_binary_origin.py new file mode 100644 index 00000000..e2f704c7 --- /dev/null +++ b/research/weave/tests/test_binary_origin.py @@ -0,0 +1,246 @@ +# === CHECKS === +# id: check_all_256_values_use_byte_axes_not_bit_axes +# proves: binary_origin_byte_axes +# call: self::test_all_256_values_use_byte_axes_not_bit_axes +# mutates: none +# cleanup: none +# +# id: check_equal_values_preserve_occurrences +# proves: binary_origin_byte_axes +# call: self::test_equal_values_preserve_occurrences +# mutates: none +# cleanup: none +# +# id: check_repeat_and_residual_closure +# proves: binary_sequence_closure +# call: self::test_repeat_and_residual_closure +# mutates: none +# cleanup: none +# +# id: check_native_720_return +# proves: binary_native_geometry +# call: self::test_native_720_return +# mutates: none +# cleanup: none +# +# id: check_space_changes_actual_positions_not_labels +# proves: binary_native_geometry +# call: self::test_space_changes_actual_positions_not_labels +# mutates: none +# cleanup: none +# +# id: check_recover_from_circle_positions +# proves: binary_coordinate_recovery, binary_sequence_closure +# call: self::test_recover_from_circle_positions +# mutates: none +# cleanup: none +# +# id: check_all_seven_circles_participate +# proves: binary_coordinate_recovery, binary_sequence_closure +# call: self::test_all_seven_circles_participate +# mutates: none +# cleanup: none +# +# id: check_round_context_changes_frame_not_message +# proves: binary_origin_byte_axes +# call: self::test_round_context_changes_frame_not_message +# mutates: none +# cleanup: none +# +# id: check_leading_zero_and_empty_sources +# proves: binary_sequence_closure +# call: self::test_leading_zero_and_empty_sources +# mutates: none +# cleanup: none +# +# id: check_bad_partitions_refuse +# proves: binary_source_refusal +# call: self::test_bad_partitions_refuse +# mutates: none +# cleanup: none +# +# id: check_bad_coordinate_counts_gaps_and_frames_refuse +# proves: binary_source_refusal +# call: self::test_bad_coordinate_counts_gaps_and_frames_refuse +# mutates: none +# cleanup: none +# +# id: check_changed_native_source_refuses_before_execution +# proves: binary_source_refusal +# call: self::test_changed_native_source_refuses_before_execution +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_work_graph_identity +# proves: binary_source_refusal +# call: self::test_work_graph_identity +# mutates: none +# cleanup: none +# === END CHECKS === +"""Run: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_binary_origin.py. + +The tests execute the pinned native UCNS modules, never fabricated circle classes. +All writes are confined to temporary directories. No network or corpus database. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +from hashlib import sha256 +import json +import os +import tempfile +import unittest +from native_binary import * + +UCNS = Path(os.environ['WEAVE_SOURCES'])/'ucns' +SPACES = tuple(Fraction(i,9) for i in range(8)) + + +class BinaryOriginTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.geometry = Geometry(UCNS) + + def fixture(self, data=b'ABxABy'): + origin = ByteOrigin(data,'native-test',0,self.geometry) + return close_sequences(origin,(b'AB',b'x',b'y'),(0,1,0,2),(1,2,3),SPACES) + + def replay(self, table, **changes): + wire = table.wire_occurrences() + kwargs = dict(scope=table.origin.scope,message_origin=table.origin.message_origin, + round_id=table.origin.round_id,origin_identity=table.origin.identity, + byte_length=len(table.origin.source),blocks=tuple(d.data for d in table.definitions), + circles=tuple(d.occurrences[0].circle for d in table.definitions),spaces=SPACES, + counts=tuple(sum(o.circle==c for _,o in wire) for c in range(1,8)), + wire_order=tuple(i for i,_ in wire), + source_turns=tuple(self.geometry.lift(o.source_state) for _,o in wire)) + kwargs.update(changes) + return recover_sequences(self.geometry,**kwargs) + + def test_all_256_values_use_byte_axes_not_bit_axes(self): + origin = ByteOrigin(bytes(range(256)),'all-bytes',0,self.geometry) + identities = set() + for i in range(256): + axis = origin.byte_axis(i) + self.assertIsInstance(axis,self.geometry.axis_module.AxisCirclePosition) + self.assertEqual(axis.axis_count,256) + self.assertEqual(axis.axis_ordinal,i) + self.assertEqual(axis.turn,Fraction(i,256)) + identities.add(axis.identity_sha256) + self.assertEqual(len(identities),256) + with self.assertRaises(BinaryError): + origin.byte_axis(256) + + def test_equal_values_preserve_occurrences(self): + origin = ByteOrigin(b'AA','same-byte',0,self.geometry) + self.assertEqual(len(origin.source),2) + self.assertNotEqual(origin.byte_axis(0),origin.byte_axis(1)) + table = close_sequences(origin,(b'A',),(0,0),(1,),SPACES) + self.assertEqual(tuple(o.source_offset for o in table.definitions[0].occurrences),(0,1)) + + def test_repeat_and_residual_closure(self): + table = self.fixture() + self.assertEqual(table.restore(),b'ABxABy') + self.assertTrue(table.definitions[0].repeated) + self.assertFalse(table.definitions[1].repeated) + self.assertEqual(tuple(o.source_offset for o in table.definitions[0].occurrences),(0,3)) + self.assertEqual(table.definitions[1].attachment_axis.turn,Fraction(1,3)) + + def test_native_720_return(self): + table = self.fixture() + state = table.definitions[0].state + self.assertIsInstance(state,self.geometry.mobius_module.NativeMobiusState) + self.assertNotEqual(state.advance(1).complete_key,state.complete_key) + self.assertEqual(state.advance(1).visible_key,state.visible_key) + self.assertEqual(state.advance(2).complete_key,state.complete_key) + + def test_space_changes_actual_positions_not_labels(self): + table = self.fixture() + origin = table.origin + other = close_sequences(origin,(b'AB',b'x',b'y'),(0,1,0,2),(1,2,3),(Fraction(0),)*8) + self.assertEqual(other.restore(),table.restore()) + self.assertNotEqual(tuple(self.geometry.lift(o.source_state) for _,o in other.wire_occurrences()), + tuple(self.geometry.lift(o.source_state) for _,o in table.wire_occurrences())) + + def test_recover_from_circle_positions(self): + table = self.fixture() + rebuilt = self.replay(table) + self.assertEqual(rebuilt.receipt(),table.receipt()) + self.assertEqual(rebuilt.restore(),b'ABxABy') + # Circle 1's space relation moves its second occurrence ahead of its first. + self.assertEqual(tuple(o.source_offset for _,o in table.wire_occurrences())[:2],(3,0)) + + def test_all_seven_circles_participate(self): + blocks = tuple(bytes([i,0,i]) for i in range(7)) + order = tuple(range(7))*3 + origin = ByteOrigin(b''.join(blocks[i] for i in order),'seven',0,self.geometry) + table = close_sequences(origin,blocks,order,tuple(range(1,8)),SPACES) + self.assertEqual({o.circle for _,o in table.wire_occurrences()},set(range(1,8))) + self.assertEqual(self.replay(table).restore(),origin.source) + + def test_round_context_changes_frame_not_message(self): + a = ByteOrigin(b'AB','one',0,self.geometry) + b = ByteOrigin(b'BA','one',1,self.geometry,a.message_origin) + self.assertEqual(a.message_origin,b.message_origin) + self.assertNotEqual(a.identity,b.identity) + self.assertNotEqual(a.byte_axis(0),b.byte_axis(0)) + with self.assertRaises(BinaryError): + ByteOrigin(b'AB','one',0,self.geometry,'0'*64) + with self.assertRaises(BinaryError): + ByteOrigin(b'AB','one',1,self.geometry) + + def test_leading_zero_and_empty_sources(self): + data=b'\x00\x00A\x00\x00A' + origin=ByteOrigin(data,'zeros',0,self.geometry) + table=close_sequences(origin,(b'\x00\x00A',),(0,0),(1,),SPACES) + self.assertEqual(self.replay(table).restore(),data) + empty=ByteOrigin(b'','empty',0,self.geometry) + self.assertEqual(close_sequences(empty,(),(),(),SPACES).restore(),b'') + with self.assertRaises(BinaryError): + empty.byte_axis(0) + + def test_bad_partitions_refuse(self): + o=ByteOrigin(b'ABAB','bad',0,self.geometry) + bad=(( (b'AB',),(0,),(1,),SPACES), + ( (b'AB',b'AB'),(0,1),(1,2),SPACES), + ( (b'AB',),(0,0),(0,),SPACES), + ( (b'AB',),(True,0),(1,),SPACES), + ( (b'AB',),[0,0],(1,),SPACES), + ( (b'AB',),(0,0),(1,),(0,)*8)) + for args in bad: + with self.assertRaises(BinaryError): + close_sequences(o,*args) + + def test_bad_coordinate_counts_gaps_and_frames_refuse(self): + t=self.fixture() + wire=t.wire_occurrences() + positions=tuple(self.geometry.lift(o.source_state) for _,o in wire) + for changes in (dict(counts=(0,)*7),dict(source_turns=(Fraction(1,11),)+positions[1:]), + dict(source_turns=(positions[1],)+positions[1:]),dict(max_bytes=5), + dict(source_turns=(positions[0]+1,)+positions[1:])): + with self.assertRaises(ValueError): + self.replay(t,**changes) + + def test_work_graph_identity(self): + path = Path(__file__).resolve().parents[1]/'CYCLE_WORK_GRAPH.json' + record = json.loads(path.read_text()) + payload = {k:record[k] for k in ('repositories','boundaries')} + digest = sha256(json.dumps(payload,sort_keys=True,separators=(',',':')).encode()).hexdigest() + self.assertEqual(digest,record['work_graph_sha256']) + entries = {e['repository']:e for e in record['repositories']} + self.assertEqual(entries['The-Interdependency/ucns']['commit'],UCNS_COMMIT) + self.assertFalse(record['boundaries']['authority_transfer']) + self.assertTrue(record['boundaries']['hmmm']) + + def test_changed_native_source_refuses_before_execution(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory);path=root/'src/ucns';path.mkdir(parents=True) + marker=root/'executed' + (path/'axis_circle.py').write_text(f"open({str(marker)!r},'w').write('bad')") + with self.assertRaises(BinaryError): + Geometry(root) + self.assertFalse(marker.exists()) + + +if __name__=='__main__': + unittest.main() diff --git a/research/weave/tests/test_cycle.py b/research/weave/tests/test_cycle.py new file mode 100644 index 00000000..dbd6901b --- /dev/null +++ b/research/weave/tests/test_cycle.py @@ -0,0 +1,515 @@ +# === CHECKS === +# id: check_all_8191_short_binary_alphabet_byte_strings +# proves: discovery_exact_partition, discovery_repeat_selection +# call: self::test_all_8191_short_binary_alphabet_byte_strings +# mutates: none +# cleanup: none +# +# id: check_repeated_multibyte_and_residuals +# proves: discovery_exact_partition, discovery_repeat_selection +# call: self::test_repeated_multibyte_and_residuals +# mutates: none +# cleanup: none +# +# id: check_competing_overlaps_are_deterministic +# proves: discovery_exact_partition, discovery_repeat_selection +# call: self::test_competing_overlaps_are_deterministic +# mutates: none +# cleanup: none +# +# id: check_nonrepeated_and_single_byte_remain_literal +# proves: discovery_exact_partition, discovery_repeat_selection +# call: self::test_nonrepeated_and_single_byte_remain_literal +# mutates: none +# cleanup: none +# +# id: check_discovery_budget_refuses_not_truncates +# proves: discovery_resource_refusal +# call: self::test_discovery_budget_refuses_not_truncates +# mutates: none +# cleanup: none +# +# id: check_normalization_exact_bucket_and_corpus_bits +# proves: cycle_normalize_once +# call: self::test_normalization_exact_bucket_and_corpus_bits +# mutates: none +# cleanup: none +# +# id: check_exact_bucket_input_still_uses_corpus +# proves: cycle_normalize_once +# call: self::test_exact_bucket_input_still_uses_corpus +# mutates: none +# cleanup: none +# +# id: check_normalization_source_zeroes_and_empty +# proves: cycle_normalize_once +# call: self::test_normalization_source_zeroes_and_empty +# mutates: none +# cleanup: none +# +# id: check_wrong_corpus_and_padding_are_rejected +# proves: cycle_normalize_once +# call: self::test_wrong_corpus_and_padding_are_rejected +# mutates: none +# cleanup: none +# +# id: check_all_small_composition_ranks +# proves: prime_split_replay +# call: self::test_all_small_composition_ranks +# mutates: none +# cleanup: none +# +# id: check_actual_prime_recursion_and_jumps +# proves: prime_split_replay +# call: self::test_actual_prime_recursion_and_jumps +# mutates: none +# cleanup: none +# +# id: check_route_occurrence_identity_is_not_destination_only +# proves: prime_split_replay +# call: self::test_route_occurrence_identity_is_not_destination_only +# mutates: none +# cleanup: none +# +# id: check_split_controls_replay_without_source +# proves: prime_split_replay +# call: self::test_split_controls_replay_without_source +# mutates: none +# cleanup: none +# +# id: check_both_end_orders_preserve_each_bit +# proves: first_last_inverse +# call: self::test_both_end_orders_preserve_each_bit +# mutates: none +# cleanup: none +# +# id: check_all_256_byte_values_interleave_inverse +# proves: first_last_inverse +# call: self::test_all_256_byte_values_interleave_inverse +# mutates: none +# cleanup: none +# +# id: check_invalid_split_parameters_refused +# proves: first_last_inverse +# call: self::test_invalid_split_parameters_refused +# mutates: none +# cleanup: none +# +# id: check_native_objects_and_720_return_are_used +# proves: cycle_native_occurrence_recovery +# call: self::test_native_objects_and_720_return_are_used +# mutates: none +# cleanup: none +# +# id: check_native_affix_and_coordinate_recovery +# proves: cycle_native_occurrence_recovery +# call: self::test_native_affix_and_coordinate_recovery +# mutates: none +# cleanup: none +# +# id: check_spaces_are_effective_native_inputs +# proves: cycle_native_occurrence_recovery +# call: self::test_spaces_are_effective_native_inputs +# mutates: none +# cleanup: none +# +# id: check_one_round_empty_and_all_byte_values +# proves: cycle_reversible_rounds +# call: self::test_one_round_empty_and_all_byte_values +# mutates: none +# cleanup: none +# +# id: check_three_round_cycle_exact_and_once_normalized +# proves: cycle_reversible_rounds +# call: self::test_three_round_cycle_exact_and_once_normalized +# mutates: none +# cleanup: none +# +# id: check_seeded_random_mixed_data_two_rounds +# proves: cycle_reversible_rounds +# call: self::test_seeded_random_mixed_data_two_rounds +# mutates: none +# cleanup: none +# +# id: check_65536_byte_declared_case_roundtrip +# proves: cycle_reversible_rounds +# call: self::test_65536_byte_declared_case_roundtrip +# mutates: none +# cleanup: none +# +# id: check_every_outer_truncation_and_trailing_bytes_refused +# proves: cycle_strict_refusal +# call: self::test_every_outer_truncation_and_trailing_bytes_refused +# mutates: none +# cleanup: none +# +# id: check_profile_material_native_identity_mismatches_refused +# proves: cycle_native_occurrence_recovery +# call: self::test_profile_material_native_identity_mismatches_refused +# mutates: none +# cleanup: none +# +# id: check_profile_strict_fields_and_types +# proves: cycle_strict_refusal +# call: self::test_profile_strict_fields_and_types +# mutates: none +# cleanup: none +# +# id: check_cycle_budgets_fail_without_partial_results +# proves: cycle_strict_refusal +# call: self::test_cycle_budgets_fail_without_partial_results +# mutates: none +# cleanup: none +# +# id: check_source_tampering_refused_before_execution +# proves: cycle_native_source_pinned +# call: self::test_source_tampering_refused_before_execution +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_cli_fresh_process_source_deleted_and_no_overwrite +# proves: cycle_reversible_rounds +# call: self::test_cli_fresh_process_source_deleted_and_no_overwrite +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_profile_roundtrip_and_all_accounting_fields +# proves: cycle_complete_accounting +# call: self::test_profile_roundtrip_and_all_accounting_fields +# mutates: none +# cleanup: none +# +# === END CHECKS === +"""Run: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_cycle.py -v. + +The suite exercises the declared complete sequence cycle and real locked producers. +It is not a test of public-key secrecy. Files are isolated in temporary directories. +""" +from copy import deepcopy +from dataclasses import replace +from fractions import Fraction +from itertools import combinations +from math import comb +from pathlib import Path +import json +import os +import random +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +from affixiation import discover +from prime_schedule import Route, SplitPlan, plan, interleave, _composition +from numeral import PrimePath, Refused, ResourceLimit +import cycle +from cycle import Profile, CycleLimits, normalize, denormalize, affix, unaffix, forward, reverse +from cycle_native import load_native +import cycle_native + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ.get('WEAVE_SOURCES', ROOT/'sources')) +PROFILE = Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) +CORPUS = bytes(range(256)) + b'actual corpus bytes' + + +def one_round(): + return replace(PROFILE, rounds=PROFILE.rounds[:1]) + + +class CycleTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.api, cls.geometry = load_native(SOURCES) + + def test_all_8191_short_binary_alphabet_byte_strings(self): + count = 0 + for n in range(13): + for value in range(1 << n): + data = bytes((value >> i) & 1 for i in range(n)) + part = discover(data) + self.assertEqual(part.restore(), data) + self.assertEqual(part.starts, tuple(sum(len(part.blocks[j]) for j in part.order[:i]) + for i in range(len(part.order)))) + count += 1 + self.assertEqual(count, 8191) + + def test_repeated_multibyte_and_residuals(self): + p = discover(b'ABxABy') + self.assertEqual(p.blocks, (b'AB', b'x', b'y')) + self.assertEqual(p.order, (0,1,0,2)) + self.assertEqual(p.starts, (0,2,3,5)) + self.assertEqual(p.repeat_ids, (0,)) + + def test_competing_overlaps_are_deterministic(self): + for data in (b'AAAAA', b'ABABABA', b'banana bandana banana', b'abcabcabcab'): + p = discover(data) + self.assertEqual(p, discover(data)) + self.assertEqual(p.restore(), data) + for a,b,i in zip(p.starts,p.starts[1:],p.order): + self.assertLessEqual(a+len(p.blocks[i]), b) + self.assertEqual(discover(b'AAAAA').blocks, (b'AA', b'A')) + self.assertEqual(discover(b'AAAAA').order, (0,0,1)) + + def test_nonrepeated_and_single_byte_remain_literal(self): + data = bytes(range(256)) + self.assertEqual(discover(data).blocks, (data,)) + self.assertEqual(discover(b'x').blocks, (b'x',)) + self.assertEqual(discover(b'').restore(), b'') + self.assertEqual(discover(b'abaca').repeat_ids, ()) + + def test_discovery_budget_refuses_not_truncates(self): + with self.assertRaises(ResourceLimit): + discover(b'ABABAB',visit_budget=1) + with self.assertRaises(ResourceLimit): + discover(b'ABABAB',max_bytes=5) + for bad in ('abc', bytearray(b'abc'), None): + with self.assertRaises(Refused): discover(bad) + + def test_normalization_exact_bucket_and_corpus_bits(self): + profile = one_round() + data = normalize(b'abc', CORPUS, profile) + self.assertEqual(len(data), 256) + bits = ''.join(format(x,'08b') for x in CORPUS) + n = len(data)-47 + expected = ''.join(bits[(profile.corpus_bit_offset+i)%len(bits)] for i in range(n*8)) + self.assertEqual(data[47:], int(expected,2).to_bytes(n,'big')) + self.assertEqual(denormalize(data,CORPUS,profile),b'abc') + + def test_exact_bucket_input_still_uses_corpus(self): + profile = one_round() + data = normalize(b'x'*212,b'\xa5',profile) + self.assertEqual(len(data),512) + self.assertEqual(denormalize(data,b'\xa5',profile),b'x'*212) + + def test_normalization_source_zeroes_and_empty(self): + for raw in (b'',b'\x00',b'\x00\x00\x01',bytes(range(256))): + for offset in (0,1,7,8,15,100003): + p = replace(one_round(),corpus_bit_offset=offset) + self.assertEqual(denormalize(normalize(raw,CORPUS,p),CORPUS,p),raw) + + def test_wrong_corpus_and_padding_are_rejected(self): + profile = one_round() + data = normalize(b'abc',CORPUS,profile) + with self.assertRaises(Refused): denormalize(data,CORPUS+b'!',profile) + with self.assertRaises(Refused): denormalize(data[:-1]+bytes([data[-1]^1]),CORPUS,profile) + with self.assertRaises(Refused): normalize(b'abc',b'',profile) + with self.assertRaises(ResourceLimit): normalize(b'abc',CORPUS,profile,CycleLimits(round_bytes=100)) + + def test_all_small_composition_ranks(self): + cases = 0 + for n in range(2,13): + for arity in range(2,n+1): + for rank,gaps in enumerate(combinations(range(1,n),arity-1)): + points = (0,*gaps,n) + self.assertEqual(_composition(n,arity,rank),tuple(b-a for a,b in zip(points,points[1:]))) + cases += 1 + self.assertEqual(cases,4083) + + def test_actual_prime_recursion_and_jumps(self): + route = Route.evaluate(PROFILE.rounds[0].path) + self.assertEqual(route.trace,(5381,53,241,1523)) + self.assertEqual(Route.evaluate(PrimePath(2, (('next',),)*7)).trace, + (2,3,5,11,31,127,709,5381)) + self.assertEqual(Route.evaluate(PrimePath(5381,(('span',2,0,3),))).trace,(5381,5)) + + def test_route_occurrence_identity_is_not_destination_only(self): + a = Route.evaluate(PrimePath(313,(('span',10,0,1),))) + b = Route.evaluate(PrimePath(313,(('span',10,2,1),))) + self.assertEqual(a.trace,b.trace) + self.assertNotEqual(a.determinant,b.determinant) + self.assertNotEqual(plan(1024,a,(3,5,7)),plan(1024,b,(3,5,7))) + + def test_split_controls_replay_without_source(self): + route = Route.evaluate(PROFILE.rounds[0].path) + for n in (8,16,128,1024,65536): + split = plan(n,route,(3,5,7)) + self.assertEqual(sum(split.lengths),n) + self.assertTrue(all(x>0 for x in split.lengths)) + self.assertEqual(plan(n,route,(3,5,7)),split) + self.assertLess(split.rank,comb(n-1,len(split.lengths)-1)) + + def test_both_end_orders_preserve_each_bit(self): + split = SplitPlan(24,(7,5,12),0) + for end in ('first-last','last-first'): + mapped=[] + for index in range(24): + data = (1 << (23-index)).to_bytes(3,'big') + out = interleave(data,split,end_order=end) + self.assertEqual(int.from_bytes(out,'big').bit_count(),1) + self.assertEqual(interleave(out,split,inverse=True,end_order=end),data) + mapped.append(out) + self.assertEqual(len(set(mapped)),24) + + def test_all_256_byte_values_interleave_inverse(self): + for v in range(256): + data = bytes([v]) + for end in ('first-last','last-first'): + for split in (SplitPlan(8,(1,7),0),SplitPlan(8,(3,2,3),0)): + self.assertEqual(interleave(interleave(data,split,end_order=end),split,inverse=True,end_order=end),data) + + def test_invalid_split_parameters_refused(self): + route = Route.evaluate(PrimePath(53,())) + for arities in ((1,), (9,), (3,3), (True,), ([3],), [3]): + with self.assertRaises(Refused): plan(8,route,arities) + for lengths in ((3,3),(0,8),(True,7),(-1,9)): + with self.assertRaises(Refused): interleave(b'a',SplitPlan(8,lengths,0)) + + def test_native_objects_and_720_return_are_used(self): + origin = self.api.ByteOrigin(b'ABxABy','native-test',0,self.geometry) + axis = origin.byte_axis(3) + self.assertEqual(type(axis).__name__,'AxisCirclePosition') + self.assertEqual(axis.turn,Fraction(1,2)) + state = self.geometry.placed(axis,Fraction(1,7)) + self.assertEqual(type(state).__name__,'NativeMobiusState') + self.assertNotEqual(state.complete_key,state.advance(1).complete_key) + self.assertEqual(state.complete_key,state.advance(2).complete_key) + + def test_native_affix_and_coordinate_recovery(self): + raw = b'ABxABy' + origin = self.api.ByteOrigin(raw,'native-test',0,self.geometry) + spec = PROFILE.rounds[0] + wire,stats = affix(raw,api=self.api,geometry=self.geometry,scope=origin.scope, + root=origin.message_origin,round_id=0,spec=spec) + restored = unaffix(wire,api=self.api,geometry=self.geometry,scope=origin.scope, + root=origin.message_origin,round_id=0,spec=spec) + self.assertEqual(restored,raw) + self.assertEqual(stats['repeated_definitions'],1) + self.assertEqual(stats['occurrences'],4) + self.assertEqual(stats['output_bytes'],stats['numeral_bytes']+stats['native_coordinate_and_frame_bytes']) + + def test_spaces_are_effective_native_inputs(self): + raw = b'ABxABy' + root = self.api.ByteOrigin(raw,'native-test',0,self.geometry).message_origin + a = PROFILE.rounds[0] + b = replace(a,spaces=tuple((x+Fraction(1,3))%2 for x in a.spaces)) + kwargs = dict(api=self.api,geometry=self.geometry,scope='native-test',root=root,round_id=0) + wa,_ = affix(raw,spec=a,**kwargs) + wb,_ = affix(raw,spec=b,**kwargs) + self.assertNotEqual(wa,wb) + self.assertEqual(unaffix(wa,spec=a,**kwargs),raw) + self.assertEqual(unaffix(wb,spec=b,**kwargs),raw) + with self.assertRaises(ValueError): unaffix(wa,spec=b,**kwargs) + + def test_one_round_empty_and_all_byte_values(self): + for raw in (b'',b'\x00',b'\xff',bytes(range(256)),b'ababababcabc'): + wire,stats = forward(raw,CORPUS,one_round(),SOURCES) + self.assertEqual(reverse(wire,CORPUS,one_round(),SOURCES),raw) + self.assertEqual(len(stats['rounds']),1) + + def test_three_round_cycle_exact_and_once_normalized(self): + raw = b'Weave repeats byte sequences. '*24 + bytes(range(64)) + with patch.object(cycle,'normalize',wraps=normalize) as call: + wire,stats = forward(raw,CORPUS,PROFILE,SOURCES) + self.assertEqual(call.call_count,1) + self.assertEqual(reverse(wire,CORPUS,PROFILE,SOURCES),raw) + self.assertEqual(len(stats['rounds']),3) + for left,right in zip(stats['rounds'],stats['rounds'][1:]): + self.assertEqual(left['output_bytes'],right['input_bytes']) + self.assertEqual(stats['total_bytes'],len(wire)) + self.assertEqual(stats['final_payload_bytes']+stats['outer_frame_bytes'],len(wire)) + + def test_seeded_random_mixed_data_two_rounds(self): + rng = random.Random(20261005) + p = replace(PROFILE,rounds=PROFILE.rounds[:2]) + for n in (0,7,31,128,511): + raw = rng.randbytes(n)+b'\x00xy\x00xy'*7 + wire,_ = forward(raw,CORPUS,p,SOURCES) + self.assertEqual(reverse(wire,CORPUS,p,SOURCES),raw) + + def test_65536_byte_declared_case_roundtrip(self): + raw = bytes(range(256))*256 + p = replace(PROFILE,bucket_bytes=4096,rounds=PROFILE.rounds[:2]) + wire,stats = forward(raw,CORPUS,p,SOURCES) + self.assertEqual(reverse(wire,CORPUS,p,SOURCES),raw) + self.assertEqual(stats['source_bytes'],65536) + + def test_every_outer_truncation_and_trailing_bytes_refused(self): + wire,_ = forward(b'ABxABy',CORPUS,one_round(),SOURCES) + for length in range(len(wire)): + with self.assertRaises(ValueError): reverse(wire[:length],CORPUS,one_round(),SOURCES) + with self.assertRaises(Refused): reverse(wire+b'!',CORPUS,one_round(),SOURCES) + + def test_profile_material_native_identity_mismatches_refused(self): + wire,_ = forward(b'test',CORPUS,one_round(),SOURCES) + with self.assertRaises(Refused): reverse(wire,CORPUS,replace(one_round(),scope='other'),SOURCES) + with self.assertRaises(Refused): reverse(wire,CORPUS+b'!',one_round(),SOURCES) + for pos in (4,36,68,100): + damaged = bytearray(wire); damaged[pos] ^= 1 + with self.assertRaises(ValueError): reverse(bytes(damaged),CORPUS,one_round(),SOURCES) + + def test_profile_strict_fields_and_types(self): + obj = PROFILE.as_dict() + cases=[] + c=deepcopy(obj); c['unknown']=True; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['spaces'][0]=[1,0]; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['spaces'][0]=[2,4]; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['arities']=[True]; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['arities']=[[3]]; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['circle_order']=[1]*7; cases.append(c) + c=deepcopy(obj); c['rounds'][0]['end_order']='arbitrary'; cases.append(c) + for c in cases: + with self.assertRaises(ValueError): Profile.read(json.dumps(c).encode()) + with self.assertRaises(Refused): Profile.read(b'{"schema":1,"schema":2}') + with self.assertRaises(ResourceLimit): Profile.read(json.dumps(obj).encode(),CycleLimits(rounds=2)) + + def test_cycle_budgets_fail_without_partial_results(self): + with self.assertRaises(ResourceLimit): forward(b'1234',CORPUS,PROFILE,SOURCES,CycleLimits(input_bytes=3)) + with self.assertRaises(ResourceLimit): forward(b'1234',CORPUS,PROFILE,SOURCES,CycleLimits(rounds=2)) + with self.assertRaises(ResourceLimit): forward(b'1234',CORPUS,PROFILE,SOURCES,CycleLimits(prime_work=1)) + with self.assertRaises(ResourceLimit): forward(b'ABxABy',CORPUS,one_round(),SOURCES,CycleLimits(native_occurrences=1)) + + def test_source_tampering_refused_before_execution(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory) + shutil.copy2(ROOT/'CYCLE_NATIVE.json',root/'CYCLE_NATIVE.json') + marker=root/'executed' + (root/'native_binary.py').write_text(f"open({str(marker)!r},'w').write('oops')\n") + with patch.object(cycle_native,'ROOT',root): + with self.assertRaises(Refused): load_native(SOURCES) + self.assertFalse(marker.exists()) + + def test_cli_fresh_process_source_deleted_and_no_overwrite(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory) + src,wire,out,corpus,profile = (root/x for x in ('source','packet','restored','corpus','profile.json')) + raw=bytes(range(256))*4+b'\x00AB\x00AB'*17 + src.write_bytes(raw);corpus.write_bytes(CORPUS) + profile.write_bytes(cycle.canonical(PROFILE.as_dict())) + args=['--sources',str(SOURCES),'--profile',str(profile),'--corpus',str(corpus)] + command=[sys.executable,str(ROOT/'cycle.py')] + enc=subprocess.run(command+['forward',str(src),str(wire),*args],capture_output=True) + self.assertEqual(enc.returncode,0,enc.stderr.decode()) + src.unlink() + dec=subprocess.run(command+['reverse',str(wire),str(out),*args],capture_output=True) + self.assertEqual(dec.returncode,0,dec.stderr.decode()) + self.assertEqual(out.read_bytes(),raw) + again=subprocess.run(command+['reverse',str(wire),str(out),*args],capture_output=True) + self.assertEqual(again.returncode,2) + self.assertEqual(out.read_bytes(),raw) + + def test_profile_roundtrip_and_all_accounting_fields(self): + lock = json.loads((ROOT/'CYCLE_NATIVE.json').read_text()) + graph = json.loads((ROOT/'CYCLE_WORK_GRAPH.json').read_text()) + payload = {k: graph[k] for k in ('repositories', 'boundaries')} + from hashlib import sha256 + digest = sha256(json.dumps(payload, sort_keys=True, separators=(',', ':')).encode()).hexdigest() + self.assertEqual(digest, graph['work_graph_sha256']) + repositories = {x['repository']: x for x in graph['repositories']} + self.assertEqual(repositories['The-Interdependency/uchc']['commit'], lock['uchc_architecture']['commit']) + self.assertEqual(repositories['The-Interdependency/ucns']['commit'], lock['ucns_commit']) + self.assertEqual(lock['binary_source']['owner'], 'The-Interdependency/stack') + self.assertFalse(graph['boundaries']['authority_transfer']) + self.assertEqual(Profile.read(cycle.canonical(PROFILE.as_dict())),PROFILE) + wire,stats=forward(b'ABCD'*64,CORPUS,PROFILE,SOURCES) + for row in stats['rounds']: + self.assertEqual(row['input_bytes']*8 % 8,0) + self.assertEqual(row['permutation_bit_count'],row['output_bytes']*8) + self.assertEqual(row['numeral_bytes']+row['native_coordinate_and_frame_bytes'],row['output_bytes']) + self.assertEqual(len(wire),stats['total_bytes']) + self.assertEqual(reverse(wire,CORPUS,PROFILE,SOURCES),b'ABCD'*64) + + +if __name__=='__main__': unittest.main() diff --git a/research/weave/tests/test_cycle_repairs.py b/research/weave/tests/test_cycle_repairs.py new file mode 100644 index 00000000..21cf7d7d --- /dev/null +++ b/research/weave/tests/test_cycle_repairs.py @@ -0,0 +1,215 @@ +# === CHECKS === +# id: native_closed_records_reject_forgery +# proves: binary_sequence_closure, binary_coordinate_recovery +# call: self::test_closed_table_rejects_order_source_and_definition_mutations +# id: native_modules_are_fresh +# proves: binary_source_refusal, cycle_native_source_pinned +# call: self::test_geometry_instances_ignore_mutated_earlier_modules +# id: output_write_failures_leave_no_partial_destination +# proves: weave_output_atomic_no_clobber, cycle_strict_refusal +# call: self::test_write_flush_and_close_failures_leave_no_partial_output +# id: empty_attachment_arguments_are_validated +# proves: numeral_strict_input +# call: self::test_empty_bind_rejects_invalid_attachments +# === END CHECKS === +"""Regression replay: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_cycle_repairs.py. + +Temporary data only. These exercise genuine failure modes, not encryption claims. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +from types import ModuleType +from unittest.mock import patch +import contextlib +import io +import json +import os +import subprocess +import sys +import tempfile +import unittest + +import cycle +import cycle_native +import native_binary as native +import safe_output +from numeral import Refused + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ['WEAVE_SOURCES']) +SPACES = tuple(Fraction(i,9) for i in range(8)) + + +class CycleRepairTests(unittest.TestCase): + def fixture(self): + geometry = native.Geometry(SOURCES/'ucns') + origin = native.ByteOrigin(b'ABxABy', 'repair', 0, geometry) + return native.close_sequences(origin, (b'AB', b'x', b'y'), (0,1,0,2), (1,2,3), SPACES) + + def test_closed_table_rejects_order_source_and_definition_mutations(self): + table = self.fixture() + wrong_origin = native.ByteOrigin(b'ABxABy!', 'repair', 0, table.origin.geometry) + changes = (dict(order=()), dict(order=(2,0,1,0)), dict(definitions=list(table.definitions)), + dict(definitions=table.definitions[:-1]), dict(origin=wrong_origin), + dict(spaces=(Fraction(0),)*8)) + for change in changes: + with self.subTest(change=tuple(change)): + with self.assertRaises(native.BinaryError): replace(table, **change) + direct = native.SequenceTable(table.origin, table.definitions, table.order, table.spaces) + self.assertEqual(direct.receipt(), table.receipt()) + + def test_closed_table_rejects_native_axes_frames_and_occurrence_mutations(self): + table = self.fixture() + first = table.definitions[0] + occurrence = first.occurrences[0] + bad_occurrences = ( + replace(occurrence, ordinal=1), + replace(occurrence, source_offset=1), + replace(occurrence, circle=2), + replace(occurrence, source_axis=table.origin.byte_axis(1)), + replace(occurrence, source_state=occurrence.source_state.advance(1)), + replace(occurrence, state=occurrence.state.advance(1)), + ) + bad_definitions = [replace(first, occurrences=(o,)+first.occurrences[1:]) for o in bad_occurrences] + bad_definitions += [replace(first, data=b'ZZ'), replace(first, state=first.state.advance(1)), + replace(first, attachment_axis=table.origin.byte_axis(1)), + replace(first, occurrences=first.occurrences[::-1])] + for definition in bad_definitions: + with self.assertRaises(native.BinaryError): + replace(table, definitions=(definition,)+table.definitions[1:]) + + def test_receipt_and_restore_recheck_even_bypassed_frozen_records(self): + table = self.fixture() + object.__setattr__(table, 'order', ()) + for operation in (table.restore, table.receipt, table.wire_occurrences): + with self.assertRaises(native.BinaryError): operation() + table = self.fixture() + object.__setattr__(table.origin, '_identity', '0'*64) + with self.assertRaises(native.BinaryError): table.receipt() + + def test_empty_and_invalid_native_record_types_refuse(self): + table = self.fixture() + definition = table.definitions[0] + for changes in (dict(data=b''), dict(occurrences=()), dict(occurrences=[])): + with self.assertRaises(native.BinaryError): replace(definition, **changes) + for changes in (dict(circle=True), dict(circle=8), dict(ordinal=-1), dict(source_offset=True)): + with self.assertRaises(native.BinaryError): replace(definition.occurrences[0], **changes) + + def test_geometry_instances_ignore_mutated_earlier_modules(self): + first = native.Geometry(SOURCES/'ucns') + first.axis_module.build_axis_circle_position = lambda **kw: 'not native' + first.mobius_module.native_mobius_state = lambda *a,**kw: 'not native' + poisoned = {'_uchc_ucns_'+sha: ModuleType('poison') for sha in native.UCNS_BLOBS.values()} + with patch.dict(sys.modules, poisoned): + second = native.Geometry(SOURCES/'ucns') + axis = second.axis('0'*64, 8, 3) + state = second.placed(axis,Fraction(1,7)) + self.assertEqual(type(axis).__name__, 'AxisCirclePosition') + self.assertEqual(type(state).__name__, 'NativeMobiusState') + self.assertEqual(axis.turn,Fraction(3,8)) + self.assertIsNot(first.axis_module, second.axis_module) + + def test_cycle_loads_fresh_candidate_and_has_no_uchc_runtime_dependency(self): + api,geometry = cycle_native.load_native(SOURCES) + api.close_sequences = lambda *a,**kw: 'poisoned candidate' + geometry.axis_module.build_axis_circle_position = lambda **kw: 'poisoned geometry' + with tempfile.TemporaryDirectory() as directory: + src = Path(directory) + (src/'ucns').symlink_to(SOURCES/'ucns',target_is_directory=True) + newer,geom = cycle_native.load_native(src) + origin = newer.ByteOrigin(b'ABAB','fresh',0,geom) + self.assertEqual(newer.close_sequences(origin,(b'AB',),(0,0),(1,),SPACES).restore(),b'ABAB') + self.assertIsNot(newer,api) + self.assertFalse(any(name.startswith('_weave_binary_') or name.startswith('_weave_ucns_') for name in sys.modules)) + + def test_write_success_is_owner_only_and_never_overwrites(self): + with tempfile.TemporaryDirectory() as directory: + path=Path(directory)/'out' + safe_output.write_new(path,b'complete') + self.assertEqual(path.read_bytes(),b'complete') + self.assertEqual(path.stat().st_mode & 0o777,0o600) + with self.assertRaises(FileExistsError): safe_output.write_new(path,b'replace') + self.assertEqual(path.read_bytes(),b'complete') + self.assertEqual(list(Path(directory).iterdir()),[path]) + link=Path(directory)/'symlink' + link.symlink_to(path) + with self.assertRaises(FileExistsError): safe_output.write_new(link,b'replace') + self.assertTrue(link.is_symlink()) + self.assertEqual(path.read_bytes(),b'complete') + + def test_write_flush_and_close_failures_leave_no_partial_output(self): + original = os.fdopen + class Broken: + def __init__(self, fd, mode, stage): self.raw,self.stage=original(fd,mode),stage + def __enter__(self): return self + def write(self,data): + if self.stage=='write': + self.raw.write(data[:2]); raise OSError('disk full') + if self.stage=='short': return self.raw.write(data[:2]) + return self.raw.write(data) + def flush(self): + if self.stage=='flush': raise OSError('flush failure') + self.raw.flush() + def fileno(self): return self.raw.fileno() + def __exit__(self,*args): + self.raw.close() + if self.stage=='close': raise OSError('close failure') + for stage in ('write','short','flush','close','fsync'): + with self.subTest(stage=stage),tempfile.TemporaryDirectory() as directory: + path=Path(directory)/'out' + with patch.object(safe_output.os,'fdopen',side_effect=lambda fd,mode:Broken(fd,mode,stage)): + cm=patch.object(safe_output.os,'fsync',side_effect=OSError('quota')) if stage=='fsync' else contextlib.nullcontext() + with cm,self.assertRaises(OSError): safe_output.write_new(path,b'complete record') + self.assertFalse(path.exists()) + self.assertEqual(list(Path(directory).iterdir()),[]) + + def test_atomic_install_failure_preserves_other_files(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory); path=root/'out'; other=root/'other'; other.write_bytes(b'unchanged') + with patch.object(safe_output.os,'link',side_effect=OSError('unsupported atomic link')): + with self.assertRaises(OSError): safe_output.write_new(path,b'complete') + self.assertEqual(list(root.iterdir()),[other]) + self.assertEqual(other.read_bytes(),b'unchanged') + + def test_cli_both_directions_refuse_failed_output_without_partial_files(self): + for command in ('forward','reverse'): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory); src=root/'input'; dst=root/'output'; corpus=root/'corpus' + src.write_bytes(b'input');corpus.write_bytes(b'corpus') + argv=['cycle.py',command,str(src),str(dst),'--corpus',str(corpus)] + effect=(b'full record',{}) if command=='forward' else b'restored' + with patch.object(sys,'argv',argv),patch.object(cycle,command,return_value=effect): + with patch.object(safe_output.os,'fsync',side_effect=OSError('quota')): + with contextlib.redirect_stderr(io.StringIO()),self.assertRaises(SystemExit) as failure: cycle.main() + self.assertEqual(failure.exception.code,2) + self.assertFalse(dst.exists()) + self.assertEqual({p.name for p in root.iterdir()},{'input','corpus'}) + + def test_empty_bind_rejects_invalid_attachments(self): + with tempfile.TemporaryDirectory() as directory: + root=Path(directory);src=root/'empty';src.write_bytes(b'') + for angle,circle in (('99','1'),('1/7','999'),('99','999'),('-1','2')): + out=root/'record' + run=subprocess.run([sys.executable,str(ROOT/'numeral.py'),'bind',str(src),str(out), + '--origin','empty','--angle',angle,'--circle',circle],capture_output=True) + self.assertEqual(run.returncode,2,run.stderr) + self.assertNotIn(b'Traceback',run.stderr) + self.assertFalse(out.exists()) + run=subprocess.run([sys.executable,str(ROOT/'numeral.py'),'bind',str(src),str(root/'valid'), + '--origin','empty','--angle','1/7','--circle','3'],capture_output=True) + self.assertEqual(run.returncode,0,run.stderr) + + def test_native_edges_and_forge_ownership_match_root_manifest(self): + manifest=json.loads((ROOT.parents[1]/'stack-manifest.json').read_text()) + human=(ROOT.parents[1]/'STACK_MANIFEST.md').read_text() + lock=json.loads((ROOT/'CYCLE_NATIVE.json').read_text()) + participants={p['participant_id']:p for p in manifest['research_participants'] if p['workspace']=='research/weave/'} + self.assertEqual(participants['weave-ucns-geometry']['commit'],lock['ucns_commit']) + self.assertEqual(participants['weave-uchc-architecture']['commit'],lock['uchc_architecture']['commit']) + self.assertIn('Stack-owned binary',participants['weave']['relation']) + self.assertIn('native_binary.py',human) + self.assertNotIn('uchc_source',lock) + + +if __name__=='__main__': unittest.main() diff --git a/research/weave/tests/test_eight_circle.py b/research/weave/tests/test_eight_circle.py deleted file mode 100644 index 54076bfa..00000000 --- a/research/weave/tests/test_eight_circle.py +++ /dev/null @@ -1,180 +0,0 @@ -# === CHECKS === -# id: weave_byte_mapping_witness -# proves: weave_byte_has_eight_circle_placements -# call: tests.test_eight_circle.EightCircleTests.test_all_bytes -# id: weave_star_witness -# proves: weave_public_pair_always_contains_whole -# call: tests.test_eight_circle.EightCircleTests.test_all_seven_star_views -# id: weave_no_bit_code_witness -# proves: weave_positions_are_not_bit_codes -# call: tests.test_eight_circle.EightCircleTests.test_removed_bitwise_cipher_surface -# === END CHECKS === -"""Run with: python -m unittest discover -s tests -p test_eight_circle.py. - -Coverage: all 256 byte values, all 40,320 bit/circle assignments, all seven -whole-part views and invalid direct construction. This is not cryptanalysis. -""" -from dataclasses import FrozenInstanceError, replace -from fractions import Fraction -from itertools import permutations -import unittest - -from stages import eight_circle as module -from stages.eight_circle import ( - BitPlacement, ByteConstruction, Circle, FullKeySet, PublicPair, construct_byte, -) - -POSITIONS = tuple(Fraction(i, 7) for i in range(8)) - - -def key(mapping=tuple(range(8))): - return FullKeySet(tuple(Circle(i, f"G{i}", Fraction(i, 9)) for i in range(8)), mapping) - - -def build(value=0xA5, full=None, positions=POSITIONS): - return construct_byte(value, key() if full is None else full, positions, - origin_id="message", byte_index=0) - - -class EightCircleTests(unittest.TestCase): - def test_all_bytes(self): - full = key() - for value in range(256): - state = build(value, full) - self.assertEqual(len(state.placements), 8) - self.assertEqual(state.source_value, value) - self.assertEqual(tuple(p.circle_index for p in state.placements), tuple(range(8))) - self.assertEqual(tuple(p.bit for p in state.placements), - tuple((value >> i) & 1 for i in range(7, -1, -1))) - - def test_all_40320_assignments(self): - circles = key().circles - count = 0 - for mapping in permutations(range(8)): - state = build(0xA5, FullKeySet(circles, mapping)) - self.assertEqual(state.source_value, 0xA5) - for source, circle in enumerate(mapping): - self.assertEqual(state.placements[circle].source_bit_index, source) - count += 1 - self.assertEqual(count, 40320) - - def test_all_seven_star_views(self): - full = key() - for part in range(1, 8): - pair = full.degenerate(part) - self.assertEqual(pair.indices, (0, part)) - self.assertEqual(len(pair.circles), 2) - self.assertIs(pair.circles[0], full.circles[0]) - self.assertIs(pair.circles[1], full.circles[part]) - - def test_arbitrary_and_malformed_pairs_rejected(self): - full = key() - for bad in (0, -1, 8, True, 1.0, "1", None): - with self.subTest(part=bad), self.assertRaises(ValueError): - full.degenerate(bad) - for members in ((full.circles[1], full.circles[2]), - (full.circles[1], full.circles[0]), - (full.circles[0], full.circles[0]), - list(full.circles[:2]), (), (None, None)): - with self.subTest(members=members), self.assertRaises(ValueError): - PublicPair(members) - with self.assertRaises(TypeError): - full.degenerate(0, 1) - - def test_full_key_validation(self): - full = key() - for circles in (full.circles[:-1], list(full.circles), - tuple(reversed(full.circles)), (None,) * 8, - (full.circles[0],) * 8): - with self.assertRaises(ValueError): - FullKeySet(circles, tuple(range(8))) - duplicate_names = tuple(Circle(i, "same", Fraction(0)) for i in range(8)) - with self.assertRaises(ValueError): - FullKeySet(duplicate_names, tuple(range(8))) - for mapping in ((), (0,) * 8, (False,) + tuple(range(1, 8)), - (0.0,) + tuple(range(1, 8)), list(range(8)), tuple(range(1, 9))): - with self.assertRaises(ValueError): - FullKeySet(full.circles, mapping) - - def test_exact_720_positions_not_two_bit_codes(self): - phases = (Fraction(0), Fraction(1), Fraction(2), Fraction(-1), - Fraction(1, 10**40 + 7), Fraction(9, 4), Fraction(3, 2), Fraction(7, 4)) - state = build(0, positions=phases) - self.assertEqual(tuple(p.position for p in state.placements), - tuple(p % 2 for p in phases)) - self.assertNotEqual(state.placements[0].position, state.placements[1].position) - self.assertEqual(state.placements[0].position, state.placements[2].position) - self.assertEqual(state.placements[4].position, phases[4]) - - def test_positions_and_space_are_independent_supplied_data(self): - full = key() - other = FullKeySet(tuple(replace(c, space=c.space + Fraction(1, 17)) - for c in full.circles), full.bit_to_circle) - self.assertNotEqual(full.circles, other.circles) - shifted = tuple(p + Fraction(1, 19) for p in POSITIONS) - first, second = build(full=full), build(full=other, positions=shifted) - self.assertEqual(first.source_value, second.source_value) - self.assertNotEqual(first.placements[0].position, second.placements[0].position) - # No claim that changing private data with identical public inputs must - # change encryption. This test checks supplied construction data only. - - def test_invalid_byte_and_position_inputs(self): - for value in (-1, 256, True, 1.0, b"A", None): - with self.assertRaises(ValueError): - build(value) - for phases in ((), list(POSITIONS), POSITIONS[:-1]): - with self.assertRaises(ValueError): - build(positions=phases) - for phase in (0, 0.0, True, None): - with self.assertRaises(TypeError): - build(positions=(phase,) + POSITIONS[1:]) - with self.assertRaises(TypeError): - build(full="not a full key") - - def test_direct_construction_validation(self): - state = build() - for changes in ({"placements": state.placements[:-1]}, - {"placements": tuple(reversed(state.placements))}, - {"placements": (state.placements[0],) * 8}, - {"placements": (None,) * 8}, - {"placements": list(state.placements)}, - {"origin_id": " "}, {"byte_index": True}, {"byte_index": -1}): - with self.assertRaises(ValueError): - replace(state, **changes) - for changes in ({"source_bit_index": True}, {"circle_index": 8}, {"bit": False}): - with self.assertRaises(ValueError): - replace(state.placements[0], **changes) - duplicate_source = (replace(state.placements[0], source_bit_index=1),) + state.placements[1:] - with self.assertRaises(ValueError): - replace(state, placements=duplicate_source) - for bad in (True, -1, 8): - with self.assertRaises(ValueError): - Circle(bad, "bad", Fraction(0)) - - def test_public_view_does_not_export_byte_or_other_six(self): - full = key() - pair = full.degenerate(4) - self.assertEqual(set(vars(pair)), {"circles"}) - self.assertEqual(pair.indices, (0, 4)) - self.assertFalse(hasattr(pair, "bit_to_circle")) - self.assertFalse(hasattr(pair, "full")) - self.assertFalse(hasattr(pair, "placements")) - self.assertEqual({c.index for c in pair.circles}, {0, 4}) - - def test_removed_bitwise_cipher_surface(self): - for name in ("encode", "decode", "encode_byte", "decode_byte"): - self.assertFalse(hasattr(module, name), name) - self.assertFalse(hasattr(Circle, "public_sheet")) - self.assertFalse(hasattr(PublicPair, "witness")) - - def test_records_are_immutable_and_payload_not_repr(self): - state = build() - with self.assertRaises(FrozenInstanceError): - state.byte_index = 2 - self.assertNotIn("placements=", repr(state)) - self.assertNotIn("bit=", repr(state.placements[0])) - self.assertNotIn("space=", repr(key().circles[0])) - - -if __name__ == "__main__": - unittest.main() diff --git a/research/weave/tests/test_final_findings.py b/research/weave/tests/test_final_findings.py new file mode 100644 index 00000000..c05bbe93 --- /dev/null +++ b/research/weave/tests/test_final_findings.py @@ -0,0 +1,181 @@ +# === CHECKS === +# id: byte_origin_dispatch_is_trusted +# proves: binary_sequence_closure, binary_coordinate_recovery +# call: self::test_byte_axis_shadow_cannot_change_closure +# id: profile_serialization_is_trusted +# proves: cycle_strict_refusal +# call: self::test_profile_and_round_serializers_cannot_change_identity +# id: capped_candidates_preserve_occurrences +# proves: discovery_repeat_selection +# call: self::test_capped_candidate_collects_all_disjoint_occurrences +# id: cycle_definitions_are_literal +# proves: cycle_discovery_profile +# call: self::test_cycle_rejects_recipe_backed_definition +# id: scheduler_replay_is_trusted +# proves: prime_split_replay +# call: self::test_scheduler_ignores_record_owned_replay +# === END CHECKS === +"""Five follow-up findings on PR #77; replay without network or user data. + +Usage: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_final_findings.py -v +The closure/profile tests mutate supplied records, never trusted runtime classes. +The repetition tests exercise the declared selection rule, not a new compressor. +""" +from copy import copy +from dataclasses import replace +from fractions import Fraction +from itertools import product +from pathlib import Path +from unittest.mock import patch +import os +import unittest + +import affixiation +import cycle +import native_binary as native +import numeral +from prime_schedule import Route +from numeral import PrimePath, Refused, ResourceLimit, Limits + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ['WEAVE_SOURCES']) +SPACES = tuple(Fraction(i, 9) for i in range(8)) + + +class FinalFindingsTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.geometry = native.Geometry(SOURCES/'ucns') + + def profile(self): + return cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) + + def test_byte_axis_shadow_cannot_change_closure(self): + origin = native.ByteOrigin(b'ABxABy', 'review', 0, self.geometry) + args = ((b'AB', b'x', b'y'), (0,1,0,2), (1,2,3), SPACES) + expected = native.close_sequences(origin, *args) + other = native.ByteOrigin(origin.source, 'other', 0, self.geometry) + called = [] + object.__setattr__(origin, 'byte_axis', lambda n: called.append(n) or other.byte_axis(n)) + actual = native.close_sequences(origin, *args) + self.assertEqual(actual.receipt(), expected.receipt()) + self.assertEqual(called, []) + self.assertTrue(all(d.attachment_axis.origin_sha256 == origin.identity for d in actual.definitions)) + + def test_forged_origin_axes_are_rejected_by_all_exports(self): + origin = native.ByteOrigin(b'ABxABy', 'review', 0, self.geometry) + table = native.close_sequences(origin,(b'AB',b'x',b'y'),(0,1,0,2),(1,2,3),SPACES) + other = native.ByteOrigin(origin.source, 'other', 0, self.geometry) + object.__setattr__(origin, 'byte_axis', other.byte_axis) + definitions = [] + for d in table.definitions: + axis = other.byte_axis(d.occurrences[0].source_offset) + occurrences = [] + for o in d.occurrences: + src = other.byte_axis(o.source_offset) + occurrences.append(replace(o,source_axis=src,source_state=self.geometry.placed(src,SPACES[o.circle]))) + definitions.append(replace(d,attachment_axis=axis,state=self.geometry.placed(axis,SPACES[0]),occurrences=tuple(occurrences))) + object.__setattr__(table,'definitions',tuple(definitions)) + for operation in (table.restore,table.receipt,table.wire_occurrences): + with self.subTest(operation=operation.__name__), self.assertRaises(native.BinaryError): + operation() + + def test_profile_and_round_serializers_cannot_change_identity(self): + for slot in ('profile','round'): + p = self.profile(); identity = p.identity; called = [] + target = p if slot == 'profile' else p.rounds[0] + object.__setattr__(target,'as_dict',lambda: called.append(1) or {'spoof':'unexecuted'}) + self.assertEqual(p.identity,identity) + self.assertEqual(called,[]) + wire,_ = cycle.forward(b'ABxABy',b'corpus',replace(p,rounds=p.rounds[:1]),SOURCES) + clean=replace(self.profile(),rounds=self.profile().rounds[:1]) + self.assertEqual(cycle.reverse(wire,b'corpus',clean,SOURCES),b'ABxABy') + + def test_profile_revalidates_mutated_record_fields(self): + cases = (('profile','bucket_bytes',0),('profile','corpus_bit_offset',True), + ('round','arities',(True,)),('round','end_order','unknown'), + ('round','circle_order',(1,)*7),('round','spaces',(Fraction(0),)*7), + ('path','seed',True),('path','steps',(('span',True,0,1),))) + for slot,name,value in cases: + p=self.profile(); target = p if slot=='profile' else p.rounds[0] if slot=='round' else p.rounds[0].path + object.__setattr__(target,name,value) + object.__setattr__(target,'__post_init__',lambda:None) + with self.subTest(slot=slot,name=name),self.assertRaises(Refused): + _ = p.identity + + def test_scheduler_ignores_record_owned_replay(self): + path=PrimePath(53,(('next',),));expected=Route.evaluate(path);called=[] + object.__setattr__(path,'replay',lambda *a,**k:called.append(1) or (53,999)) + self.assertEqual(Route.evaluate(path),expected) + self.assertEqual(called,[]) + object.__setattr__(path,'seed',4) + with self.assertRaises(Refused):Route.evaluate(path) + self.assertEqual(called,[]) + + def test_scheduler_shadow_cannot_bypass_work_budget(self): + path=PrimePath(101,());called=[] + object.__setattr__(path,'replay',lambda *a,**k:called.append(1) or (101,)) + with self.assertRaises(ResourceLimit):Route.evaluate(path,Limits(prime_work=1)) + self.assertEqual(called,[]) + + def test_capped_candidate_collects_all_disjoint_occurrences(self): + p=affixiation.discover(b'\x00\x01\x00\x01\x00\x01\x01') + self.assertEqual(p.blocks,(b'\x00\x01',b'\x01')) + self.assertEqual(p.order,(0,0,0,1)) + self.assertEqual(p.starts,(0,2,4,6)) + self.assertEqual(p.restore(),b'\x00\x01\x00\x01\x00\x01\x01') + + def test_capped_suffix_intervals_match_full_prefix_occurrences(self): + # Check the existing LCP-derived candidate family, not all substring sizes. + # The oracle expands each candidate with byte comparisons and checks the + # leftmost nonoverlapping result under the declared longest-first rule. + for n in range(1,10): + for values in product(range(2),repeat=n): + data=bytes(values);suffixes=affixiation._suffix_array(data) + common=affixiation._lcp(data,suffixes);stack=[];patterns=set() + for i in range(1,n+1): + depth=common[i] if idepth: + size,begin=stack.pop();positions=suffixes[begin:i] + size=min(size,max(positions)-min(positions)) + if size>=2:patterns.add(data[positions[0]:positions[0]+size]) + left=begin + if depth and (not stack or stack[-1][0]=end and not used.intersection(range(start,start+len(block))): + selected.append(start);end=start+len(block) + if len(selected)>=2: + for start in selected: + used.update(range(start,start+len(block)));chosen.append((start,len(block))) + result=affixiation.discover(data) + for start,size in chosen: + self.assertIn(start,result.starts,(data,start,size,result)) + self.assertEqual(result.blocks[result.order[result.starts.index(start)]],data[start:start+size]) + self.assertEqual(result.restore(),data) + + def test_cycle_rejects_recipe_backed_definition(self): + source=b'\x00eX\x00eY';p=self.profile() + args=dict(api=native,geometry=self.geometry,scope=p.scope, + root=native.ByteOrigin(source,p.scope,0,self.geometry).message_origin, + round_id=0,spec=p.rounds[0]) + wire,_=cycle.affix(source,**args);r=numeral._Reader(wire) + magic=r.take(4);size=r.uint();packet=numeral.decode(r.blob(cycle.CycleLimits().round_bytes)) + tail=wire[r.pos:];entries=[];changed=0 + for e in packet.entries: + if numeral.BitBlock.to_bytes(e.block)==b'\x00e': + e=replace(e,recipe=PrimePath(101,()));changed+=1 + entries.append(e) + self.assertEqual(changed,1) + variant=replace(packet,entries=tuple(entries)) + self.assertEqual(numeral.decode(numeral.encode(variant)).restore(),packet.restore()) + altered=magic+numeral._uint(size)+numeral._blob(numeral.encode(variant))+tail + with self.assertRaisesRegex(Refused,'literal|recipe'): + cycle.unaffix(altered,**args) + self.assertEqual(cycle.unaffix(wire,**args),source) + + +if __name__=='__main__':unittest.main() diff --git a/research/weave/tests/test_message_origin.py b/research/weave/tests/test_message_origin.py deleted file mode 100644 index dfefe285..00000000 --- a/research/weave/tests/test_message_origin.py +++ /dev/null @@ -1,117 +0,0 @@ -# === CHECKS === -# id: weave_message_byte_origin_witness -# proves: weave_message_origin_contains_bytes -# call: tests.test_message_origin.MessageOriginTests.test_one_participant_per_byte -# id: weave_message_occurrence_scope_witness -# proves: weave_byte_occurrences_preserve_scope -# call: tests.test_message_origin.MessageOriginTests.test_repeated_bytes_preserve_occurrences -# === END CHECKS === -"""Run: python -m unittest discover -s tests -p test_message_origin.py. - -The source construction can be recovered because it retains its plaintext. -These tests never report that operation as private-key decryption. -""" -from dataclasses import replace -from fractions import Fraction -import unittest - -from stages.eight_circle import Circle, FullKeySet -from stages import message_origin as module -from stages.message_origin import ByteOccurrence, MessageOrigin, construct_at, construct_origin - - -def key(): - return FullKeySet(tuple(Circle(i, f"G{i}", Fraction(i, 9)) for i in range(8)), - (7, 2, 5, 0, 3, 1, 6, 4)) - - -class MessageOriginTests(unittest.TestCase): - def test_one_participant_per_byte(self): - origin = construct_origin(b"AA", identity="M") - self.assertEqual(origin.byte_length, 2) - self.assertEqual(len(origin.bytesets), 2) - self.assertEqual(tuple(b.index for b in origin.bytesets), (0, 1)) - self.assertFalse(hasattr(origin, "axes")) - - def test_eight_placements_are_inside_each_byte(self): - origin = construct_origin(b"ABC", identity="M") - full = key() - positions = tuple(Fraction(i, 7) for i in range(8)) - for index, value in enumerate(b"ABC"): - state = construct_at(origin, index, full, positions) - self.assertEqual((state.origin_id, state.byte_index), ("M", index)) - self.assertEqual(len(state.placements), 8) - self.assertEqual(state.source_value, value) - self.assertEqual(tuple(p.circle_index for p in state.placements), tuple(range(8))) - - def test_repeated_bytes_preserve_occurrences(self): - origin = construct_origin(b"AA", identity="M") - self.assertEqual(origin.bytesets[0].value, origin.bytesets[1].value) - self.assertNotEqual(origin.bytesets[0], origin.bytesets[1]) - self.assertEqual(origin.recover_bytes(), b"AA") - - def test_all_bytes_without_text_normalization(self): - data = bytes(range(256)) + b"\x00\xff\r\n" - origin = construct_origin(data, identity="binary") - self.assertEqual(origin.recover_bytes(), data) - self.assertEqual(origin.byte_length, len(data)) - - def test_empty_message_keeps_its_origin(self): - origin = construct_origin(b"", identity="empty") - self.assertEqual(origin.bytesets, ()) - self.assertEqual(origin.recover_bytes(), b"") - self.assertEqual(origin.identity, "empty") - with self.assertRaises(ValueError): - construct_at(origin, 0, key(), (Fraction(0),) * 8) - - def test_message_names_scope_occurrences_not_content_hashes(self): - first = construct_origin(b"same", identity="first") - second = construct_origin(b"same", identity="second") - self.assertNotEqual(first, second) - self.assertNotEqual(first.bytesets[0], second.bytesets[0]) - self.assertEqual(first.recover_bytes(), second.recover_bytes()) - self.assertEqual(first.identity, "first") - - def test_bad_input_and_identity_rejected(self): - for data in ("text", bytearray(b"A"), [65], None): - with self.assertRaises(TypeError): - construct_origin(data, identity="M") - for identity in (None, "", " ", True, 1): - with self.assertRaises(ValueError): - construct_origin(b"A", identity=identity) - with self.assertRaises(TypeError): - construct_origin(b"A") - - def test_direct_origin_validation(self): - origin = construct_origin(b"AB", identity="M") - for members in (list(origin.bytesets), (None,)): - with self.assertRaises(TypeError): - MessageOrigin("M", members) - for members in (tuple(reversed(origin.bytesets)), (origin.bytesets[1],), - (origin.bytesets[0], origin.bytesets[0]), - (replace(origin.bytesets[0], origin_id="other"),)): - with self.assertRaises(ValueError): - MessageOrigin("M", members) - for index, value in ((True, 0), (-1, 0), (0, False), (0, 256)): - with self.assertRaises(ValueError): - ByteOccurrence("M", index, value) - - def test_attachment_indices_fail_closed(self): - origin = construct_origin(b"A", identity="M") - positions = (Fraction(0),) * 8 - for index in (True, -1, 1, 0.0, None): - with self.assertRaises(ValueError): - construct_at(origin, index, key(), positions) - with self.assertRaises(TypeError): - construct_at("M", 0, key(), positions) - self.assertNotIn("value=", repr(origin.bytesets[0])) - self.assertNotIn("bytesets=", repr(origin)) - - def test_removed_feedback_and_bit_axis_surface(self): - for name in ("MessageAxis", "CouplingKey", "CoupledWitness", "encode", "public_recover", - "_axis_origin", "_source_bits", "witness_bits"): - self.assertFalse(hasattr(module, name), name) - - -if __name__ == "__main__": - unittest.main() diff --git a/research/weave/tests/test_numeral.py b/research/weave/tests/test_numeral.py new file mode 100644 index 00000000..3f1765f4 --- /dev/null +++ b/research/weave/tests/test_numeral.py @@ -0,0 +1,344 @@ +# === CHECKS === +# id: check_numeral_all_8191_short_bitblocks +# proves: numeral_exact_recovery +# call: self::test_all_8191_short_bitblocks +# mutates: none +# cleanup: none +# +# id: check_numeral_leading_zeros_and_partial_byte_concatenation +# proves: numeral_exact_recovery +# call: self::test_leading_zeros_and_partial_byte_concatenation +# mutates: none +# cleanup: none +# +# id: check_numeral_large_integer_and_four_occurrences +# proves: numeral_exact_recovery, numeral_accounting +# call: self::test_large_integer_and_four_occurrences +# mutates: none +# cleanup: none +# +# id: check_numeral_origin_and_round_scope +# proves: numeral_scoped_symbols +# call: self::test_origin_and_round_scope +# mutates: none +# cleanup: none +# +# id: check_numeral_all_private_use_boundaries_and_utf8_width +# proves: numeral_scoped_symbols +# call: self::test_all_private_use_boundaries_and_utf8_width +# mutates: none +# cleanup: none +# +# id: check_numeral_prime_index_and_embedded_paths +# proves: numeral_recipe_replay +# call: self::test_prime_index_and_embedded_paths +# mutates: none +# cleanup: none +# +# id: check_numeral_prime_errors_and_budget_are_distinct +# proves: numeral_recipe_replay, numeral_strict_input +# call: self::test_prime_errors_and_budget_are_distinct +# mutates: none +# cleanup: none +# +# id: check_numeral_recipe_mismatch_does_not_fallback +# proves: numeral_recipe_replay +# call: self::test_recipe_mismatch_does_not_fallback +# mutates: none +# cleanup: none +# +# id: check_numeral_order_offsets_and_seven_occurrence_circles +# proves: numeral_exact_recovery +# call: self::test_order_offsets_and_seven_occurrence_circles +# mutates: none +# cleanup: none +# +# id: check_numeral_invalid_definitions_and_numeric_types +# proves: numeral_strict_input +# call: self::test_invalid_definitions_and_numeric_types +# mutates: none +# cleanup: none +# +# id: check_numeral_every_truncation_unknown_version_and_trailing_data +# proves: numeral_strict_input +# call: self::test_every_truncation_unknown_version_and_trailing_data +# mutates: none +# cleanup: none +# +# id: check_numeral_decoder_length_and_work_limits +# proves: numeral_strict_input +# call: self::test_decoder_length_and_work_limits +# mutates: none +# cleanup: none +# +# id: check_numeral_accounting_counts_every_byte +# proves: numeral_accounting +# call: self::test_accounting_counts_every_byte +# mutates: none +# cleanup: none +# +# id: check_numeral_fresh_process_recovery_and_no_overwrite +# proves: numeral_exact_recovery +# call: self::test_fresh_process_recovery_and_no_overwrite +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_numeral_fresh_process_prime_recipe +# proves: numeral_recipe_replay +# call: self::test_fresh_process_prime_recipe +# mutates: filesystem +# cleanup: tempdir_teardown +# +# === END CHECKS === +"""Run: python -m unittest discover -s tests -p test_numeral.py -v. + +Tests concern this representation and its exact replay, not native gonol geometry +or Weave security. No user data, network, or persistent writes outside tempdirs. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +import random +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +from numeral import (BitBlock, Entry, Packet, PrimePath, Limits, Refused, + ResourceLimit, encode, decode, accounting, _uint) + +ROOT = Path(__file__).resolve().parents[1] +A, B = chr(0xE000), chr(0xE001) + + +def packet(block, symbol=A): + return Packet("test-origin", 0, (Entry(symbol, block, Fraction(1, 7), 1),), symbol) + + +class NumeralTests(unittest.TestCase): + def test_all_8191_short_bitblocks(self): + checked = 0 + for length in range(13): + for value in range(1 << length): + block = BitBlock(value, length) + p = packet(block) if length else Packet("empty", 0, (), "") + restored = decode(encode(p)).restore() + self.assertEqual(restored, block) + self.assertEqual(BitBlock.from_bytes(block.to_bytes(), length), block) + checked += 1 + self.assertEqual(checked, 8191) + + def test_leading_zeros_and_partial_byte_concatenation(self): + self.assertNotEqual(BitBlock(5, 3), BitBlock(5, 8)) + rng = random.Random(71005) + for _ in range(100): + lengths = [rng.randrange(1, 65) for _ in range(12)] + blocks = [BitBlock(rng.getrandbits(n), n) for n in lengths] + entries = tuple(Entry(chr(0xE000+i), v, Fraction(i, 13), 1+i%7) + for i, v in enumerate(blocks)) + symbols = ''.join(e.symbol for e in entries) + p = Packet("partials", 7, entries, symbols) + expected = ''.join(format(v.value, f'0{v.length}b') for v in blocks) + rebuilt = decode(encode(p)).restore() + self.assertEqual(rebuilt, BitBlock(int(expected, 2), len(expected))) + + def test_large_integer_and_four_occurrences(self): + data = bytes(range(256)) * 4096 + block = BitBlock.from_bytes(data) + single = packet(block) + p = replace(single, symbols=A * 4) + wire = encode(p) + self.assertEqual(decode(wire).restore().to_bytes(), data * 4) + self.assertEqual(block.length, 8388608) + self.assertLess(len(wire), len(data) * 2) + self.assertGreater(len(encode(single)), len(data)) + # A small reference is real, but does not erase its definition cost. + self.assertEqual(accounting(single)['symbol_utf8_bytes'], 3) + self.assertGreater(accounting(single)['definition_bytes'], len(data)) + + def test_origin_and_round_scope(self): + p = packet(BitBlock(5, 8)) + q = replace(packet(BitBlock(9, 8)), origin="another-origin", round_id=2) + self.assertEqual(p.symbols, q.symbols) + self.assertNotEqual(decode(encode(p)).restore(), decode(encode(q)).restore()) + self.assertEqual(decode(encode(q)), q) + + def test_all_private_use_boundaries_and_utf8_width(self): + for cp in (0xE000, 0xF8FF, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD): + p = packet(BitBlock(7, 8), chr(cp)) + self.assertEqual(decode(encode(p)), p) + self.assertEqual(accounting(p)['symbol_utf8_bytes'], 3 if cp < 0x10000 else 4) + for symbol in ('', 'AB', '7', '\ud800', '\ufdd0', chr(0x10FFFF)): + with self.assertRaises((Refused, UnicodeError)): + encode(packet(BitBlock(7, 8), symbol)) + + def test_prime_index_and_embedded_paths(self): + recursion = PrimePath(2, (("next",),) * 8) + self.assertEqual(recursion.replay(), (2, 3, 5, 11, 31, 127, 709, 5381, 52711)) + jump = PrimePath(5381, (("span", 10, 0, 2), ("next",), ("next",))) + self.assertEqual(jump.replay(), (5381, 53, 241, 1523)) + binary = PrimePath(5381, (("span", 2, 0, 3), ("next",))) + self.assertEqual(binary.replay(), (5381, 5, 11)) + # Both occurrences lead to 3, but the serialized route preserves which one. + left = PrimePath(313, (("span", 10, 0, 1),)) + right = PrimePath(313, (("span", 10, 2, 1),)) + self.assertEqual(left.replay(), right.replay()) + self.assertNotEqual(left, right) + for path in (jump, binary, left, right): + value = path.replay()[-1] + e = Entry(A, BitBlock(value, 32), Fraction(1, 7), 7, path) + p = Packet("prime", 0, (e,), A * 3) + self.assertEqual(decode(encode(p)), p) + self.assertEqual(decode(encode(p)).restore(), p.restore()) + + def test_prime_errors_and_budget_are_distinct(self): + for path in (PrimePath(4, ()), PrimePath(True, ()), + PrimePath(5381, (("span", 10, 1, 2),)), + PrimePath(5381, (("span", 10, 3, 2),)), + PrimePath(5381, (("span", 2, 0, 0),)), + PrimePath(5381, (("span", True, 0, 1),)), + PrimePath(53, (("execute",),))): + with self.assertRaises(Refused): + path.replay() + with self.assertRaises(ResourceLimit): + PrimePath(101, (("next",),)).replay(Limits(prime_index=100)) + with self.assertRaises(ResourceLimit): + PrimePath(101, ()).replay(Limits(prime_value=100)) + with self.assertRaises(ResourceLimit): + PrimePath(101, (("next",),)).replay(Limits(sieve_bytes=16)) + with self.assertRaises(ResourceLimit): + PrimePath(2, (("next",),) * 3).replay(Limits(recipe_steps=2)) + with self.assertRaises(ResourceLimit): + PrimePath(101, ()).replay(Limits(prime_work=1)) + entries = (Entry(A, BitBlock(101, 16), Fraction(1, 7), 1, PrimePath(101, ())), + Entry(B, BitBlock(101, 16), Fraction(2, 7), 2, PrimePath(101, ()))) + p = Packet("aggregate-work", 0, entries, A+B) + with self.assertRaises(ResourceLimit): + encode(p, Limits(prime_work=8)) + with self.assertRaises(ResourceLimit): + decode(encode(p), Limits(prime_work=8)) + + def test_recipe_mismatch_does_not_fallback(self): + entry = Entry(A, BitBlock(241, 16), Fraction(1, 7), 1, PrimePath(53, ())) + with self.assertRaises(Refused): + encode(Packet("mismatch", 0, (entry,), A)) + # Arbitrary composite values are fully admitted by literal representation. + p = packet(BitBlock(2**1000-2, 1001)) + self.assertEqual(decode(encode(p)), p) + + def test_order_offsets_and_seven_occurrence_circles(self): + entries = tuple(Entry(chr(0xE000+i), BitBlock(i, 16), Fraction(i, 8), i+1) + for i in range(7)) + symbols = ''.join(e.symbol for e in entries) * 3 + p = Packet("occurrences", 5, entries, symbols) + occurrences = decode(encode(p)).occurrences() + self.assertEqual(len(occurrences), 21) + for i, (symbol, offset, ordinal, circle) in enumerate(occurrences): + self.assertEqual((symbol, offset, ordinal, circle), (symbols[i], i*16, i//7, i%7+1)) + + def test_invalid_definitions_and_numeric_types(self): + p = packet(BitBlock(5, 8)) + bad = (replace(p, origin=""), replace(p, round_id=True), + replace(p, entries=list(p.entries)), replace(p, symbols=B), + replace(p, entries=p.entries * 2), + replace(p, entries=(replace(p.entries[0], circle=0),)), + replace(p, entries=(replace(p.entries[0], circle=True),)), + replace(p, entries=(replace(p.entries[0], angle=0.5),)), + replace(p, entries=(replace(p.entries[0], angle=Fraction(2)),)), + replace(p, entries=(replace(p.entries[0], block=BitBlock(0, 0)),))) + for item in bad: + with self.assertRaises(Refused): + encode(item) + for value, length in ((True, 1), (0, False), (-1, 1), (8, 3), (0, -1)): + with self.assertRaises(Refused): + BitBlock(value, length) + with self.assertRaises(Refused): + BitBlock.from_bytes(b'\x01', 1) + with self.assertRaises(Refused): + BitBlock.from_bytes(b'\x00\x00', 1) + + def test_every_truncation_unknown_version_and_trailing_data(self): + wire = encode(packet(BitBlock(5, 8))) + for offset in range(len(wire)): + with self.assertRaises(Refused): + decode(wire[:offset]) + for corrupt in (b'bad!' + wire[4:], wire+b'\x00'): + with self.assertRaises(Refused): + decode(corrupt) + # Origin-length varint widened noncanonically from one byte to two. + with self.assertRaises(Refused): + decode(wire[:4] + bytes([wire[4] | 128, 0]) + wire[5:]) + # A structurally valid modification need not be detected: no authentication claim. + changed = encode(packet(BitBlock(6, 8))) + self.assertEqual(decode(changed).restore(), BitBlock(6, 8)) + + def test_decoder_length_and_work_limits(self): + p = packet(BitBlock(5, 8)) + wire = encode(p) + with self.assertRaises(ResourceLimit): + decode(wire, Limits(output_bits=7)) + with self.assertRaises(ResourceLimit): + decode(wire, Limits(wire_bytes=len(wire)-1)) + with self.assertRaises(ResourceLimit): + encode(replace(p, symbols=A*2), Limits(occurrences=1)) + with self.assertRaises(ResourceLimit): + encode(p, Limits(wire_bytes=3)) + # Byte-budget preflight precedes literal materialization. + with patch.object(BitBlock, 'to_bytes', side_effect=AssertionError('allocated early')): + with self.assertRaises(ResourceLimit): + encode(p, Limits(wire_bytes=3)) + # Declared output mismatch, without changing the remainder of the packet. + output_pos = 4 + 1 + len(p.origin.encode()) + 1 + wrong = wire[:output_pos] + _uint(9) + wire[output_pos+1:] + with self.assertRaises(Refused): + decode(wrong) + + def test_accounting_counts_every_byte(self): + p = packet(BitBlock.from_bytes(b'\x00AB' * 32)) + for symbols in (A, A*7): + candidate = replace(p, symbols=symbols) + stats = accounting(candidate) + self.assertEqual(stats['total_bytes'], len(encode(candidate))) + self.assertEqual(stats['total_bytes'], stats['header_bytes']+ + stats['definition_bytes']+stats['occurrence_bytes']) + self.assertEqual(stats['source_bits'], candidate.restore().length) + path = PrimePath(5381, (("span", 10, 0, 2), ("next",), ("next",))) + e = Entry(A, BitBlock(1523, 16), Fraction(1, 7), 1, path) + p = Packet("recipe-cost", 0, (e,), A) + # The tested recipe is real but larger than storing this small block. + self.assertGreater(accounting(p)['total_bytes'], 2) + + def test_fresh_process_recovery_and_no_overwrite(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + src, wire, restored = root/'source', root/'packet', root/'restored' + data = bytes(range(256))*8 + b'\x00\x00\xff' + src.write_bytes(data) + command = [sys.executable, str(ROOT/'numeral.py')] + encoded = subprocess.run(command + ['bind', str(src), str(wire), '--origin', + 'process-test', '--angle', '1/7', '--circle', '3'], capture_output=True) + self.assertEqual(encoded.returncode, 0, encoded.stderr) + src.unlink() + decoded = subprocess.run(command+['recover', str(wire), str(restored)], capture_output=True) + self.assertEqual(decoded.returncode, 0, decoded.stderr) + self.assertEqual(restored.read_bytes(), data) + again = subprocess.run(command+['recover', str(wire), str(restored)], capture_output=True) + self.assertEqual(again.returncode, 2) + self.assertEqual(restored.read_bytes(), data) + + def test_fresh_process_prime_recipe(self): + path = PrimePath(5381, (("span", 10, 0, 2), ("next",), ("next",))) + p = Packet("prime-process", 0, (Entry(A, BitBlock(1523, 16), Fraction(1, 7), 3, path),), A) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + wire, result = root/'recipe', root/'recovered' + wire.write_bytes(encode(p)) + completed = subprocess.run([sys.executable, str(ROOT/'numeral.py'), + 'recover', str(wire), str(result)], capture_output=True) + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertEqual(result.read_bytes(), b'\x05\xf3') + + +if __name__ == '__main__': + unittest.main() diff --git a/research/weave/tests/test_numeral_replay.py b/research/weave/tests/test_numeral_replay.py new file mode 100644 index 00000000..b5eb999d --- /dev/null +++ b/research/weave/tests/test_numeral_replay.py @@ -0,0 +1,108 @@ +# === CHECKS === +# id: numeral_equal_limits_roundtrip +# proves: numeral_exact_recovery, numeral_recipe_replay, numeral_strict_input +# call: self::test_single_recipe_roundtrip_at_exact_encode_budget +# id: numeral_decode_one_replay_per_definition +# proves: numeral_recipe_replay, numeral_strict_input +# call: self::test_multiple_recipes_share_budget_without_second_replay +# id: numeral_replay_does_not_replace_structural_validation +# proves: numeral_strict_input +# call: self::test_recipe_packets_still_reject_invalid_structure +# id: numeral_decode_does_not_grant_mutable_validation_capability +# proves: numeral_recipe_replay, numeral_strict_input +# call: self::test_decoded_packets_are_revalidated_after_mutation +# === END CHECKS === +"""Run: python -m unittest discover -s tests -p test_numeral_replay.py -v. + +One exact recipe evaluation per definition during decode; typed structure is +still validated. No persistent validation flag is attached to decoded objects. +Only nonsecret in-memory fixtures. This tests recovery, not cryptographic secrecy. +""" +from dataclasses import replace +from fractions import Fraction +from unittest.mock import patch +import unittest + +import numeral +from numeral import (BitBlock, Entry, Packet, PrimePath, Limits, Refused, + ResourceLimit, encode, decode, _uint, _blob) + +A, B = chr(0xE000), chr(0xE001) + + +class NumeralReplayTests(unittest.TestCase): + def packet(self): + return Packet('replay', 0, (Entry(A, BitBlock(101,16), Fraction(1,7), + 1, PrimePath(101, ())),), A) + + def test_single_recipe_roundtrip_at_exact_encode_budget(self): + packet = self.packet() + limits = Limits(prime_work=5) + wire = encode(packet, limits) + decoded = decode(wire, limits) + self.assertEqual(decoded, packet) + self.assertEqual(Packet.restore(decoded, limits), BitBlock(101,16)) + with self.assertRaises(ResourceLimit): encode(packet, Limits(prime_work=4)) + with self.assertRaises(ResourceLimit): decode(wire, Limits(prime_work=4)) + + def test_multiple_recipes_share_budget_without_second_replay(self): + first = self.packet().entries[0] + second = Entry(B, BitBlock(103,16), Fraction(2,7), 2, PrimePath(103, ())) + packet = Packet('aggregate',0,(first,second),A+B+A) + limits = Limits(prime_work=10) + encoder = numeral._Primes(limits) + wire = encode(packet, limits, _engine=encoder) + self.assertEqual(encoder.work,10) + decoder = numeral._Primes(limits) + seen = [] + replay = PrimePath.replay + def record(path,*args,**kwargs): + seen.append(path) + return replay(path,*args,**kwargs) + with patch.object(PrimePath,'replay',record): + restored = decode(wire, limits, _engine=decoder) + self.assertEqual(restored,packet) + self.assertEqual(seen,[first.recipe,second.recipe]) + self.assertEqual(decoder.work,encoder.work) + with self.assertRaises(ResourceLimit): encode(packet, Limits(prime_work=9)) + with self.assertRaises(ResourceLimit): decode(wire, Limits(prime_work=9)) + + def test_recipe_packets_still_reject_invalid_structure(self): + # Construct deliberately invalid wire without asking the encoder to accept it. + # Each valid recipe evaluates to 101; every other constraint remains checked. + def definition(symbol=A,n=1,d=7,circle=1,length=16): + return (_blob(symbol.encode()) + _uint(n)+_uint(d)+_uint(circle)+_uint(length) + + b'\x01'+_uint(101)+_uint(0)) + def wire(rows, symbols=A, declared=16, origin=b'replay'): + return (numeral.MAGIC + _blob(origin)+_uint(0)+_uint(declared) + + _uint(len(rows))+b''.join(rows)+_blob(symbols.encode())) + invalid = ( + wire([definition(circle=0)]), wire([definition(circle=8)]), + wire([definition(n=2,d=1)]), wire([definition(length=0)]), + wire([definition()],origin=b''), wire([definition()],declared=17), + wire([definition()],symbols=B), + wire([definition(),definition(n=2)],declared=16), + wire([definition(),definition(symbol=B)],symbols=A+B,declared=32), + ) + for i,data in enumerate(invalid): + with self.subTest(case=i),self.assertRaises(Refused) as failure: + decode(data,Limits(prime_work=100)) + self.assertNotIsInstance(failure.exception,ResourceLimit) + self.assertEqual(decode(wire([definition()]),Limits(prime_work=5)), self.packet()) + + def test_decoded_packets_are_revalidated_after_mutation(self): + packet = decode(encode(self.packet())) + # A successful earlier decode is not authority over changed fields. + bad = replace(packet, entries=(replace(packet.entries[0],recipe=PrimePath(103,())),)) + for operation in (encode,numeral.accounting,Packet.restore,Packet.occurrences): + with self.subTest(operation=operation.__name__),self.assertRaises(Refused): + operation(bad) + object.__setattr__(packet.entries[0].recipe,'seed',4) + object.__setattr__(packet,'validate',lambda *args,**kwargs:16) + object.__setattr__(packet,'_validate_fields',lambda *args,**kwargs:16) + for operation in (encode,numeral.accounting,Packet.restore,Packet.occurrences): + with self.subTest(operation=operation.__name__),self.assertRaises(Refused): + operation(packet) + + +if __name__=='__main__': unittest.main() diff --git a/research/weave/tests/test_numeral_review.py b/research/weave/tests/test_numeral_review.py new file mode 100644 index 00000000..b6dd1bef --- /dev/null +++ b/research/weave/tests/test_numeral_review.py @@ -0,0 +1,64 @@ +# === CHECKS === +# id: numeral_review_aggregate_budget +# proves: numeral_strict_input +# call: self::test_decode_has_one_aggregate_prime_budget +# mutates: none +# cleanup: none +# id: numeral_review_malformed_not_capacity +# proves: numeral_strict_input +# call: self::test_malformed_structures_are_not_capacity_failures +# mutates: none +# cleanup: none +# id: numeral_review_cli_angle +# proves: numeral_strict_input +# call: self::test_zero_denominator_cli_is_concise_refusal +# mutates: none +# cleanup: none +# === END CHECKS === +"""Run: python -m unittest discover -s tests -p test_numeral_review.py -v. + +Regression witnesses for the live PR #77 input/budget findings. The CLI refusal +occurs before input reads or output writes; no original tests are weakened. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +import subprocess +import sys +import unittest +from numeral import BitBlock, Entry, Packet, PrimePath, Limits, Refused, ResourceLimit, encode, decode + +ROOT = Path(__file__).resolve().parents[1] +A = chr(0xE000) + + +class NumeralReviewTests(unittest.TestCase): + def test_decode_has_one_aggregate_prime_budget(self): + path = PrimePath(101, ()) + p = Packet('budget', 0, (Entry(A, BitBlock(101, 16), Fraction(1, 7), 1, path),), A) + wire = encode(p) + # One trusted replay: one step and four trial-divisor candidates, five total. + with self.assertRaises(ResourceLimit): + decode(wire, Limits(prime_work=4)) + self.assertEqual(decode(wire, Limits(prime_work=5)), p) + + def test_malformed_structures_are_not_capacity_failures(self): + p = Packet('scope', 0, (Entry(A, BitBlock(5,8), Fraction(1,7), 1),), A) + operations = (lambda: PrimePath(2, []).replay(), + lambda: encode(replace(p, entries=list(p.entries))), + lambda: encode(replace(p, symbols=[A]))) + for operation in operations: + with self.assertRaises(Refused) as raised: + operation() + self.assertNotIsInstance(raised.exception, ResourceLimit) + + def test_zero_denominator_cli_is_concise_refusal(self): + result = subprocess.run([sys.executable, str(ROOT/'numeral.py'), 'bind', + 'unused-input', 'unused-output', '--origin', 'test', '--angle', '1/0', + '--circle', '3'], capture_output=True, text=True) + self.assertEqual(result.returncode, 2) + self.assertNotIn('Traceback', result.stderr) + self.assertIn('exact finite fraction', result.stderr) + + +if __name__ == '__main__': unittest.main() diff --git a/research/weave/tests/test_review_closure.py b/research/weave/tests/test_review_closure.py new file mode 100644 index 00000000..8d2be65e --- /dev/null +++ b/research/weave/tests/test_review_closure.py @@ -0,0 +1,373 @@ +# === CHECKS === +# id: review_accounting_one_replay +# proves: numeral_strict_input, numeral_accounting +# call: self::test_accounting_reuses_the_single_validation +# mutates: none +# cleanup: none +# id: review_cli_aggregate_budget +# proves: numeral_strict_input +# call: self::test_cli_shares_prime_budget_through_inspect_and_recover +# mutates: filesystem +# cleanup: tempdir_teardown +# id: review_closure_owner +# proves: discovery_closure_authority +# call: self::test_discovery_documentation_preserves_closure_owner +# mutates: none +# cleanup: none +# id: review_native_objects_no_spoof +# proves: binary_sequence_closure, binary_source_refusal +# call: self::test_all_native_record_slots_reject_equality_spoofs +# mutates: none +# cleanup: none +# id: review_native_fields_no_spoof +# proves: binary_sequence_closure, binary_source_refusal +# call: self::test_native_scalar_fields_reject_spoofs_and_subclasses +# mutates: none +# cleanup: none +# id: review_native_geometry_instance +# proves: binary_sequence_closure, binary_source_refusal +# call: self::test_records_from_another_geometry_instance_are_not_substituted +# mutates: none +# cleanup: none +# id: review_native_export_revalidation +# proves: binary_sequence_closure, binary_coordinate_recovery +# call: self::test_all_export_paths_reject_bypassed_frozen_spoofs +# mutates: none +# cleanup: none +# id: review_decode_discovery_identity +# proves: cycle_discovery_profile +# call: self::test_unaffix_rejects_valid_alternate_partitions +# mutates: none +# cleanup: none +# id: review_reverse_discovery_identity +# proves: cycle_discovery_profile +# call: self::test_whole_cycle_reverse_rejects_alternate_selection +# mutates: none +# cleanup: none +# id: review_discovery_roundtrip +# proves: cycle_discovery_profile, cycle_reversible_rounds +# call: self::test_canonical_discovery_records_still_recover +# mutates: none +# cleanup: none +# id: review_inverse_discovery_budget +# proves: cycle_discovery_profile, discovery_resource_refusal +# call: self::test_inverse_discovery_honors_the_visit_budget +# mutates: none +# cleanup: none +# id: review_native_validator_not_shadowed +# proves: binary_sequence_closure, binary_source_refusal +# call: self::test_record_instances_cannot_shadow_validation +# mutates: none +# cleanup: none +# id: review_table_dispatch +# proves: binary_sequence_closure, binary_coordinate_recovery +# call: self::test_table_exports_use_trusted_validation +# mutates: none +# cleanup: none +# id: review_entry_dispatch +# proves: numeral_strict_input, numeral_exact_recovery +# call: self::test_entry_validation_cannot_be_shadowed +# mutates: none +# cleanup: none +# id: review_packet_dispatch +# proves: numeral_strict_input, numeral_exact_recovery +# call: self::test_packet_validation_cannot_be_shadowed +# mutates: none +# cleanup: none +# id: review_block_conversion +# proves: numeral_exact_recovery +# call: self::test_block_conversion_cannot_be_shadowed +# mutates: none +# cleanup: none +# id: review_block_fields +# proves: numeral_strict_input +# call: self::test_entry_revalidates_supplied_bitblock_fields +# mutates: none +# cleanup: none +# id: review_recipe_dispatch +# proves: numeral_recipe_replay, numeral_strict_input +# call: self::test_recipe_replay_cannot_be_shadowed +# mutates: none +# cleanup: none +# === END CHECKS === +"""PR #77 final-review regressions, not cryptographic-strength tests. + +Usage: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_review_closure.py -v +Only temporary files are written. The original profile, UCNS geometry and wire +representation remain unchanged; invalid native records/false profile claims fail. +""" +from copy import copy +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +from unittest.mock import patch +import contextlib +import io +import os +import sys +import tempfile +import unittest + +import affixiation +import cycle +import native_binary as native +import numeral +from affixiation import Partition, discover +from numeral import BitBlock, Entry, Packet, PrimePath, Limits, Refused, ResourceLimit + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ['WEAVE_SOURCES']) +PROFILE = cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) +SPACES = tuple(Fraction(i, 9) for i in range(8)) + + +class EqualitySpoof: + calls = 0 + + def __eq__(self, other): + type(self).calls += 1 + return True + + def __ne__(self, other): + type(self).calls += 1 + return False + + +class FractionSpoof(Fraction): + def __eq__(self, other): + return True + + def __ne__(self, other): + return False + + +class ReviewClosureTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.geometry = native.Geometry(SOURCES/'ucns') + + def fixture(self): + origin = native.ByteOrigin(b'ABxABy', 'review', 0, self.geometry) + return native.close_sequences(origin, (b'AB', b'x', b'y'), (0,1,0,2), (1,2,3), SPACES) + + def recipe_packet(self): + symbol = chr(0xE000) + return Packet('budget', 0, (Entry(symbol, BitBlock(101,16), Fraction(1,7), + 1, PrimePath(101,())),), symbol) + + def test_accounting_reuses_the_single_validation(self): + packet = self.recipe_packet() + seen = [] + replay = PrimePath.replay + def record(path, *args, **kwargs): + seen.append(path) + return replay(path, *args, **kwargs) + with patch.object(PrimePath, 'replay', record): + stats = numeral.accounting(packet, Limits(prime_work=5)) + self.assertEqual(len(seen), 1, 'accounting repeated the recipe validation') + self.assertEqual(stats['source_bits'], 16) + self.assertEqual(stats['total_bytes'], len(numeral.encode(packet))) + with self.assertRaises(ResourceLimit): + numeral.accounting(packet, Limits(prime_work=4)) + + def test_cli_shares_prime_budget_through_inspect_and_recover(self): + wire = numeral.encode(self.recipe_packet()) + # Decode charges 5, accounting 5, and restore a further 5 work units. + # Separate public operations revalidate; the shared CLI counter never resets. + for command, minimum in (('inspect',10), ('recover',15)): + for budget, success in ((minimum-1,False), (minimum,True)): + with self.subTest(command=command,budget=budget), tempfile.TemporaryDirectory() as directory: + root=Path(directory); src=root/'record'; dst=root/'out'; src.write_bytes(wire) + argv=['numeral.py',command,str(src)] + ([str(dst)] if command=='recover' else []) + with patch.object(sys,'argv',argv), patch.object(numeral,'Limits',return_value=Limits(prime_work=budget)): + with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): + if success: + self.assertEqual(numeral.main(),0) + else: + with self.assertRaises(SystemExit) as failure: + numeral.main() + self.assertEqual(failure.exception.code,2) + if command=='recover' and success: + self.assertEqual(dst.read_bytes(),b'\x00e') + else: + self.assertFalse(dst.exists()) + + def test_discovery_documentation_preserves_closure_owner(self): + self.assertNotIn('UCHC closes', affixiation.__doc__) + self.assertIn('Stack-owned', affixiation.__doc__) + self.assertIn('native_binary.py', affixiation.__doc__) + self.assertIn('UCNS geometry', affixiation.__doc__) + + def test_all_native_record_slots_reject_equality_spoofs(self): + for target, fields in (('definition',('attachment_axis','state')), + ('occurrence',('source_axis','circle_axis','state','source_state'))): + for field in fields: + with self.subTest(target=target,field=field): + table=self.fixture(); first=table.definitions[0]; EqualitySpoof.calls=0 + if target=='definition': + forged=replace(first, **{field:EqualitySpoof()}) + else: + occurrence=replace(first.occurrences[0], **{field:EqualitySpoof()}) + forged=replace(first,occurrences=(occurrence,)+first.occurrences[1:]) + with self.assertRaises(native.BinaryError): + replace(table,definitions=(forged,)+table.definitions[1:]) + self.assertEqual(EqualitySpoof.calls,0,'untrusted equality was invoked') + + def test_native_scalar_fields_reject_spoofs_and_subclasses(self): + cases=(('attachment_axis','origin_sha256',EqualitySpoof()), + ('attachment_axis','identity_sha256',EqualitySpoof()), + ('attachment_axis','axis_count',EqualitySpoof()), + ('attachment_axis','axis_ordinal',EqualitySpoof()), + ('attachment_axis','turn',FractionSpoof(0)), + ('state','phase_turns',FractionSpoof(0)), + ('state','frame',EqualitySpoof())) + for slot, field, value in cases: + with self.subTest(slot=slot,field=field): + table=self.fixture(); first=table.definitions[0] + forged=copy(getattr(first,slot)); object.__setattr__(forged,field,value) + changed=replace(first,**{slot:forged}) + with self.assertRaises(native.BinaryError): + replace(table,definitions=(changed,)+table.definitions[1:]) + + def test_records_from_another_geometry_instance_are_not_substituted(self): + table=self.fixture(); first=table.definitions[0] + other=native.Geometry(SOURCES/'ucns') + axis=other.axis(table.origin.identity,len(table.origin.source),0) + with self.assertRaises(native.BinaryError): + replace(table,definitions=(replace(first,attachment_axis=axis),)+table.definitions[1:]) + self.assertEqual(table.restore(),b'ABxABy') + + def test_all_export_paths_reject_bypassed_frozen_spoofs(self): + for method in ('restore','receipt','wire_occurrences'): + with self.subTest(method=method): + table=self.fixture() + object.__setattr__(table.definitions[0],'attachment_axis',EqualitySpoof()) + with self.assertRaises(native.BinaryError): getattr(table,method)() + + def test_record_instances_cannot_shadow_validation(self): + for target in ('definition', 'occurrence'): + with self.subTest(target=target): + table=self.fixture(); definition=table.definitions[0] + item=definition if target=='definition' else definition.occurrences[0] + called=[] + object.__setattr__(item,'__post_init__',lambda:called.append(True)) + self.assertEqual(table.restore(),b'ABxABy') + self.assertEqual(called,[], 'record-supplied validation was invoked') + if target=='definition': + object.__setattr__(item,'occurrences',(EqualitySpoof(),)) + else: + object.__setattr__(item,'source_offset',EqualitySpoof()) + for method in (table.restore,table.receipt,table.wire_occurrences): + with self.assertRaises(native.BinaryError): method() + self.assertEqual(called,[]) + + def test_table_exports_use_trusted_validation(self): + for change in ('order', 'definitions', 'origin'): + table=self.fixture(); called=[] + object.__setattr__(table,'validate',lambda:called.append(True)) + self.assertEqual(table.restore(),b'ABxABy') + self.assertEqual(called,[]) + if change=='order': + object.__setattr__(table,'order',(1,0,0,2)) + elif change=='definitions': + object.__setattr__(table,'definitions',table.definitions[:-1]) + else: + object.__setattr__(table,'origin',EqualitySpoof()) + for method in (table.restore,table.receipt,table.wire_occurrences): + with self.subTest(change=change,method=method.__name__): + with self.assertRaises(native.BinaryError):method() + self.assertEqual(called,[]) + + def test_entry_validation_cannot_be_shadowed(self): + packet=self.recipe_packet();entry=packet.entries[0];called=[] + object.__setattr__(entry,'validate',lambda *a,**kw:called.append(True)) + self.assertEqual(numeral.decode(numeral.encode(packet)).restore(),BitBlock(101,16)) + self.assertEqual(called,[]) + object.__setattr__(entry,'circle',999) + for method in (lambda:numeral.encode(packet),lambda:numeral.accounting(packet), + packet.restore,packet.occurrences): + with self.assertRaises(Refused):method() + self.assertEqual(called,[]) + + def test_packet_validation_cannot_be_shadowed(self): + packet=self.recipe_packet();called=[] + object.__setattr__(packet,'validate',lambda *a,**kw:called.append(True) or 16) + self.assertEqual(numeral.decode(numeral.encode(packet)).restore(),BitBlock(101,16)) + self.assertEqual(called,[]) + object.__setattr__(packet,'entries',packet.entries*2) + for method in (lambda:numeral.encode(packet),lambda:numeral.accounting(packet), + packet.restore,packet.occurrences): + with self.assertRaises(Refused):method() + self.assertEqual(called,[]) + + def test_block_conversion_cannot_be_shadowed(self): + packet=self.recipe_packet();entry=replace(packet.entries[0],recipe=None) + packet=replace(packet,entries=(entry,));called=[] + object.__setattr__(entry.block,'to_bytes',lambda:called.append(True) or b'ZZ') + self.assertEqual(numeral.decode(numeral.encode(packet)).restore(),BitBlock(101,16)) + self.assertEqual(packet.restore(),BitBlock(101,16)) + self.assertEqual(called,[]) + + def test_entry_revalidates_supplied_bitblock_fields(self): + for field,value in (('length',True),('length',-1),('length',EqualitySpoof()), + ('value',True),('value',-1),('value',1<<20)): + packet=self.recipe_packet();entry=replace(packet.entries[0],recipe=None) + packet=replace(packet,entries=(entry,)) + object.__setattr__(entry.block,field,value) + with self.subTest(field=field,value_type=type(value).__name__): + with self.assertRaises(Refused):numeral.encode(packet) + + def test_recipe_replay_cannot_be_shadowed(self): + packet=self.recipe_packet();recipe=packet.entries[0].recipe;called=[] + object.__setattr__(recipe,'replay',lambda *a,**kw:called.append(True) or (101,)) + self.assertEqual(numeral.decode(numeral.encode(packet)).restore(),BitBlock(101,16)) + self.assertEqual(called,[]) + object.__setattr__(recipe,'seed',4) + with self.assertRaises(Refused):numeral.encode(packet) + self.assertEqual(called,[]) + # A spoofed opcode is malformed data, not an executable next-prime step. + path=PrimePath(2,((EqualitySpoof(),),));EqualitySpoof.calls=0 + with self.assertRaises(Refused):PrimePath.replay(path) + self.assertEqual(EqualitySpoof.calls,0) + + def native_args(self, source): + return dict(api=native,geometry=self.geometry,scope=PROFILE.scope, + root=native.ByteOrigin(source,PROFILE.scope,0,self.geometry).message_origin, + round_id=0,spec=PROFILE.rounds[0]) + + def test_unaffix_rejects_valid_alternate_partitions(self): + source=b'ABxABy'; canonical=discover(source) + alternatives=(Partition((b'A',b'B',b'x',b'y'),(0,1,2,0,1,3),tuple(range(6)),0), + Partition((b'x',b'AB',b'y'),(1,0,1,2),(0,2,3,5),0), + Partition((source,),(0,),(0,),0)) + for alternative in alternatives: + self.assertEqual(alternative.restore(),source) + self.assertNotEqual((alternative.blocks,alternative.order),(canonical.blocks,canonical.order)) + with patch.object(cycle,'discover',return_value=alternative): + wire,_=cycle.affix(source,**self.native_args(source)) + with self.assertRaisesRegex(Refused,'discovery profile'): + cycle.unaffix(wire,**self.native_args(source)) + + def test_whole_cycle_reverse_rejects_alternate_selection(self): + profile=replace(PROFILE,rounds=PROFILE.rounds[:1]) + def single_literal(data,**kwargs): + return Partition((data,),(0,),(0,),0) + with patch.object(cycle,'discover',side_effect=single_literal): + wire,_=cycle.forward(b'ABABABAB',b'corpus',profile,SOURCES) + with self.assertRaisesRegex(Refused,'discovery profile'): + cycle.reverse(wire,b'corpus',profile,SOURCES) + + def test_canonical_discovery_records_still_recover(self): + for source in (b'',b'x',b'ABxABy',b'AAAAA',b'banana bandana banana',bytes(range(256))): + with self.subTest(length=len(source)): + wire,_=cycle.affix(source,**self.native_args(source)) + self.assertEqual(cycle.unaffix(wire,**self.native_args(source)),source) + + def test_inverse_discovery_honors_the_visit_budget(self): + source=b'ABABABAB'; args=self.native_args(source) + wire,_=cycle.affix(source,**args) + with self.assertRaises(ResourceLimit): + cycle.unaffix(wire,**args,limits=cycle.CycleLimits(discovery_visits=1)) + + +if __name__=='__main__': unittest.main() diff --git a/research/weave/tests/test_terminal_records.py b/research/weave/tests/test_terminal_records.py new file mode 100644 index 00000000..788ace93 --- /dev/null +++ b/research/weave/tests/test_terminal_records.py @@ -0,0 +1,232 @@ +# === CHECKS === +# id: numeral_fraction_internals_are_canonical +# proves: numeral_exact_recovery, numeral_strict_input +# call: self::test_numeral_rejects_mutated_fraction_fields +# id: route_relation_is_replayed_before_planning +# proves: prime_split_replay +# call: self::test_plan_rejects_inconsistent_direct_and_mutated_routes +# id: scheduler_validation_budget_is_shared +# proves: prime_split_replay, cycle_strict_refusal +# call: self::test_route_planning_uses_one_charged_engine +# id: recovery_uses_trusted_geometry +# proves: binary_coordinate_recovery, binary_source_refusal +# call: self::test_recovery_cannot_use_shadowed_geometry_to_accept_coordinates +# id: cycle_header_admission_precedes_expensive_work +# proves: cycle_strict_refusal +# call: self::test_reverse_rejects_every_truncated_header_before_preparation +# id: cycle_invalid_framing_never_loads_producers +# proves: cycle_strict_refusal +# call: self::test_reverse_rejects_outer_identity_count_and_length_before_preparation +# id: cycle_valid_header_retains_scheduler_budget +# proves: cycle_strict_refusal, prime_split_replay +# call: self::test_reverse_prepares_valid_headers_under_the_existing_budget +# === END CHECKS === +"""Replay three terminal-review gaps using only nonsecret local records. + +Usage: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_terminal_records.py -v +These tests mutate supplied records, not trusted producer code or runtime classes. +""" +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +from unittest.mock import patch +import os +import unittest + +import cycle +import native_binary as native +import numeral +from numeral import BitBlock, Entry, Packet, PrimePath, Limits, Refused, ResourceLimit +from prime_schedule import Route, plan + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ['WEAVE_SOURCES']) +SPACES = tuple(Fraction(i, 9) for i in range(8)) + + +def bad_fraction(numerator, denominator): + value = Fraction(1, 2) + object.__setattr__(value, '_numerator', numerator) + object.__setattr__(value, '_denominator', denominator) + return value + + +class TerminalRecordTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.geometry = native.Geometry(SOURCES/'ucns') + + def table(self, geometry): + origin = native.ByteOrigin(b'ABxABy', 'terminal', 0, geometry) + return native.close_sequences(origin, (b'AB', b'x', b'y'), (0,1,0,2), (1,2,3), SPACES) + + def recovery(self, table): + items = native.SequenceTable.wire_occurrences(table) + return dict(scope=table.origin.scope, message_origin=table.origin.message_origin, + round_id=0, origin_identity=table.origin.identity, byte_length=6, + blocks=tuple(d.data for d in table.definitions), circles=(1,2,3), + spaces=SPACES, counts=tuple(sum(o.circle == c for _,o in items) for c in range(1,8)), + wire_order=tuple(i for i,_ in items), + source_turns=tuple(native.Geometry.lift(table.origin.geometry,o.source_state) for _,o in items)) + + def test_numeral_rejects_mutated_fraction_fields(self): + class EqualInt(int): + def __eq__(self, other): return True + for n,d in ((2,2),(0,2),(-1,-2),(1,0),(True,2),(1,EqualInt(2)),(1.0,2)): + angle = bad_fraction(n,d) + p = Packet('fraction',0,(Entry(chr(0xE000),BitBlock(7,8),angle,1),),chr(0xE000)) + for operation in (numeral.encode,numeral.accounting,Packet.restore,Packet.occurrences): + with self.subTest(n=n,d=d,operation=operation.__name__), self.assertRaises(Refused): + operation(p) + for angle in (Fraction(0),Fraction(1,2),Fraction(3,2)): + p=Packet('valid',0,(Entry(chr(0xE000),BitBlock(7,8),angle,1),),chr(0xE000)) + self.assertEqual(numeral.decode(numeral.encode(p)),p) + + def test_native_rejects_noncanonical_fraction_inputs(self): + g = self.geometry + axis = native.Geometry.axis(g,'0'*64,8,0) + for value in (bad_fraction(2,2),bad_fraction(0,2),bad_fraction(1,0),bad_fraction(True,2)): + with self.subTest(operation='placed'),self.assertRaises(native.BinaryError): + native.Geometry.placed(g,axis,value) + with self.subTest(operation='recover'),self.assertRaises(native.BinaryError): + native.Geometry.recover_axis(g,'0'*64,8,value,Fraction(0)) + + def test_plan_rejects_inconsistent_direct_and_mutated_routes(self): + path=PrimePath(53,(('next',),));good=Route.evaluate(path) + bad=(Route(path,good.trace,0), Route(path,(53,239),good.determinant), + Route(PrimePath(59,()),good.trace,good.determinant), + Route(path,list(good.trace),good.determinant), + Route(path,good.trace,True)) + for value in bad: + object.__setattr__(value,'validate',lambda *a,**k: good.determinant) + with self.subTest(value=value),self.assertRaises(Refused):plan(128,value,(3,5,7)) + mutated=Route.evaluate(path) + object.__setattr__(mutated,'determinant',0) + with self.assertRaises(Refused):plan(128,mutated,(3,5,7)) + self.assertEqual(plan(128,Route(path,good.trace,good.determinant),(3,5,7)),plan(128,good,(3,5,7))) + + def test_plan_rejects_spoofed_trace_scalars_and_replays_actual_path(self): + class EqualInt(int): + def __eq__(self, other):return True + route=Route.evaluate(PrimePath(101,())) + for changed in (replace(route,trace=(EqualInt(101),)),replace(route,determinant=EqualInt(route.determinant))): + with self.assertRaises(Refused):plan(128,changed,(3,5,7)) + called=[] + object.__setattr__(route.path,'replay',lambda *a,**k: called.append(1) or (101,)) + object.__setattr__(route.path,'seed',4) + with self.assertRaises(Refused):plan(128,route,(3,5,7)) + self.assertEqual(called,[]) + + def test_route_planning_uses_one_charged_engine(self): + # Seed 101 costs one step plus four trial divisors: five units per replay. + for budget in (9,10): + limits=Limits(prime_work=budget);engine=numeral._Primes(limits) + route=Route.evaluate(PrimePath(101,()),limits,engine=engine) + self.assertEqual(engine.work,5) + if budget==9: + with self.assertRaises(ResourceLimit):plan(128,route,(3,5,7),limits=limits,engine=engine) + else: + result=plan(128,route,(3,5,7),limits=limits,engine=engine) + self.assertEqual(sum(result.lengths),128) + self.assertEqual(engine.work,10) + + def test_cycle_shares_route_validation_work_across_evaluation_and_plan(self): + p=cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) + spec=replace(p.rounds[0],path=PrimePath(101,())) + p=replace(p,rounds=(spec,)) + with self.assertRaises(ResourceLimit): + cycle.forward(b'ABxABy',b'corpus',p,SOURCES,cycle.CycleLimits(prime_work=9)) + wire,_=cycle.forward(b'ABxABy',b'corpus',p,SOURCES,cycle.CycleLimits(prime_work=10)) + with self.assertRaises(ResourceLimit): + cycle.reverse(wire,b'corpus',p,SOURCES,cycle.CycleLimits(prime_work=9)) + self.assertEqual(cycle.reverse(wire,b'corpus',p,SOURCES,cycle.CycleLimits(prime_work=10)),b'ABxABy') + + def test_recovery_cannot_use_shadowed_geometry_to_accept_coordinates(self): + geometry=native.Geometry(SOURCES/'ucns');table=self.table(geometry);args=self.recovery(table) + items=native.SequenceTable.wire_occurrences(table) + expected_axes=iter(o.source_axis for _,o in items);called=[] + object.__setattr__(geometry,'recover_axis',lambda *a,**k: called.append('recover') or next(expected_axes)) + object.__setattr__(geometry,'lift',lambda state: called.append('lift') or Fraction(1,16)) + args['source_turns']=(Fraction(1,16),)*len(items) + with self.assertRaises(native.BinaryError):native.recover_sequences(geometry,**args) + self.assertEqual(called,[]) + good=self.recovery(table) + self.assertEqual(native.recover_sequences(geometry,**good).restore(),b'ABxABy') + self.assertEqual(called,[]) + + def test_recovery_axis_constructor_ignores_instance_override(self): + geometry=native.Geometry(SOURCES/'ucns');called=[] + object.__setattr__(geometry,'axis',lambda *a,**k: called.append(1) or None) + result=native.Geometry.recover_axis(geometry,'0'*64,8,Fraction(3,8),Fraction(0)) + self.assertEqual(result.axis_ordinal,3) + self.assertEqual(result.origin_sha256,'0'*64) + self.assertEqual(called,[]) + + def test_cycle_uses_trusted_lift_for_supplied_geometry(self): + geometry=native.Geometry(SOURCES/'ucns');p=cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) + data=b'ABxABy';root=native.ByteOrigin(data,p.scope,0,geometry).message_origin + args=dict(api=native,geometry=geometry,scope=p.scope,root=root,round_id=0,spec=p.rounds[0]) + expected,_=cycle.affix(data,**args);called=[] + object.__setattr__(geometry,'lift',lambda *a: called.append(1) or Fraction(0)) + actual,_=cycle.affix(data,**args) + self.assertEqual(actual,expected) + self.assertEqual(cycle.unaffix(actual,**args),data) + self.assertEqual(called,[]) + + +class HeaderAdmissionTests(unittest.TestCase): + """Cheap outer-record admission must precede prime replay and source execution.""" + + def fixture(self): + profile = cycle.Profile.read((ROOT/'profiles/cycle-v1.json').read_bytes()) + # Syntactically admitted, genuinely executable prime path; invalid wire + # cases must not perform its two nth-prime calculations. + spec = replace(profile.rounds[0], path=PrimePath(5381, (("next",),)*2)) + profile = replace(profile, rounds=(spec,)) + header = cycle.MAGIC + cycle._native_identity() + profile.identity + bytes(32) + frame = header + numeral._uint(1) + numeral._blob(b'x') + return profile, header, frame + + def test_reverse_rejects_every_truncated_header_before_preparation(self): + profile, header, frame = self.fixture() + cases = [frame[:i] for i in range(len(frame))] + cases.append(b'bad!' + frame[4:]) + with patch.object(cycle, '_prepared', side_effect=AssertionError('prime work before admission')) as prepared: + with patch.object(cycle, 'load_native', side_effect=AssertionError('native source before admission')) as native_load: + for i, wire in enumerate(cases): + with self.subTest(case=i), self.assertRaises(Refused): + cycle.reverse(wire, b'corpus', profile, '/unused-sources') + prepared.assert_not_called() + native_load.assert_not_called() + + def test_reverse_rejects_outer_identity_count_and_length_before_preparation(self): + profile, header, frame = self.fixture() + cases = ( + frame[:4] + bytes([frame[4] ^ 1]) + frame[5:], + frame[:36] + bytes([frame[36] ^ 1]) + frame[37:], + header + numeral._uint(2) + numeral._blob(b'x'), + header + b'\x81\x00' + numeral._blob(b'x'), + header + numeral._uint(1) + b'\x81\x00x', + header + numeral._uint(1) + numeral._uint(cycle.CycleLimits().round_bytes + 1), + frame + b'trailing', + ) + with patch.object(cycle, '_prepared', side_effect=AssertionError('prime work before admission')) as prepared: + with patch.object(cycle, 'load_native', side_effect=AssertionError('native source before admission')) as native_load: + for i, wire in enumerate(cases): + with self.subTest(case=i), self.assertRaises(Refused): + cycle.reverse(wire, b'corpus', profile, '/unused-sources') + prepared.assert_not_called() + native_load.assert_not_called() + + def test_reverse_prepares_valid_headers_under_the_existing_budget(self): + profile, header, frame = self.fixture() + limits = cycle.CycleLimits(prime_work=1) + with patch.object(cycle, '_prepared', wraps=cycle._prepared) as prepared: + with patch.object(cycle, 'load_native', side_effect=AssertionError('native load after exhausted budget')) as native_load: + with self.assertRaisesRegex(ResourceLimit, 'aggregate prime work'): + cycle.reverse(frame, b'corpus', profile, '/unused-sources', limits) + prepared.assert_called_once_with(profile, limits) + native_load.assert_not_called() + + +if __name__=='__main__':unittest.main() diff --git a/stack-manifest.json b/stack-manifest.json index 1cda58a4..66c12462 100644 --- a/stack-manifest.json +++ b/stack-manifest.json @@ -1,7 +1,7 @@ { "schema": "the-interdependency.stack-manifest", "version": "1.1.0", - "work_graph_sha256": "889a1234456c29c27473bfddb7808b209b2e1451da10e596fa9feea0f6ea4918", + "work_graph_sha256": "dc2cac5ac1d609f7f72434514ec95a55e8020b0b9326c17883b1ef25e86baaa3", "repositories": [ { "repository": "The-Interdependency/skill-lib", @@ -70,7 +70,7 @@ "Python Gonol Construction remains stack-local research without independent repository or release authority", "URPCS is retired historical evidence after GPT-assisted implementation diverged from Erin Spencer's intended multi-arity interleaving specification; why the substitution occurred remains unresolved; immutable public intended-design transcript identity and detailed attribution remain unresolved", "skill-lib remains a special operational snapshot at stack root rather than following the libs/research pair", - "Weave preserves the intended multi-arity interleaving mechanism, while uneven partitioning, stage-input composition, schedule encoding, thread/corpus binding, hyperspace/gonol binding, authentication/state, and threat model remain unresolved", + "Weave has an explicit Stack-owned native binary sequence-cycle candidate with exact recovery; other profiles, private/public lifting, authentication/state and full-cipher security remain unresolved.", "Weave native public/private key derivation and source of asymmetry remain unresolved; source-bound key metadata does not establish a native asymmetric law.", "a0 Municipality is specification-first composition research; native semantic-origin/affixiation integration, operational authorities and its fifth workflow remain unresolved; no runtime or six-month proof is established by placement." ] @@ -198,7 +198,7 @@ "canonical_release": false, "commit": "1ba201449731337dc20c564788f4303c8910bfdd", "participant_id": "weave", - "relation": "new stack-local specification and research for Erin Spencer's full intended multi-layer encryption construction: hyperspace/gonol plaintext representation, private-gonol recovery, reconstruction-dependent threads, corpus/material binding, multi-arity bit interleaving, and intended asymmetric key relation; no URPCS implementation inheritance and no security standing yet", + "relation": "Stack-owned binary origin/sequence candidate and complete declared corpus-normalization, repeated-sequence affixiation, prime-route split and interleave recovery profile; private/public cryptographic relation remains unresolved; no URPCS inheritance or security standing", "repository": "The-Interdependency/stack", "workspace": "research/weave/" }, @@ -421,6 +421,26 @@ "relation": "unmerged draft PR #66 protocol reference only; no implemented recovery, theorem or minimum-complexity claim", "canonical_release": false, "authority_transfer": false + }, + { + "workspace": "research/weave/", + "participant_id": "weave-ucns-geometry", + "repository": "The-Interdependency/ucns", + "commit": "905e66964a495d7596a577bb64e4158db9465864", + "authority": "native exact axis-circle and Mobius geometry", + "relation": "source-verified unchanged mathematical producer for native_binary.py", + "authority_transfer": false, + "canonical_release": false + }, + { + "workspace": "research/weave/", + "participant_id": "weave-uchc-architecture", + "repository": "The-Interdependency/uchc", + "commit": "4ad94e92be10d2c4c875a848addfda46f3c7cfdb", + "authority": "origin/axis architecture and domain migration boundary", + "relation": "architectural reference only; speculative binary implementation remains in Stack until graduation gates complete", + "authority_transfer": false, + "canonical_release": false } ] }