conservation-enforcer 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ """Conservation Enforcer — FLUX bytecode governance for LLM outputs.
2
+
3
+ Implements conservation-law enforcement on AI behavior using deterministic
4
+ FLUX bytecode programs as policy layers.
5
+
6
+ Architecture:
7
+ User Request → LLM Call → [FLUX Conservation Validator] → Response
8
+
9
+ If violation: return correction
10
+ If clean: return response
11
+
12
+ The FLUX bytecode acts as a deterministic, auditable policy layer.
13
+ You can't lie to bytecode — it doesn't have opinions, it just executes instructions.
14
+ """
15
+
16
+ from .vm import VM, Op, Syscall, RegisterFile, Memory
17
+ from .assembler import assemble
18
+ from .enforcer import ConservationEnforcer, EnforcementResult, Violation
19
+ from .policies import (
20
+ length_budget_policy,
21
+ repetition_policy,
22
+ category_policy,
23
+ combined_policy,
24
+ entropy_policy,
25
+ )
26
+
27
+ __version__ = "0.1.0"
28
+ __author__ = "SuperInstance"
29
+ __license__ = "MIT"
30
+
31
+ __all__ = [
32
+ "VM",
33
+ "Op",
34
+ "Syscall",
35
+ "RegisterFile",
36
+ "Memory",
37
+ "assemble",
38
+ "ConservationEnforcer",
39
+ "EnforcementResult",
40
+ "Violation",
41
+ "length_budget_policy",
42
+ "repetition_policy",
43
+ "category_policy",
44
+ "combined_policy",
45
+ "entropy_policy",
46
+ ]
@@ -0,0 +1,206 @@
1
+ """FLUX Assembler — human-readable assembly → bytecode.
2
+
3
+ Single clean implementation. Two-pass with label resolution.
4
+
5
+ Supported syntax:
6
+ ; line comments # also comments
7
+ label: ; label definition
8
+ MOVI R0, 42 ; D-format: reg + i16 immediate
9
+ IADD R0, R1, R2 ; E-format: three registers
10
+ MOV R0, R1 ; C-format: two registers
11
+ INC R0 ; B-format: one register
12
+ HALT ; A-format: no operands
13
+ CMP R0, R1 ; set flags from R0 - R1
14
+ JE label ; jump if equal (flag_zero)
15
+ JNE label ; jump if not equal
16
+ JSGE label ; jump if signed ≥ (after CMP/ISUB)
17
+ JSLT label ; jump if signed <
18
+ JMP label ; unconditional jump
19
+ SYSCALL ; dispatch using R0
20
+
21
+ Convenience aliases (multi-instruction expansions):
22
+ JGE Rd, Rs, label → CMP Rd, Rs + JSGE label
23
+ JLT Rd, Rs, label → CMP Rd, Rs + JSLT label
24
+ JGT Rd, Rs, label → CMP Rd, Rs + JE skip + JSGE label
25
+ JLE Rd, Rs, label → CMP Rd, Rs + JE label + JSLT label
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import re
31
+ from typing import Optional
32
+
33
+ from .vm import Op, NUM_REGISTERS
34
+
35
+
36
+ SIMPLE = {
37
+ 'NOP': (Op.NOP, 'A'), 'HALT': (Op.HALT, 'A'), 'YIELD': (Op.YIELD, 'A'),
38
+ 'DUP': (Op.DUP, 'A'), 'RET': (Op.RET, 'A'), 'SYSCALL': (Op.SYSCALL, 'A'),
39
+ 'INC': (Op.INC, 'B'), 'DEC': (Op.DEC, 'B'),
40
+ 'PUSH': (Op.PUSH, 'B'), 'POP': (Op.POP, 'B'),
41
+ 'MOV': (Op.MOV, 'C'), 'LOAD': (Op.LOAD, 'C'), 'STORE': (Op.STORE, 'C'),
42
+ 'NEG': (Op.INEG, 'C'), 'INEG': (Op.INEG, 'C'),
43
+ 'NOT': (Op.INOT, 'C'), 'INOT': (Op.INOT, 'C'),
44
+ 'CMP': (Op.CMP, 'C'),
45
+ 'JMP': (Op.JMP, 'D'), 'JZ': (Op.JZ, 'D'), 'JNZ': (Op.JNZ, 'D'),
46
+ 'CALL': (Op.CALL, 'D'), 'MOVI': (Op.MOVI, 'D'),
47
+ 'JE': (Op.JE, 'D'), 'JNE': (Op.JNE, 'D'),
48
+ 'JSGE': (Op.JSGE, 'D'), 'JSLT': (Op.JSLT, 'D'),
49
+ 'ADD': (Op.IADD, 'E'), 'IADD': (Op.IADD, 'E'),
50
+ 'SUB': (Op.ISUB, 'E'), 'ISUB': (Op.ISUB, 'E'),
51
+ 'MUL': (Op.IMUL, 'E'), 'IMUL': (Op.IMUL, 'E'),
52
+ 'DIV': (Op.IDIV, 'E'), 'IDIV': (Op.IDIV, 'E'),
53
+ 'MOD': (Op.IMOD, 'E'), 'IMOD': (Op.IMOD, 'E'),
54
+ 'AND': (Op.IAND, 'E'), 'IAND': (Op.IAND, 'E'),
55
+ 'OR': (Op.IOR, 'E'), 'IOR': (Op.IOR, 'E'),
56
+ 'XOR': (Op.IXOR, 'E'), 'IXOR': (Op.IXOR, 'E'),
57
+ 'SHL': (Op.ISHL, 'E'), 'ISHL': (Op.ISHL, 'E'),
58
+ 'SHR': (Op.ISHR, 'E'), 'ISHR': (Op.ISHR, 'E'),
59
+ }
60
+
61
+ FMT_SIZE = {'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 4}
62
+ PSEUDO = {'JGE', 'JLE', 'JGT', 'JLT'}
63
+
64
+
65
+ class AssemblerError(Exception):
66
+ pass
67
+
68
+
69
+ def assemble(source: str) -> bytes:
70
+ """Assemble FLUX source code to bytecode."""
71
+ raw: list[dict] = []
72
+ labels: dict[str, int] = {} # label → instruction index
73
+
74
+ # ── Parse ──
75
+ for line_num, line in enumerate(source.split('\n'), 1):
76
+ text = line
77
+ for marker in (';', '#'):
78
+ idx = text.find(marker)
79
+ if idx >= 0:
80
+ text = text[:idx]
81
+ text = text.strip()
82
+ if not text:
83
+ continue
84
+
85
+ # Label?
86
+ m = re.match(r'^([A-Za-z_]\w*):\s*(.*)$', text)
87
+ if m:
88
+ label = m.group(1)
89
+ if label in labels:
90
+ raise AssemblerError(f"Duplicate label '{label}'")
91
+ labels[label] = len(raw)
92
+ text = m.group(2).strip()
93
+ if not text:
94
+ continue
95
+
96
+ # Parse mnemonic
97
+ parts = text.split(None, 1)
98
+ mnem = parts[0].upper()
99
+ rest = parts[1].strip() if len(parts) > 1 else ""
100
+
101
+ def reg(tok: str) -> int:
102
+ tok = tok.strip().upper()
103
+ if not re.match(r'^R\d+$', tok):
104
+ raise AssemblerError(f"Line {line_num}: expected register, got '{tok}'")
105
+ n = int(tok[1:])
106
+ if n >= NUM_REGISTERS:
107
+ raise AssemblerError(f"Line {line_num}: R{n} out of range")
108
+ return n
109
+
110
+ if mnem in PSEUDO:
111
+ ps = [p.strip() for p in rest.split(',')]
112
+ if len(ps) != 3:
113
+ raise AssemblerError(f"Line {line_num}: {mnem} needs Rd, Rs, label")
114
+ rd, rs = reg(ps[0]), reg(ps[1])
115
+ lbl = ps[2]
116
+
117
+ # CMP rd, rs (3 bytes)
118
+ raw.append({'op': Op.CMP, 'fmt': 'C', 'rd': rd, 'rs': rs, 'size': 3})
119
+
120
+ if mnem == 'JGE':
121
+ raw.append({'op': Op.JSGE, 'fmt': 'D', 'label': lbl, 'size': 4})
122
+ elif mnem == 'JLT':
123
+ raw.append({'op': Op.JSLT, 'fmt': 'D', 'label': lbl, 'size': 4})
124
+ elif mnem == 'JLE':
125
+ raw.append({'op': Op.JE, 'fmt': 'D', 'label': lbl, 'size': 4})
126
+ raw.append({'op': Op.JSLT, 'fmt': 'D', 'label': lbl, 'size': 4})
127
+ elif mnem == 'JGT':
128
+ # JE skip (skip the next JSGE instruction if equal)
129
+ raw.append({'op': Op.JE, 'fmt': 'D', 'imm': 4, 'size': 4})
130
+ raw.append({'op': Op.JSGE, 'fmt': 'D', 'label': lbl, 'size': 4})
131
+
132
+ elif mnem in SIMPLE:
133
+ op, fmt = SIMPLE[mnem]
134
+ entry = {'op': op, 'fmt': fmt, 'size': FMT_SIZE[fmt]}
135
+
136
+ if fmt == 'A':
137
+ pass
138
+ elif fmt == 'B':
139
+ entry['rd'] = reg(rest)
140
+ elif fmt == 'C':
141
+ ps = [p.strip() for p in rest.split(',')]
142
+ entry['rd'] = reg(ps[0])
143
+ entry['rs'] = reg(ps[1])
144
+ elif fmt == 'D':
145
+ ps = [p.strip() for p in rest.split(',')]
146
+ if len(ps) == 1:
147
+ entry['label'] = ps[0]
148
+ elif len(ps) == 2:
149
+ entry['rd'] = reg(ps[0])
150
+ v = ps[1]
151
+ if v and (v[0].isdigit() or (v[0] == '-' and len(v) > 1)):
152
+ entry['imm'] = int(v)
153
+ else:
154
+ entry['label'] = v
155
+ elif fmt == 'E':
156
+ ps = [p.strip() for p in rest.split(',')]
157
+ entry['rd'] = reg(ps[0])
158
+ entry['rs1'] = reg(ps[1])
159
+ entry['rs2'] = reg(ps[2])
160
+
161
+ raw.append(entry)
162
+ else:
163
+ raise AssemblerError(f"Line {line_num}: unknown instruction '{mnem}'")
164
+
165
+ # ── Compute byte offsets ──
166
+ offset = 0
167
+ for instr in raw:
168
+ instr['offset'] = offset
169
+ offset += instr['size']
170
+
171
+ # Build label → byte-offset map
172
+ label_bytes: dict[str, int] = {}
173
+ for lbl, idx in labels.items():
174
+ label_bytes[lbl] = raw[idx]['offset'] if idx < len(raw) else offset
175
+
176
+ # ── Emit ──
177
+ out = bytearray()
178
+ for instr in raw:
179
+ fmt = instr['fmt']
180
+ out.append(int(instr['op']))
181
+
182
+ if fmt == 'A':
183
+ pass
184
+ elif fmt == 'B':
185
+ out.append(instr['rd'])
186
+ elif fmt == 'C':
187
+ out.append(instr['rd'])
188
+ out.append(instr['rs'])
189
+ elif fmt == 'D':
190
+ out.append(instr.get('rd', 0))
191
+ if 'label' in instr:
192
+ if instr['label'] not in label_bytes:
193
+ raise AssemblerError(f"Undefined label: '{instr['label']}'")
194
+ rel = label_bytes[instr['label']] - (instr['offset'] + 4)
195
+ out.append(rel & 0xFF)
196
+ out.append((rel >> 8) & 0xFF)
197
+ else:
198
+ imm = instr.get('imm', 0)
199
+ out.append(imm & 0xFF)
200
+ out.append((imm >> 8) & 0xFF)
201
+ elif fmt == 'E':
202
+ out.append(instr['rd'])
203
+ out.append(instr['rs1'])
204
+ out.append(instr['rs2'])
205
+
206
+ return bytes(out)
@@ -0,0 +1,100 @@
1
+ """ConservationEnforcer — the main enforcement class.
2
+
3
+ Wraps any LLM call in a conservation-law check powered by FLUX bytecode.
4
+
5
+ Usage:
6
+ from conservation_enforcer import ConservationEnforcer, length_budget_policy
7
+
8
+ enforcer = ConservationEnforcer(length_budget_policy(max_tokens=500), budget=500)
9
+ result = enforcer.enforce("What is AI?", llm_response)
10
+
11
+ if result.allowed:
12
+ return result.output
13
+ else:
14
+ return result.correction
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from typing import Callable, Optional
21
+
22
+ from .vm import VM
23
+
24
+
25
+ @dataclass
26
+ class Violation:
27
+ reason: str
28
+ code: int
29
+
30
+
31
+ @dataclass
32
+ class EnforcementResult:
33
+ allowed: bool
34
+ output: str
35
+ violation: Optional[Violation] = None
36
+ cycles: int = 0
37
+
38
+ def __bool__(self) -> bool:
39
+ return self.allowed
40
+
41
+
42
+ class ConservationEnforcer:
43
+ """Enforce conservation laws on LLM outputs using FLUX bytecode.
44
+
45
+ The bytecode policy is deterministic and auditable. It runs in a
46
+ sandboxed VM and cannot be influenced by the LLM output it checks.
47
+
48
+ Convention: R0 = 0 at HALT means ALLOW. R0 ≠ 0 means BLOCK.
49
+ The policy sets R1 and calls SET_VIOLATION (syscall 8) to record the reason.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ policy_bytecode: bytes,
55
+ budget: int = 1000,
56
+ correction_template: str = (
57
+ "⚠️ This response was blocked by a conservation law: {reason}. "
58
+ "Please try again with a more conserved response."
59
+ ),
60
+ ):
61
+ self.vm = VM()
62
+ self.policy = policy_bytecode
63
+ self.budget = budget
64
+ self.correction_template = correction_template
65
+
66
+ def enforce(self, input_text: str, output_text: str) -> EnforcementResult:
67
+ """Check an LLM output against conservation laws."""
68
+ self.vm.load_input(input_text)
69
+ self.vm.load_output(output_text)
70
+ self.vm.set_budget(self.budget)
71
+
72
+ result_code = self.vm.run(self.policy) # 0 = allow, non-zero = block
73
+
74
+ if result_code == 0:
75
+ return EnforcementResult(
76
+ allowed=True,
77
+ output=output_text,
78
+ cycles=self.vm.cycle_count,
79
+ )
80
+ else:
81
+ violation = Violation(
82
+ reason=self.vm.violation_reason or "Unknown conservation violation",
83
+ code=result_code,
84
+ )
85
+ correction = self.correction_template.format(reason=violation.reason)
86
+ return EnforcementResult(
87
+ allowed=False,
88
+ output=correction,
89
+ violation=violation,
90
+ cycles=self.vm.cycle_count,
91
+ )
92
+
93
+ def enforce_with_llm(
94
+ self,
95
+ input_text: str,
96
+ llm_call: Callable[[str], str],
97
+ ) -> EnforcementResult:
98
+ """Call the LLM and enforce in one step."""
99
+ output = llm_call(input_text)
100
+ return self.enforce(input_text, output)
@@ -0,0 +1,228 @@
1
+ """Pre-built conservation policies written in FLUX assembly.
2
+
3
+ Available policies:
4
+ - length_budget_policy: Block outputs exceeding a token budget
5
+ - repetition_policy: Block outputs with excessive repetition
6
+ - category_policy: Block outputs that drift off-topic
7
+ - entropy_policy: Block outputs with too-low entropy
8
+ - combined_policy: Multiple conservation laws in one program
9
+
10
+ Policy convention:
11
+ - R0 = 0 at HALT → ALLOW
12
+ - R0 ≠ 0 at HALT → BLOCK
13
+ - Syscall number goes in R0, args in R1-R7
14
+ - SET_VIOLATION (syscall 8) reads R1 for the reason code
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from ..assembler import assemble
20
+
21
+
22
+ def length_budget_policy(max_tokens: int = 500) -> bytes:
23
+ """Enforce a maximum output length (approximate token count).
24
+
25
+ Conservation law: Information output cannot exceed the budget.
26
+ """
27
+ source = f"""
28
+ ; ── Length Budget Conservation Law ──
29
+ ; R0 = result (0=allow, non-zero=block)
30
+
31
+ MOVI R0, 0 ; default: ALLOW
32
+ MOVI R0, 5 ; syscall: GET_TOKEN_COUNT
33
+ SYSCALL
34
+ MOV R2, R0 ; R2 = token_count (return value clobbers R0)
35
+
36
+ MOVI R0, 10 ; syscall: GET_BUDGET
37
+ SYSCALL
38
+ MOV R3, R0 ; R3 = budget
39
+
40
+ ; if token_count > budget → block
41
+ JGT R2, R3, block
42
+
43
+ MOVI R0, 0 ; ALLOW
44
+ HALT
45
+
46
+ block:
47
+ MOVI R1, 1 ; reason code: LENGTH_BUDGET
48
+ MOVI R0, 8 ; syscall: SET_VIOLATION
49
+ SYSCALL
50
+ MOVI R0, 1 ; BLOCK
51
+ HALT
52
+ """
53
+ return assemble(source)
54
+
55
+
56
+ def repetition_policy(max_ratio: int = 300) -> bytes:
57
+ """Block outputs where a single word appears too frequently.
58
+
59
+ max_ratio is in per-mille (300 = 30%).
60
+ Conservation law: Information diversity.
61
+ """
62
+ source = f"""
63
+ ; ── Repetition Conservation Law ──
64
+
65
+ MOVI R0, 6 ; syscall: GET_REPETITION
66
+ SYSCALL
67
+ MOV R2, R0 ; R2 = repetition ratio
68
+
69
+ MOVI R3, {max_ratio} ; threshold
70
+
71
+ JGT R2, R3, block ; if repetition > threshold → block
72
+
73
+ MOVI R0, 0 ; ALLOW
74
+ HALT
75
+
76
+ block:
77
+ MOVI R1, 2 ; reason: REPETITION
78
+ MOVI R0, 8 ; SET_VIOLATION
79
+ SYSCALL
80
+ MOVI R0, 1 ; BLOCK
81
+ HALT
82
+ """
83
+ return assemble(source)
84
+
85
+
86
+ def category_policy(min_overlap: int = 150) -> bytes:
87
+ """Block outputs that drift too far from the input topic.
88
+
89
+ min_overlap is in per-mille (150 = 15%).
90
+ Conservation law: Category confinement.
91
+ """
92
+ source = f"""
93
+ ; ── Category Confinement Conservation Law ──
94
+
95
+ MOVI R0, 7 ; syscall: GET_CATEGORY
96
+ SYSCALL
97
+ MOV R2, R0 ; R2 = overlap score
98
+
99
+ MOVI R3, {min_overlap} ; minimum required overlap
100
+
101
+ JLT R2, R3, block ; if overlap < threshold → block
102
+
103
+ MOVI R0, 0 ; ALLOW
104
+ HALT
105
+
106
+ block:
107
+ MOVI R1, 3 ; reason: CATEGORY
108
+ MOVI R0, 8 ; SET_VIOLATION
109
+ SYSCALL
110
+ MOVI R0, 1 ; BLOCK
111
+ HALT
112
+ """
113
+ return assemble(source)
114
+
115
+
116
+ def entropy_policy(min_entropy: int = 2000) -> bytes:
117
+ """Block outputs with too-low Shannon entropy.
118
+
119
+ min_entropy is entropy × 1000 (2000 = 2.0 bits/word minimum).
120
+ Conservation law: Information density.
121
+ """
122
+ source = f"""
123
+ ; ── Entropy Conservation Law ──
124
+
125
+ MOVI R0, 12 ; syscall: GET_ENTROPY
126
+ SYSCALL
127
+ MOV R2, R0 ; R2 = entropy × 1000
128
+
129
+ MOVI R3, {min_entropy} ; minimum entropy
130
+
131
+ JLT R2, R3, block ; if entropy < threshold → block
132
+
133
+ MOVI R0, 0 ; ALLOW
134
+ HALT
135
+
136
+ block:
137
+ MOVI R1, 4 ; reason: ENTROPY
138
+ MOVI R0, 8 ; SET_VIOLATION
139
+ SYSCALL
140
+ MOVI R0, 1 ; BLOCK
141
+ HALT
142
+ """
143
+ return assemble(source)
144
+
145
+
146
+ def combined_policy(
147
+ max_tokens: int = 500,
148
+ max_repetition: int = 300,
149
+ min_overlap: int = 100,
150
+ min_entropy: int = 1500,
151
+ ) -> bytes:
152
+ """Combined conservation policy: length + repetition + category + entropy.
153
+
154
+ This is the flagship policy — four conservation laws in one bytecode program.
155
+ """
156
+ source = f"""
157
+ ; ═══════════════════════════════════════════════════════
158
+ ; COMBINED CONSERVATION POLICY
159
+ ; 1. Length budget (information quantity)
160
+ ; 2. Repetition limit (information diversity)
161
+ ; 3. Category confinement (topical coherence)
162
+ ; 4. Entropy floor (information density)
163
+ ; ═══════════════════════════════════════════════════════
164
+
165
+ ; ── Law 1: Length Budget ──
166
+ MOVI R0, 5 ; GET_TOKEN_COUNT
167
+ SYSCALL
168
+ MOV R2, R0
169
+ MOVI R0, 10 ; GET_BUDGET
170
+ SYSCALL
171
+ MOV R3, R0
172
+ JGT R2, R3, block_length
173
+
174
+ ; ── Law 2: Repetition Limit ──
175
+ MOVI R0, 6 ; GET_REPETITION
176
+ SYSCALL
177
+ MOV R2, R0
178
+ MOVI R3, {max_repetition}
179
+ JGT R2, R3, block_repetition
180
+
181
+ ; ── Law 3: Category Confinement ──
182
+ MOVI R0, 7 ; GET_CATEGORY
183
+ SYSCALL
184
+ MOV R2, R0
185
+ MOVI R3, {min_overlap}
186
+ JLT R2, R3, block_category
187
+
188
+ ; ── Law 4: Entropy Floor ──
189
+ MOVI R0, 12 ; GET_ENTROPY
190
+ SYSCALL
191
+ MOV R2, R0
192
+ MOVI R3, {min_entropy}
193
+ JLT R2, R3, block_entropy
194
+
195
+ ; ── All laws satisfied ──
196
+ MOVI R0, 0 ; ALLOW
197
+ HALT
198
+
199
+ ; ── Violation handlers ──
200
+ block_length:
201
+ MOVI R1, 1 ; reason: LENGTH
202
+ MOVI R0, 8 ; SET_VIOLATION
203
+ SYSCALL
204
+ MOVI R0, 1 ; BLOCK
205
+ HALT
206
+
207
+ block_repetition:
208
+ MOVI R1, 2 ; reason: REPETITION
209
+ MOVI R0, 8
210
+ SYSCALL
211
+ MOVI R0, 1
212
+ HALT
213
+
214
+ block_category:
215
+ MOVI R1, 3 ; reason: CATEGORY
216
+ MOVI R0, 8
217
+ SYSCALL
218
+ MOVI R0, 1
219
+ HALT
220
+
221
+ block_entropy:
222
+ MOVI R1, 4 ; reason: ENTROPY
223
+ MOVI R0, 8
224
+ SYSCALL
225
+ MOVI R0, 1
226
+ HALT
227
+ """
228
+ return assemble(source)
@@ -0,0 +1,412 @@
1
+ """FLUX Micro-VM — Register-based bytecode interpreter.
2
+
3
+ Implements the core FLUX ISA for conservation-law enforcement.
4
+ Based on the FLUX Bytecode Specification (SuperInstance/flux-core).
5
+
6
+ This is a deterministic, side-effect-free VM. It cannot lie — it executes
7
+ instructions and produces a result. That determinism is the whole point.
8
+
9
+ Instruction Formats:
10
+ A (1 byte): [opcode]
11
+ B (2 bytes): [opcode][reg:u8]
12
+ C (3 bytes): [opcode][rd:u8][rs:u8]
13
+ D (4 bytes): [opcode][reg:u8][off_lo:u8][off_hi:u8] (signed i16 offset)
14
+ E (4 bytes): [opcode][rd:u8][rs1:u8][rs2:u8]
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import struct
20
+ import math
21
+ from dataclasses import dataclass, field
22
+ from enum import IntEnum
23
+ from collections import Counter
24
+ from typing import Callable
25
+
26
+
27
+ # ── Opcodes ────────────────────────────────────────────────────────────────
28
+
29
+ class Op(IntEnum):
30
+ NOP = 0x00
31
+ MOV = 0x01
32
+ LOAD = 0x02
33
+ STORE = 0x03
34
+ JMP = 0x04
35
+ JZ = 0x05
36
+ JNZ = 0x06
37
+ CALL = 0x07
38
+ IADD = 0x08
39
+ ISUB = 0x09
40
+ IMUL = 0x0A
41
+ IDIV = 0x0B
42
+ IMOD = 0x0C
43
+ INEG = 0x0D
44
+ INC = 0x0E
45
+ DEC = 0x0F
46
+ IAND = 0x10
47
+ IOR = 0x11
48
+ IXOR = 0x12
49
+ INOT = 0x13
50
+ ISHL = 0x14
51
+ ISHR = 0x15
52
+ PUSH = 0x20
53
+ POP = 0x21
54
+ DUP = 0x22
55
+ RET = 0x28
56
+ MOVI = 0x2B
57
+ CMP = 0x2D
58
+ JE = 0x2E
59
+ JNE = 0x2F
60
+ # Sign-flag jumps (for signed comparisons after CMP/ISUB)
61
+ JSGE = 0x30 # jump if !flag_sign (signed ≥)
62
+ JSLT = 0x31 # jump if flag_sign (signed <)
63
+ HALT = 0x80
64
+ YIELD = 0x81
65
+ SYSCALL = 0xF0
66
+
67
+
68
+ # ── Exceptions ─────────────────────────────────────────────────────────────
69
+
70
+ class VMError(Exception): pass
71
+ class VMHalt(VMError): pass
72
+ class VMDivisionByZero(VMError): pass
73
+ class VMInvalidOpcode(VMError): pass
74
+
75
+
76
+ # ── Register File ──────────────────────────────────────────────────────────
77
+
78
+ NUM_REGISTERS = 16
79
+
80
+ @dataclass
81
+ class RegisterFile:
82
+ """R0–R15 general-purpose registers + condition flags."""
83
+ r: list[int] = field(default_factory=lambda: [0] * NUM_REGISTERS)
84
+ flag_zero: bool = False
85
+ flag_sign: bool = False
86
+
87
+ def get(self, idx: int) -> int:
88
+ return self.r[idx]
89
+
90
+ def set(self, idx: int, val: int) -> None:
91
+ self.r[idx] = val & 0xFFFFFFFF
92
+ self._update_flags(self.r[idx])
93
+
94
+ def _update_flags(self, uval: int) -> None:
95
+ self.flag_zero = (uval & 0xFFFFFFFF) == 0
96
+ signed = uval if uval < 0x80000000 else uval - 0x100000000
97
+ self.flag_sign = signed < 0
98
+
99
+
100
+ # ── Memory ─────────────────────────────────────────────────────────────────
101
+
102
+ class Memory:
103
+ def __init__(self, size: int = 65536):
104
+ self.buf = bytearray(size)
105
+
106
+ def store_i32(self, addr: int, val: int) -> None:
107
+ if addr + 4 <= len(self.buf):
108
+ struct.pack_into('<i', self.buf, addr, val & 0x7FFFFFFF if val < 0x80000000 else val)
109
+
110
+ def load_i32(self, addr: int) -> int:
111
+ return struct.unpack_from('<i', self.buf, addr)[0]
112
+
113
+ def store_bytes(self, addr: int, data: bytes) -> None:
114
+ end = min(addr + len(data), len(self.buf))
115
+ self.buf[addr:end] = data[:end - addr]
116
+
117
+ def load_bytes(self, addr: int, length: int) -> bytes:
118
+ return bytes(self.buf[addr:addr + length])
119
+
120
+
121
+ # ── Syscall Numbers ────────────────────────────────────────────────────────
122
+
123
+ class Syscall(IntEnum):
124
+ GET_INPUT_LEN = 1
125
+ GET_OUTPUT_LEN = 2
126
+ GET_INPUT_WORDS = 3
127
+ GET_OUTPUT_WORDS = 4
128
+ GET_TOKEN_COUNT = 5
129
+ GET_REPETITION = 6
130
+ GET_CATEGORY = 7
131
+ SET_VIOLATION = 8 # R1 = reason code
132
+ GET_BUDGET = 10
133
+ GET_UNIQUE_RATIO = 11
134
+ GET_ENTROPY = 12
135
+
136
+
137
+ VIOLATION_REASONS = {
138
+ 1: "Length budget exceeded",
139
+ 2: "Excessive repetition detected",
140
+ 3: "Category confinement violation",
141
+ 4: "Information entropy violation",
142
+ 99: "Custom conservation law violation",
143
+ }
144
+
145
+
146
+ # ── VM ─────────────────────────────────────────────────────────────────────
147
+
148
+ class VM:
149
+ """FLUX Virtual Machine — deterministic bytecode interpreter.
150
+
151
+ Usage:
152
+ vm = VM()
153
+ vm.load_input("user question")
154
+ vm.load_output("LLM response")
155
+ vm.set_budget(500)
156
+ vm.run(bytecode)
157
+ # R0 at HALT = result: 0 = allow, non-zero = block
158
+ """
159
+
160
+ MAX_CYCLES = 1_000_000
161
+
162
+ def __init__(self, memory_size: int = 65536):
163
+ self.regs = RegisterFile()
164
+ self.memory = Memory(memory_size)
165
+ self.pc = 0
166
+ self.bytecode = b''
167
+ self.running = False
168
+ self.cycle_count = 0
169
+ self._input_text = ""
170
+ self._output_text = ""
171
+ self._budget = 1000
172
+ self._violated = False
173
+ self._violation_reason = ""
174
+ self._stack: list[int] = []
175
+
176
+ @property
177
+ def violated(self) -> bool:
178
+ return self._violated
179
+
180
+ @property
181
+ def violation_reason(self) -> str:
182
+ return self._violation_reason
183
+
184
+ def load_input(self, text: str) -> None:
185
+ self._input_text = text
186
+
187
+ def load_output(self, text: str) -> None:
188
+ self._output_text = text
189
+
190
+ def set_budget(self, budget: int) -> None:
191
+ self._budget = budget
192
+
193
+ def reset(self) -> None:
194
+ self.regs = RegisterFile()
195
+ self.pc = 0
196
+ self.cycle_count = 0
197
+ self.running = False
198
+ self._violated = False
199
+ self._violation_reason = ""
200
+ self._stack.clear()
201
+
202
+ def run(self, bytecode: bytes) -> int:
203
+ """Execute bytecode. Returns R0 at HALT (0=allow, non-zero=block)."""
204
+ self.bytecode = bytecode
205
+ self.reset()
206
+ try:
207
+ while self.cycle_count < self.MAX_CYCLES:
208
+ if self.pc >= len(self.bytecode):
209
+ break
210
+ self._step()
211
+ self.cycle_count += 1
212
+ except VMHalt:
213
+ pass
214
+ if self.cycle_count >= self.MAX_CYCLES:
215
+ raise VMError(f"Cycle budget exhausted ({self.MAX_CYCLES})")
216
+ return self.regs.get(0)
217
+
218
+ def _step(self) -> None:
219
+ opcode = self.bytecode[self.pc]
220
+ try:
221
+ op = Op(opcode)
222
+ except ValueError:
223
+ raise VMInvalidOpcode(f"0x{opcode:02X} at pc={self.pc}")
224
+ handler = self._DISPATCH.get(op)
225
+ if handler is None:
226
+ raise VMInvalidOpcode(f"Unhandled 0x{opcode:02X} at pc={self.pc}")
227
+ handler(self)
228
+
229
+ # ── Decoders ──
230
+
231
+ def _d_A(self): self.pc += 1
232
+ def _d_B(self) -> int:
233
+ r = self.bytecode[self.pc + 1]; self.pc += 2; return r
234
+ def _d_C(self) -> tuple[int, int]:
235
+ rd = self.bytecode[self.pc + 1]; rs = self.bytecode[self.pc + 2]; self.pc += 3; return rd, rs
236
+ def _d_D(self) -> tuple[int, int]:
237
+ reg = self.bytecode[self.pc + 1]
238
+ lo = self.bytecode[self.pc + 2]; hi = self.bytecode[self.pc + 3]
239
+ off = lo | (hi << 8)
240
+ if off >= 0x8000: off -= 0x10000
241
+ self.pc += 4; return reg, off
242
+ def _d_E(self) -> tuple[int, int, int]:
243
+ rd = self.bytecode[self.pc + 1]; rs1 = self.bytecode[self.pc + 2]; rs2 = self.bytecode[self.pc + 3]
244
+ self.pc += 4; return rd, rs1, rs2
245
+
246
+ # ── Handlers ──
247
+
248
+ def _h_nop(self): self._d_A()
249
+ def _h_mov(self):
250
+ rd, rs = self._d_C(); self.regs.set(rd, self.regs.get(rs))
251
+ def _h_load(self):
252
+ rd, rs = self._d_C(); self.regs.set(rd, self.memory.load_i32(self.regs.get(rs)))
253
+ def _h_store(self):
254
+ rd, rs = self._d_C(); self.memory.store_i32(self.regs.get(rs), self.regs.get(rd))
255
+ def _h_jmp(self):
256
+ _, off = self._d_D(); self.pc += off
257
+ def _h_jz(self):
258
+ reg, off = self._d_D()
259
+ if self.regs.get(reg) == 0: self.pc += off
260
+ def _h_jnz(self):
261
+ reg, off = self._d_D()
262
+ if self.regs.get(reg) != 0: self.pc += off
263
+ def _h_call(self):
264
+ reg, off = self._d_D(); self._stack.append(self.pc); self.pc += off
265
+ def _h_iadd(self):
266
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) + self.regs.get(rs2))
267
+ def _h_isub(self):
268
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) - self.regs.get(rs2))
269
+ def _h_imul(self):
270
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) * self.regs.get(rs2))
271
+ def _h_idiv(self):
272
+ rd, rs1, rs2 = self._d_E()
273
+ d = self.regs.get(rs2)
274
+ if d == 0: raise VMDivisionByZero(f"pc={self.pc}")
275
+ self.regs.set(rd, self.regs.get(rs1) // d)
276
+ def _h_imod(self):
277
+ rd, rs1, rs2 = self._d_E()
278
+ d = self.regs.get(rs2)
279
+ if d == 0: raise VMDivisionByZero(f"pc={self.pc}")
280
+ self.regs.set(rd, self.regs.get(rs1) % d)
281
+ def _h_ineg(self):
282
+ rd, rs = self._d_C(); self.regs.set(rd, (-self.regs.get(rs)) & 0xFFFFFFFF)
283
+ def _h_inc(self):
284
+ reg = self._d_B(); self.regs.set(reg, self.regs.get(reg) + 1)
285
+ def _h_dec(self):
286
+ reg = self._d_B(); self.regs.set(reg, self.regs.get(reg) - 1)
287
+ def _h_iand(self):
288
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) & self.regs.get(rs2))
289
+ def _h_ior(self):
290
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) | self.regs.get(rs2))
291
+ def _h_ixor(self):
292
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) ^ self.regs.get(rs2))
293
+ def _h_inot(self):
294
+ rd, rs = self._d_C(); self.regs.set(rd, (~self.regs.get(rs)) & 0xFFFFFFFF)
295
+ def _h_ishl(self):
296
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) << self.regs.get(rs2))
297
+ def _h_ishr(self):
298
+ rd, rs1, rs2 = self._d_E(); self.regs.set(rd, self.regs.get(rs1) >> self.regs.get(rs2))
299
+ def _h_push(self):
300
+ reg = self._d_B(); self._stack.append(self.regs.get(reg))
301
+ def _h_pop(self):
302
+ reg = self._d_B(); self.regs.set(reg, self._stack.pop() if self._stack else 0)
303
+ def _h_dup(self):
304
+ self._d_A()
305
+ if self._stack: self._stack.append(self._stack[-1])
306
+ def _h_ret(self):
307
+ self._d_A()
308
+ if self._stack: self.pc = self._stack.pop()
309
+ else: self.running = False
310
+ def _h_movi(self):
311
+ reg, off = self._d_D()
312
+ # Store as signed value wrapped to 32-bit unsigned
313
+ self.regs.set(reg, off & 0xFFFF)
314
+ def _h_cmp(self):
315
+ rd, rs = self._d_C()
316
+ a = self.regs.get(rd); b = self.regs.get(rs)
317
+ diff = (a - b) & 0xFFFFFFFF
318
+ self.regs.flag_zero = diff == 0
319
+ signed = diff if diff < 0x80000000 else diff - 0x100000000
320
+ self.regs.flag_sign = signed < 0
321
+ def _h_je(self):
322
+ _, off = self._d_D()
323
+ if self.regs.flag_zero: self.pc += off
324
+ def _h_jne(self):
325
+ _, off = self._d_D()
326
+ if not self.regs.flag_zero: self.pc += off
327
+ def _h_jsge(self):
328
+ _, off = self._d_D()
329
+ if not self.regs.flag_sign: self.pc += off
330
+ def _h_jslt(self):
331
+ _, off = self._d_D()
332
+ if self.regs.flag_sign: self.pc += off
333
+ def _h_syscall(self):
334
+ self._d_A()
335
+ num = self.regs.get(0)
336
+ handler = self._SYSCALLS.get(num)
337
+ if handler: handler(self)
338
+ def _h_halt(self):
339
+ self._d_A(); self.running = False; raise VMHalt("HALT instruction")
340
+ def _h_yield(self):
341
+ self._d_A()
342
+
343
+ # ── Dispatch table ──
344
+ _DISPATCH: dict[Op, Callable] = {}
345
+
346
+
347
+ # ── Syscall implementations ────────────────────────────────────────────────
348
+
349
+ def _sys_input_len(vm: VM): vm.regs.set(0, len(vm._input_text))
350
+ def _sys_output_len(vm: VM): vm.regs.set(0, len(vm._output_text))
351
+ def _sys_input_words(vm: VM): vm.regs.set(0, len(vm._input_text.split()))
352
+ def _sys_output_words(vm: VM): vm.regs.set(0, len(vm._output_text.split()))
353
+ def _sys_token_count(vm: VM): vm.regs.set(0, max(1, len(vm._output_text) // 4))
354
+ def _sys_repetition(vm: VM):
355
+ words = vm._output_text.lower().split()
356
+ if not words: vm.regs.set(0, 0); return
357
+ counts = Counter(words)
358
+ mx = counts.most_common(1)[0][1]
359
+ vm.regs.set(0, (mx * 1000) // len(words))
360
+ def _sys_category(vm: VM):
361
+ iw = set(vm._input_text.lower().split()); ow = set(vm._output_text.lower().split())
362
+ if not ow: vm.regs.set(0, 0); return
363
+ ov = len(iw & ow)
364
+ vm.regs.set(0, min(1000, (ov * 1000) // len(ow)))
365
+ def _sys_set_violation(vm: VM):
366
+ vm._violated = True
367
+ code = vm.regs.get(1)
368
+ vm._violation_reason = VIOLATION_REASONS.get(code, f"Violation code {code}")
369
+ def _sys_get_budget(vm: VM): vm.regs.set(0, vm._budget)
370
+ def _sys_unique_ratio(vm: VM):
371
+ words = vm._output_text.lower().split()
372
+ if not words: vm.regs.set(0, 1000); return
373
+ vm.regs.set(0, (len(set(words)) * 1000) // len(words))
374
+ def _sys_entropy(vm: VM):
375
+ words = vm._output_text.lower().split()
376
+ if not words: vm.regs.set(0, 0); return
377
+ total = len(words); ent = 0.0
378
+ for c in Counter(words).values():
379
+ p = c / total; ent -= p * math.log2(p)
380
+ vm.regs.set(0, int(ent * 1000))
381
+
382
+
383
+ # ── Build dispatch + syscall tables ────────────────────────────────────────
384
+
385
+ VM._DISPATCH = {
386
+ Op.NOP: VM._h_nop, Op.MOV: VM._h_mov, Op.LOAD: VM._h_load,
387
+ Op.STORE: VM._h_store, Op.JMP: VM._h_jmp, Op.JZ: VM._h_jz,
388
+ Op.JNZ: VM._h_jnz, Op.CALL: VM._h_call, Op.IADD: VM._h_iadd,
389
+ Op.ISUB: VM._h_isub, Op.IMUL: VM._h_imul, Op.IDIV: VM._h_idiv,
390
+ Op.IMOD: VM._h_imod, Op.INEG: VM._h_ineg, Op.INC: VM._h_inc,
391
+ Op.DEC: VM._h_dec, Op.IAND: VM._h_iand, Op.IOR: VM._h_ior,
392
+ Op.IXOR: VM._h_ixor, Op.INOT: VM._h_inot, Op.ISHL: VM._h_ishl,
393
+ Op.ISHR: VM._h_ishr, Op.PUSH: VM._h_push, Op.POP: VM._h_pop,
394
+ Op.DUP: VM._h_dup, Op.RET: VM._h_ret, Op.MOVI: VM._h_movi,
395
+ Op.CMP: VM._h_cmp, Op.JE: VM._h_je, Op.JNE: VM._h_jne,
396
+ Op.JSGE: VM._h_jsge, Op.JSLT: VM._h_jslt,
397
+ Op.SYSCALL: VM._h_syscall, Op.HALT: VM._h_halt, Op.YIELD: VM._h_yield,
398
+ }
399
+
400
+ VM._SYSCALLS = {
401
+ int(Syscall.GET_INPUT_LEN): _sys_input_len,
402
+ int(Syscall.GET_OUTPUT_LEN): _sys_output_len,
403
+ int(Syscall.GET_INPUT_WORDS): _sys_input_words,
404
+ int(Syscall.GET_OUTPUT_WORDS): _sys_output_words,
405
+ int(Syscall.GET_TOKEN_COUNT): _sys_token_count,
406
+ int(Syscall.GET_REPETITION): _sys_repetition,
407
+ int(Syscall.GET_CATEGORY): _sys_category,
408
+ int(Syscall.SET_VIOLATION): _sys_set_violation,
409
+ int(Syscall.GET_BUDGET): _sys_get_budget,
410
+ int(Syscall.GET_UNIQUE_RATIO): _sys_unique_ratio,
411
+ int(Syscall.GET_ENTROPY): _sys_entropy,
412
+ }
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: conservation-enforcer
3
+ Version: 0.1.0
4
+ Summary: FLUX bytecode conservation-law enforcement for LLM outputs
5
+ Author: SuperInstance
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=7.0; extra == "dev"
12
+ Dynamic: license-file
13
+
14
+ # ⚡ Conservation Enforcer
15
+
16
+ **FLUX bytecode conservation-law enforcement for LLM outputs.**
17
+
18
+ This is a working prototype that demonstrates conservation-law governance on AI behavior. It wraps any LLM call in a deterministic, auditable policy layer implemented as FLUX bytecode.
19
+
20
+ ```
21
+ User Request → LLM Call → [FLUX Conservation Validator] → Response
22
+
23
+ If violation: return correction
24
+ If clean: return response
25
+ ```
26
+
27
+ The FLUX bytecode acts as a deterministic, auditable policy layer. **You can't lie to bytecode** — it doesn't have opinions, it just executes instructions.
28
+
29
+ ## Why This Matters
30
+
31
+ Current AI alignment approaches rely on:
32
+ - **Prompt engineering** (the LLM can ignore instructions)
33
+ - **RLHF tuning** (opaque, hard to verify)
34
+ - **Output filtering** (post-hoc, not deterministic)
35
+
36
+ Conservation enforcement is different:
37
+ - **Deterministic**: Same input always produces the same decision
38
+ - **Auditable**: Every byte of policy can be inspected and verified
39
+ - **Immune to manipulation**: Bytecode has no opinions to argue with
40
+ - **Composable**: Multiple conservation laws combine into one program
41
+ - **Portable**: FLUX bytecode runs on Python, Rust, and JS VMs
42
+
43
+ Conservation laws are the foundation of physics. Noether's theorem connects symmetries to conservation laws. This project brings that same mathematical rigor to AI governance.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install -e .
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from conservation_enforcer import ConservationEnforcer, combined_policy
55
+
56
+ # Create a combined policy: length + repetition + category + entropy
57
+ policy = combined_policy(
58
+ max_tokens=500,
59
+ max_repetition=300,
60
+ min_overlap=100,
61
+ min_entropy=1500,
62
+ )
63
+
64
+ enforcer = ConservationEnforcer(policy, budget=500)
65
+
66
+ # Check an LLM output
67
+ result = enforcer.enforce(
68
+ input_text="What is machine learning?",
69
+ output_text="Machine learning is a subset of AI that learns from data.",
70
+ )
71
+
72
+ if result.allowed:
73
+ print(result.output)
74
+ else:
75
+ print(f"Blocked: {result.violation.reason}")
76
+ ```
77
+
78
+ ## Conservation Laws
79
+
80
+ ### 1. Length Budget (Information Quantity)
81
+ ```python
82
+ from conservation_enforcer import length_budget_policy
83
+
84
+ policy = length_budget_policy(max_tokens=500)
85
+ ```
86
+ The output cannot exceed the allocated information budget. This is analogous to energy conservation in physics — you can't output more information than you were allocated.
87
+
88
+ ### 2. Repetition Limit (Information Diversity)
89
+ ```python
90
+ from conservation_enforcer import repetition_policy
91
+
92
+ policy = repetition_policy(max_ratio=300) # max 30% repetition
93
+ ```
94
+ The output must maintain diversity. Degenerate repetition is the informational equivalent of thermal equilibrium — a system that repeats has exhausted its information capacity.
95
+
96
+ ### 3. Category Confinement (Topical Coherence)
97
+ ```python
98
+ from conservation_enforcer import category_policy
99
+
100
+ policy = category_policy(min_overlap=150) # 15% word overlap required
101
+ ```
102
+ The output must stay within the category/domain of the input. This prevents hallucinated content and topic drift — the output's "state" must remain in the same "potential well" as the input.
103
+
104
+ ### 4. Entropy Floor (Information Density)
105
+ ```python
106
+ from conservation_enforcer import entropy_policy
107
+
108
+ policy = entropy_policy(min_entropy=2000) # 2.0 bits/word minimum
109
+ ```
110
+ The output must have sufficient Shannon entropy. Low entropy means the output is too predictable and not carrying enough information.
111
+
112
+ ### Combined Policy (All Four Laws)
113
+ ```python
114
+ from conservation_enforcer import combined_policy
115
+
116
+ policy = combined_policy(
117
+ max_tokens=500,
118
+ max_repetition=300,
119
+ min_overlap=100,
120
+ min_entropy=1500,
121
+ )
122
+ ```
123
+
124
+ ## Writing Custom Policies
125
+
126
+ Policies are written in FLUX assembly:
127
+
128
+ ```flux
129
+ ; Custom policy: block outputs longer than 100 tokens
130
+ MOVI R0, 5 ; syscall: GET_TOKEN_COUNT
131
+ SYSCALL
132
+ MOV R2, R0 ; save token count
133
+
134
+ MOVI R0, 10 ; syscall: GET_BUDGET
135
+ SYSCALL
136
+ MOV R3, R0 ; save budget
137
+
138
+ JGT R2, R3, block ; if tokens > budget → block
139
+
140
+ MOVI R0, 0 ; ALLOW
141
+ HALT
142
+
143
+ block:
144
+ MOVI R1, 1 ; reason: LENGTH_BUDGET
145
+ MOVI R0, 8 ; syscall: SET_VIOLATION
146
+ SYSCALL
147
+ MOVI R0, 1 ; BLOCK
148
+ HALT
149
+ ```
150
+
151
+ Compile and use:
152
+ ```python
153
+ from conservation_enforcer import assemble, ConservationEnforcer
154
+
155
+ bytecode = assemble(source_code)
156
+ enforcer = ConservationEnforcer(bytecode, budget=100)
157
+ result = enforcer.enforce("question", "response")
158
+ ```
159
+
160
+ ## FLUX ISA
161
+
162
+ The VM implements a register-based ISA with 16 registers (R0–R15):
163
+
164
+ | Format | Layout | Example |
165
+ |--------|--------|---------|
166
+ | A | `[opcode]` | `HALT` |
167
+ | B | `[opcode][reg]` | `INC R0` |
168
+ | C | `[opcode][rd][rs]` | `CMP R0, R1` |
169
+ | D | `[opcode][reg][off_lo][off_hi]` | `JE label` |
170
+ | E | `[opcode][rd][rs1][rs2]` | `IADD R0, R1, R2` |
171
+
172
+ Key instructions: `MOVI`, `MOV`, `IADD`, `ISUB`, `IMUL`, `IDIV`, `CMP`, `JE`, `JNE`, `JSGE`, `JSLT`, `SYSCALL`, `HALT`.
173
+
174
+ Pseudo-instructions for convenience: `JGE`, `JGT`, `JLE`, `JLT`.
175
+
176
+ ### Syscalls
177
+
178
+ | # | Name | Returns |
179
+ |---|------|---------|
180
+ | 1 | GET_INPUT_LEN | Length of input text |
181
+ | 2 | GET_OUTPUT_LEN | Length of output text |
182
+ | 5 | GET_TOKEN_COUNT | Approximate token count |
183
+ | 6 | GET_REPETITION | Max word frequency ratio × 1000 |
184
+ | 7 | GET_CATEGORY | Input/output word overlap × 1000 |
185
+ | 8 | SET_VIOLATION | Sets violation flag (R1 = reason code) |
186
+ | 10 | GET_BUDGET | Configured information budget |
187
+ | 11 | GET_UNIQUE_RATIO | Unique/total words × 1000 |
188
+ | 12 | GET_ENTROPY | Shannon entropy × 1000 |
189
+
190
+ ## Demonstration
191
+
192
+ ```bash
193
+ python examples/demo.py
194
+ ```
195
+
196
+ ## Tests
197
+
198
+ ```bash
199
+ python -m pytest tests/ -v
200
+ ```
201
+
202
+ ## Architecture
203
+
204
+ ```
205
+ src/conservation_enforcer/
206
+ ├── __init__.py Public API
207
+ ├── vm.py FLUX VM (register-based bytecode interpreter)
208
+ ├── assembler.py Two-pass FLUX assembler with label resolution
209
+ ├── enforcer.py ConservationEnforcer class
210
+ └── policies/
211
+ └── __init__.py Pre-built conservation policies in FLUX assembly
212
+ ```
213
+
214
+ ## Related Projects
215
+
216
+ - [flux-runtime](https://github.com/SuperInstance/flux-runtime) — Full FLUX runtime (Python)
217
+ - [flux-core](https://github.com/SuperInstance/flux-core) — FLUX bytecode runtime (Rust)
218
+ - [flux-js](https://github.com/SuperInstance/flux-js) — FLUX VM (JavaScript)
219
+ - [conservation-law-rs](https://github.com/SuperInstance/conservation-law-rs) — Conservation laws for agent dynamics
220
+
221
+ ## License
222
+
223
+ MIT
224
+
225
+ ---
226
+
227
+ *This is not alignment theory. This is enforcement engineering.*
@@ -0,0 +1,10 @@
1
+ conservation_enforcer/__init__.py,sha256=dfdtu29KzzTEVQYmsCwMpasbzMQkLObzPBIvPOtTldg,1269
2
+ conservation_enforcer/assembler.py,sha256=SegUwotLko1R658JuKn9TUVWTG5F8HuL9MnUnwtgF6A,7567
3
+ conservation_enforcer/enforcer.py,sha256=i6U1VMYD-Fgi6xjO0gQf-_Yx7VLH0CyWr_dVZFkmBqE,2949
4
+ conservation_enforcer/vm.py,sha256=pesIWA-EMC_gx2QS6V3F2qHQZ_3zw0PTxDcYppJn3JY,15191
5
+ conservation_enforcer/policies/__init__.py,sha256=iXAqaT1srW4EKGNafLp2kH0g9WWrcP7WrtQy0uJQrxA,6102
6
+ conservation_enforcer-0.1.0.dist-info/licenses/LICENSE,sha256=oIZ4iMwyoGNfEZRn_qZ2bqd1wzV8oUQgjyS-ZAEDLEI,1070
7
+ conservation_enforcer-0.1.0.dist-info/METADATA,sha256=FdPcau46uCPHFSslqiKQf7OJh-uujkdnYMa8Ay1XmuU,6906
8
+ conservation_enforcer-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
9
+ conservation_enforcer-0.1.0.dist-info/top_level.txt,sha256=z0IaZgkBReeXdyGpzCuvho3Li6KopmQAJKILcs4LtLk,22
10
+ conservation_enforcer-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SuperInstance
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ conservation_enforcer