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.
- conservation_enforcer/__init__.py +46 -0
- conservation_enforcer/assembler.py +206 -0
- conservation_enforcer/enforcer.py +100 -0
- conservation_enforcer/policies/__init__.py +228 -0
- conservation_enforcer/vm.py +412 -0
- conservation_enforcer-0.1.0.dist-info/METADATA +227 -0
- conservation_enforcer-0.1.0.dist-info/RECORD +10 -0
- conservation_enforcer-0.1.0.dist-info/WHEEL +5 -0
- conservation_enforcer-0.1.0.dist-info/licenses/LICENSE +21 -0
- conservation_enforcer-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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,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
|