packet-tracer-skill 0.2.2 → 0.3.0
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.
- package/CHANGELOG.md +424 -42
- package/README.md +535 -250
- package/SKILL.md +337 -262
- package/bin/packet-tracer-skill.js +29 -2
- package/docs/automation-controller-proof.md +35 -0
- package/docs/curated-donor-registry.md +11 -0
- package/docs/generate-ready-pilot-design.md +30 -0
- package/docs/github-launch-ops-0.2.3.md +37 -0
- package/docs/github-metadata.md +6 -4
- package/docs/hero-demo-plan.md +1 -1
- package/docs/home-iot-donor-proof.md +4 -4
- package/docs/industrial-programming-proof.md +48 -0
- package/docs/ipv4-routing-management-proof.md +37 -0
- package/docs/l2-resiliency-bgp-proof.md +60 -0
- package/docs/l2-security-qos-proof.md +59 -0
- package/docs/packet-tracer-feature-gap-atlas.md +174 -17
- package/docs/post-launch-follow-up.md +9 -5
- package/docs/proof-readiness-dashboard.md +69 -0
- package/docs/publish-preview-roadmap.md +6 -5
- package/docs/release-checklist.md +27 -13
- package/docs/release-notes-0.2.2.md +1 -1
- package/docs/release-notes-0.2.3.md +59 -0
- package/docs/release-notes-0.2.4.md +20 -0
- package/docs/runtime-truth.md +33 -8
- package/docs/security-edge-deepening-proof.md +65 -0
- package/docs/voice-collaboration-proof.md +38 -0
- package/docs/wan-security-donor-proof.md +20 -3
- package/examples/README.md +98 -69
- package/examples/complex_campus_master_edit_v4.inventory.json +12 -2
- package/examples/gallery.md +94 -6
- package/examples/home_iot_cli_edit_v1.inventory.json +11 -2
- package/examples/index.json +932 -4
- package/examples/local-sample-evidence.json +24 -0
- package/examples/proof-cards.json +117 -0
- package/examples/service_heavy_cli_edit_v1.inventory.json +11 -2
- package/package.json +60 -44
- package/pytest.ini +9 -0
- package/references/packettracer-feature-atlas.json +67 -17
- package/references/packettracer-sample-catalog.json +45287 -4525
- package/references/packettracer-sample-catalog.md +599 -259
- package/references/proof-readiness-candidates.json +352 -0
- package/scripts/build_examples_index.py +228 -35
- package/scripts/build_sample_catalog.py +24 -44
- package/scripts/corpus_runner.py +430 -0
- package/scripts/coverage_matrix.py +1842 -1319
- package/scripts/donor_cache.py +354 -0
- package/scripts/donor_diagnostics.py +3 -1
- package/scripts/feature_atlas.py +65 -1
- package/scripts/generate_pkt.py +8762 -4070
- package/scripts/intent_parser.py +2242 -1138
- package/scripts/local_donors.py +340 -0
- package/scripts/packet_tracer_env.py +846 -391
- package/scripts/pkt_annotate.py +218 -0
- package/scripts/pkt_codec.py +420 -181
- package/scripts/pkt_editor.py +2405 -1226
- package/scripts/pkt_transformer.py +1072 -727
- package/scripts/pkt_verify.py +461 -0
- package/scripts/remote_search.py +197 -21
- package/scripts/runtime_doctor.py +80 -29
- package/scripts/sample_catalog.py +1372 -1195
- package/scripts/twofish_diagnostics.py +48 -31
- package/scripts/usage_ledger.py +218 -0
- package/scripts/vendor/README.md +44 -37
- package/scripts/vendor/twofish_pure.py +321 -0
- package/scripts/workspace_repair.py +548 -508
- package/templates/pt900/donors/README.md +15 -0
|
@@ -23,9 +23,25 @@ def _vendor_dir() -> Path:
|
|
|
23
23
|
return Path(__file__).resolve().parent / "vendor"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
MINIMUM_PYTHON = (3, 10)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _pure_python_status() -> tuple[bool, str]:
|
|
30
|
+
"""Verify the vendored pure-Python engine against the official vectors."""
|
|
31
|
+
try:
|
|
32
|
+
sys.path.insert(0, str(_vendor_dir()))
|
|
33
|
+
from twofish_pure import self_test
|
|
34
|
+
|
|
35
|
+
self_test()
|
|
36
|
+
return True, "vendored pure-Python Twofish (official test vectors pass)"
|
|
37
|
+
except Exception as exc: # pragma: no cover - runtime diagnostics
|
|
38
|
+
return False, f"vendored pure-Python Twofish failed: {exc}"
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
def collect_twofish_diagnostics() -> dict[str, str]:
|
|
27
42
|
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
28
|
-
python_supported = sys.version_info[:2]
|
|
43
|
+
python_supported = sys.version_info[:2] >= MINIMUM_PYTHON
|
|
44
|
+
compiled_abi_match = sys.version_info[:2] == SUPPORTED_PYTHON
|
|
29
45
|
result = {
|
|
30
46
|
"host_os": normalized_host_os(),
|
|
31
47
|
"python_version": python_version,
|
|
@@ -33,49 +49,50 @@ def collect_twofish_diagnostics() -> dict[str, str]:
|
|
|
33
49
|
"python_support_message": (
|
|
34
50
|
"supported"
|
|
35
51
|
if python_supported
|
|
36
|
-
else f"requires Python {
|
|
52
|
+
else f"requires Python {MINIMUM_PYTHON[0]}.{MINIMUM_PYTHON[1]} or newer"
|
|
37
53
|
),
|
|
38
54
|
"expected_twofish_patterns": expected_bridge_patterns(),
|
|
39
55
|
"twofish_search_roots": recommended_search_roots(_vendor_dir()),
|
|
40
56
|
"resolved_twofish_path": "",
|
|
41
57
|
"twofish_source": "",
|
|
58
|
+
"twofish_backend": "",
|
|
42
59
|
"twofish_load_status": "missing",
|
|
43
|
-
"twofish_message": "no
|
|
60
|
+
"twofish_message": "no Twofish engine could be resolved",
|
|
44
61
|
"twofish_sha256": "",
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
# The compiled bridge is an optional accelerator: preferred when it loads,
|
|
65
|
+
# but never required, because the vendored pure-Python engine always works.
|
|
66
|
+
if compiled_abi_match:
|
|
67
|
+
for source, candidate in candidate_bridge_paths(
|
|
68
|
+
_vendor_dir(), env_path=os.getenv("PKT_TWOFISH_LIBRARY")
|
|
69
|
+
):
|
|
70
|
+
if not candidate.exists():
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
library = CDLL(str(candidate))
|
|
74
|
+
getattr(library, "exp_Twofish_encrypt")
|
|
75
|
+
getattr(library, "exp_Twofish_decrypt")
|
|
76
|
+
except Exception: # pragma: no cover - runtime diagnostics
|
|
77
|
+
continue
|
|
78
|
+
result["resolved_twofish_path"] = str(candidate)
|
|
79
|
+
result["twofish_source"] = source
|
|
80
|
+
result["twofish_sha256"] = hashlib.sha256(candidate.read_bytes()).hexdigest()
|
|
81
|
+
result["twofish_backend"] = "compiled"
|
|
64
82
|
result["twofish_load_status"] = "ok"
|
|
65
|
-
result["twofish_message"] = f"loaded {candidate}"
|
|
66
|
-
return result
|
|
67
|
-
except Exception as exc: # pragma: no cover - runtime diagnostics
|
|
68
|
-
result["twofish_load_status"] = "load_error"
|
|
69
|
-
result["twofish_message"] = f"{candidate}: {exc}"
|
|
83
|
+
result["twofish_message"] = f"loaded compiled accelerator {candidate}"
|
|
70
84
|
return result
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
if
|
|
74
|
-
result["
|
|
75
|
-
result["twofish_source"] = "
|
|
76
|
-
result["
|
|
77
|
-
result["
|
|
86
|
+
pure_ok, pure_message = _pure_python_status()
|
|
87
|
+
if pure_ok and python_supported:
|
|
88
|
+
result["twofish_backend"] = "pure_python"
|
|
89
|
+
result["twofish_source"] = "vendored"
|
|
90
|
+
result["resolved_twofish_path"] = str(_vendor_dir() / "twofish_pure.py")
|
|
91
|
+
result["twofish_load_status"] = "ok"
|
|
92
|
+
result["twofish_message"] = pure_message
|
|
93
|
+
return result
|
|
78
94
|
|
|
95
|
+
result["twofish_message"] = pure_message
|
|
79
96
|
return result
|
|
80
97
|
|
|
81
98
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""A local, append-only record of what actually worked, so the skill improves with use.
|
|
3
|
+
|
|
4
|
+
Donor selection is expensive and mostly repetitive: the same scenario families
|
|
5
|
+
come back, and the same handful of donors keep winning or keep failing for the
|
|
6
|
+
same reasons. Rediscovering that on every run wastes several seconds per
|
|
7
|
+
candidate and, worse, throws away the only real evidence the skill ever gets
|
|
8
|
+
about which donors survive a Packet Tracer open.
|
|
9
|
+
|
|
10
|
+
The ledger records the outcome of each generation and feeds it back into donor
|
|
11
|
+
ranking on the next run. Nothing is inferred or guessed: an entry is written
|
|
12
|
+
only after a real attempt produced a real result.
|
|
13
|
+
|
|
14
|
+
Privacy and safety rules, deliberately strict:
|
|
15
|
+
|
|
16
|
+
- the ledger is local only. It is written under `output/`, which is gitignored,
|
|
17
|
+
and it is never committed, packaged, or transmitted anywhere.
|
|
18
|
+
- prompts are stored as a normalised fingerprint, not verbatim text, so a lab
|
|
19
|
+
description containing names or addresses does not end up on disk.
|
|
20
|
+
- the file is bounded. Old entries are dropped once the cap is reached.
|
|
21
|
+
- a corrupt or unreadable ledger is ignored, never fatal. Learning is an
|
|
22
|
+
optimisation; the skill must work identically with the ledger deleted.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
import re
|
|
31
|
+
from collections import defaultdict
|
|
32
|
+
from dataclasses import dataclass, field
|
|
33
|
+
from datetime import datetime, timezone
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
SKILL_ROOT = Path(__file__).resolve().parents[1]
|
|
37
|
+
DEFAULT_LEDGER_PATH = SKILL_ROOT / "output" / "usage-ledger.jsonl"
|
|
38
|
+
MAX_ENTRIES = 2000
|
|
39
|
+
LEDGER_VERSION = 1
|
|
40
|
+
|
|
41
|
+
OUTCOME_GENERATED_VERIFIED = "generated_verified"
|
|
42
|
+
OUTCOME_GENERATED_UNVERIFIED = "generated_unverified"
|
|
43
|
+
OUTCOME_REFUSED = "refused"
|
|
44
|
+
OUTCOMES = (OUTCOME_GENERATED_VERIFIED, OUTCOME_GENERATED_UNVERIFIED, OUTCOME_REFUSED)
|
|
45
|
+
|
|
46
|
+
# Outcomes that count as evidence a donor works, best first.
|
|
47
|
+
_SUCCESS_WEIGHT = {
|
|
48
|
+
OUTCOME_GENERATED_VERIFIED: 3,
|
|
49
|
+
OUTCOME_GENERATED_UNVERIFIED: 1,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ledger_path() -> Path:
|
|
54
|
+
override = os.getenv("PKT_USAGE_LEDGER")
|
|
55
|
+
return Path(override).expanduser() if override else DEFAULT_LEDGER_PATH
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def ledger_enabled() -> bool:
|
|
59
|
+
"""Learning is on by default; `PKT_USAGE_LEDGER=off` disables it entirely."""
|
|
60
|
+
return (os.getenv("PKT_USAGE_LEDGER") or "").strip().lower() not in {"off", "0", "false", "none"}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def prompt_fingerprint(prompt: str) -> str:
|
|
64
|
+
"""A stable, non-reversible fingerprint of a prompt's *shape*.
|
|
65
|
+
|
|
66
|
+
Digits are collapsed to `#` and case and spacing are normalised, so
|
|
67
|
+
"3 switch 6 pc" and "5 switch 2 pc" share a fingerprint: they are the same
|
|
68
|
+
kind of request, which is exactly the granularity donor reuse needs. The
|
|
69
|
+
result is hashed so no prompt text is ever written to disk.
|
|
70
|
+
"""
|
|
71
|
+
normalised = re.sub(r"\d+", "#", (prompt or "").strip().lower())
|
|
72
|
+
normalised = re.sub(r"[^\w#]+", " ", normalised).strip()
|
|
73
|
+
return hashlib.sha256(normalised.encode("utf-8")).hexdigest()[:16]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class LedgerEntry:
|
|
78
|
+
scenario_family: str
|
|
79
|
+
donor: str
|
|
80
|
+
outcome: str
|
|
81
|
+
prompt_shape: str = ""
|
|
82
|
+
target_version: str = ""
|
|
83
|
+
donor_version: str = ""
|
|
84
|
+
rejected_donors: list[str] = field(default_factory=list)
|
|
85
|
+
rejection_codes: list[str] = field(default_factory=list)
|
|
86
|
+
recorded_at: str = ""
|
|
87
|
+
|
|
88
|
+
def to_json(self) -> dict[str, object]:
|
|
89
|
+
return {
|
|
90
|
+
"v": LEDGER_VERSION,
|
|
91
|
+
"recorded_at": self.recorded_at or datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
92
|
+
"scenario_family": self.scenario_family,
|
|
93
|
+
"prompt_shape": self.prompt_shape,
|
|
94
|
+
"donor": self.donor,
|
|
95
|
+
"donor_version": self.donor_version,
|
|
96
|
+
"target_version": self.target_version,
|
|
97
|
+
"outcome": self.outcome,
|
|
98
|
+
"rejected_donors": self.rejected_donors[:20],
|
|
99
|
+
"rejection_codes": self.rejection_codes[:20],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def record(entry: LedgerEntry, path: Path | None = None) -> bool:
|
|
104
|
+
"""Append one outcome. Returns False if the ledger is off or unwritable."""
|
|
105
|
+
if not ledger_enabled():
|
|
106
|
+
return False
|
|
107
|
+
if entry.outcome not in OUTCOMES:
|
|
108
|
+
raise ValueError(f"unknown outcome: {entry.outcome}")
|
|
109
|
+
|
|
110
|
+
target = path or ledger_path()
|
|
111
|
+
try:
|
|
112
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
with target.open("a", encoding="utf-8") as handle:
|
|
114
|
+
handle.write(json.dumps(entry.to_json(), ensure_ascii=False) + "\n")
|
|
115
|
+
except OSError:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
_trim(target)
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _trim(path: Path) -> None:
|
|
123
|
+
try:
|
|
124
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
125
|
+
except OSError:
|
|
126
|
+
return
|
|
127
|
+
if len(lines) <= MAX_ENTRIES:
|
|
128
|
+
return
|
|
129
|
+
try:
|
|
130
|
+
path.write_text("\n".join(lines[-MAX_ENTRIES:]) + "\n", encoding="utf-8")
|
|
131
|
+
except OSError:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def load_entries(path: Path | None = None) -> list[dict[str, object]]:
|
|
136
|
+
"""Read the ledger. A damaged file yields whatever lines still parse."""
|
|
137
|
+
target = path or ledger_path()
|
|
138
|
+
if not ledger_enabled() or not target.exists():
|
|
139
|
+
return []
|
|
140
|
+
entries: list[dict[str, object]] = []
|
|
141
|
+
try:
|
|
142
|
+
raw_lines = target.read_text(encoding="utf-8").splitlines()
|
|
143
|
+
except OSError:
|
|
144
|
+
return []
|
|
145
|
+
for line in raw_lines:
|
|
146
|
+
line = line.strip()
|
|
147
|
+
if not line:
|
|
148
|
+
continue
|
|
149
|
+
try:
|
|
150
|
+
parsed = json.loads(line)
|
|
151
|
+
except json.JSONDecodeError:
|
|
152
|
+
continue
|
|
153
|
+
if isinstance(parsed, dict) and parsed.get("donor"):
|
|
154
|
+
entries.append(parsed)
|
|
155
|
+
return entries
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def donor_scores(
|
|
159
|
+
scenario_family: str,
|
|
160
|
+
prompt_shape: str = "",
|
|
161
|
+
path: Path | None = None,
|
|
162
|
+
) -> dict[str, int]:
|
|
163
|
+
"""Learned preference per donor, as `relative_path -> score`.
|
|
164
|
+
|
|
165
|
+
Positive means the donor has produced output for this kind of request
|
|
166
|
+
before; negative means it has been rejected. An exact prompt-shape match
|
|
167
|
+
counts double, because it is stronger evidence than family alone.
|
|
168
|
+
"""
|
|
169
|
+
scores: dict[str, int] = defaultdict(int)
|
|
170
|
+
for entry in load_entries(path):
|
|
171
|
+
if str(entry.get("scenario_family") or "") != scenario_family:
|
|
172
|
+
continue
|
|
173
|
+
multiplier = 2 if prompt_shape and entry.get("prompt_shape") == prompt_shape else 1
|
|
174
|
+
|
|
175
|
+
donor = str(entry.get("donor") or "")
|
|
176
|
+
weight = _SUCCESS_WEIGHT.get(str(entry.get("outcome") or ""), 0)
|
|
177
|
+
if donor and weight:
|
|
178
|
+
scores[donor] += weight * multiplier
|
|
179
|
+
|
|
180
|
+
for rejected in entry.get("rejected_donors") or []:
|
|
181
|
+
name = str(rejected)
|
|
182
|
+
if name:
|
|
183
|
+
scores[name] -= multiplier
|
|
184
|
+
return dict(scores)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def summary(path: Path | None = None) -> dict[str, object]:
|
|
188
|
+
"""Human-facing view of what the skill has learned so far."""
|
|
189
|
+
entries = load_entries(path)
|
|
190
|
+
by_outcome: dict[str, int] = defaultdict(int)
|
|
191
|
+
by_family: dict[str, int] = defaultdict(int)
|
|
192
|
+
proven: dict[str, int] = defaultdict(int)
|
|
193
|
+
for entry in entries:
|
|
194
|
+
outcome = str(entry.get("outcome") or "")
|
|
195
|
+
by_outcome[outcome] += 1
|
|
196
|
+
by_family[str(entry.get("scenario_family") or "unknown")] += 1
|
|
197
|
+
if outcome in _SUCCESS_WEIGHT:
|
|
198
|
+
proven[str(entry.get("donor") or "")] += _SUCCESS_WEIGHT[outcome]
|
|
199
|
+
return {
|
|
200
|
+
"ledger_path": str(path or ledger_path()),
|
|
201
|
+
"enabled": ledger_enabled(),
|
|
202
|
+
"entry_count": len(entries),
|
|
203
|
+
"outcomes": dict(by_outcome),
|
|
204
|
+
"scenario_families": dict(by_family),
|
|
205
|
+
"proven_donors": sorted(
|
|
206
|
+
({"donor": donor, "score": score} for donor, score in proven.items() if score > 0),
|
|
207
|
+
key=lambda item: (-int(item["score"]), str(item["donor"])),
|
|
208
|
+
)[:10],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main() -> int:
|
|
213
|
+
print(json.dumps(summary(), ensure_ascii=False, indent=2))
|
|
214
|
+
return 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
raise SystemExit(main())
|
package/scripts/vendor/README.md
CHANGED
|
@@ -1,58 +1,65 @@
|
|
|
1
|
-
# Twofish
|
|
1
|
+
# Twofish Engines
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Packet Tracer `.pkt` codec uses Twofish in EAX mode. Two engines live here.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
modern Packet Tracer 9.x encode/decode operations.
|
|
5
|
+
## `twofish_pure.py` — the baseline (always available)
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
A vendored pure-Python Twofish. Twofish is unpatented and uncopyrighted by
|
|
8
|
+
design, so it can be implemented and shipped directly.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
- no compiled artifacts, no environment variables, no per-host setup
|
|
11
|
+
- works on any supported Python (3.10+) and any OS
|
|
12
|
+
- verified at import-time diagnostics against the three official test vectors
|
|
13
|
+
from section B.2 of the Twofish book (128/192/256-bit)
|
|
14
|
+
- bit-identical to the compiled bridge; `tests/test_twofish_pure.py` asserts
|
|
15
|
+
this whenever both are present
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
This is what `pkt_codec` uses unless a compiled bridge is found. Nothing has to
|
|
18
|
+
be installed for `decode`, `inventory`, `edit`, or `generate` to work.
|
|
19
|
+
|
|
20
|
+
Cost: roughly 14 µs per block. A typical 280 KB lab decodes in under a second;
|
|
21
|
+
the largest labs seen so far (~2.8 MB) take about 12 seconds.
|
|
22
|
+
|
|
23
|
+
## `twofish.py` — the optional accelerator
|
|
24
|
+
|
|
25
|
+
A ctypes wrapper around a compiled `_twofish` C library. Roughly 12x faster than
|
|
26
|
+
the pure engine, and worth setting up if you routinely process large labs or
|
|
27
|
+
scan the whole sample corpus. It is never required.
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
The wrapper loads the bridge from, in order:
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- optional: store the bridge elsewhere and point `PKT_TWOFISH_LIBRARY` at that local file
|
|
26
|
-
- optional: store bridges in one or more local directories and set `PKT_TWOFISH_SEARCH_ROOTS`
|
|
31
|
+
1. `PKT_TWOFISH_LIBRARY`
|
|
32
|
+
2. directories listed in `PKT_TWOFISH_SEARCH_ROOTS`
|
|
33
|
+
3. a sibling file in this folder named like `_twofish*.pyd` / `.so` / `.dylib` / `.dll`
|
|
27
34
|
|
|
28
35
|
Example on Windows:
|
|
29
36
|
|
|
30
37
|
```powershell
|
|
31
|
-
$env:PKT_TWOFISH_LIBRARY="
|
|
38
|
+
$env:PKT_TWOFISH_LIBRARY="C:\path\to\_twofish.cp314-win_amd64.pyd"
|
|
32
39
|
```
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
The compiled bridge is ABI-locked to one Python version. The current filename
|
|
42
|
+
contract is `_twofish.cp314-win_amd64.pyd` (macOS `_twofish.cp314-macos*.dylib`,
|
|
43
|
+
Linux `_twofish.cp314-linux*.so`). On any other Python version the accelerator is
|
|
44
|
+
skipped and the pure engine is used instead — this is not an error.
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
$env:PKT_TWOFISH_SEARCH_ROOTS="$env:USERPROFILE\.codex\skills\pkt\scripts\vendor;$env:USERPROFILE\pkt-bridges"
|
|
38
|
-
```
|
|
46
|
+
## Which engine am I using?
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
```bash
|
|
49
|
+
python scripts/runtime_doctor.py
|
|
50
|
+
```
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
- current bridge filename: `_twofish.cp314-win_amd64.pyd`
|
|
44
|
-
- recommended non-Windows naming contract:
|
|
45
|
-
- macOS: `_twofish.cp314-macos*.dylib`
|
|
46
|
-
- Linux: `_twofish.cp314-linux*.so`
|
|
47
|
-
- other Python ABIs are not considered supported by this public setup
|
|
52
|
+
Read `twofish_backend`: `pure_python` or `compiled`.
|
|
48
53
|
|
|
49
54
|
## Security and privacy
|
|
50
55
|
|
|
51
|
-
-
|
|
52
|
-
- do not commit binaries that embed private paths, usernames, or
|
|
53
|
-
- prefer rebuilding
|
|
56
|
+
- this repository ships no prebuilt machine-specific binaries
|
|
57
|
+
- do not commit binaries that embed private paths, usernames, or build metadata
|
|
58
|
+
- prefer rebuilding the bridge reproducibly for your own machine
|
|
54
59
|
|
|
55
|
-
##
|
|
60
|
+
## Licensing
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
`twofish_pure.py` is original work implemented from the published Twofish
|
|
63
|
+
specification and is covered by this repository's licence. `twofish.py` derives
|
|
64
|
+
from the BSD-3-Clause Python Twofish ctypes bindings; see
|
|
65
|
+
`LICENSES/LICENSE.Twofish-BSD-3-Clause.txt`.
|