Shell-session behavioral observation registry layered on core. SPDX: GPL-3.0-or-later (code) / CC-BY-SA-4.0 (attribution-recipes.md). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
135 lines
4.8 KiB
Python
135 lines
4.8 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Registry coverage tests.
|
|
|
|
Asserts that every primitive listed in scratchpad.md's tables has exactly one
|
|
entry in PRIMITIVE_REGISTRY. Drift-detector — failing this test means
|
|
scratchpad.md and the registry have diverged.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from decnet_behave_shell.spec import PRIMITIVE_REGISTRY, ValueKind
|
|
|
|
# Primitive paths expected by scratchpad.md (hand-extracted; v0.1).
|
|
EXPECTED_PRIMITIVES = {
|
|
# motor.*
|
|
"motor.keystroke_cadence",
|
|
"motor.motor_stability",
|
|
"motor.error_correction",
|
|
"motor.command_chunking",
|
|
"motor.paste_burst_rate",
|
|
"motor.input_modality",
|
|
"motor.shell_mastery.tab_completion",
|
|
"motor.shell_mastery.shortcut_usage",
|
|
"motor.shell_mastery.pipe_chaining_depth",
|
|
# cognitive.*
|
|
"cognitive.cognitive_load",
|
|
"cognitive.exploration_style",
|
|
"cognitive.planning_depth",
|
|
"cognitive.tool_vocabulary",
|
|
"cognitive.inter_command_latency_class",
|
|
"cognitive.inter_command_consistency",
|
|
"cognitive.command_branch_diversity",
|
|
"cognitive.feedback_loop_engagement",
|
|
"cognitive.error_resilience.retry_tactic",
|
|
"cognitive.error_resilience.frustration_typing",
|
|
"cognitive.error_resilience.fallback_to_man",
|
|
# temporal.*
|
|
"temporal.session_timing",
|
|
"temporal.session_duration",
|
|
"temporal.escalation_pattern",
|
|
"temporal.persistence",
|
|
"temporal.lifecycle_markers.landing_ritual",
|
|
"temporal.lifecycle_markers.exit_behavior",
|
|
"temporal.lifecycle_markers.idle_periodicity",
|
|
# operational.*
|
|
"operational.opsec_discipline",
|
|
"operational.cleanup_behavior",
|
|
"operational.objective",
|
|
"operational.multi_actor_indicators",
|
|
# environmental.*
|
|
"environmental.keyboard_layout",
|
|
"environmental.locale",
|
|
"environmental.numpad_usage",
|
|
"environmental.terminal_multiplexer",
|
|
"environmental.shell_type",
|
|
# cultural.*
|
|
"cultural.meal_break_gaps",
|
|
"cultural.periodic_micro_pauses",
|
|
"cultural.dst_behavior",
|
|
"cultural.weekend_cadence",
|
|
"cultural.holiday_gaps",
|
|
# emotional_valence.*
|
|
"emotional_valence.valence",
|
|
"emotional_valence.arousal",
|
|
"emotional_valence.stress_response",
|
|
"emotional_valence.frustration_venting",
|
|
# toolchain.tls.*
|
|
"toolchain.tls.ja3_client",
|
|
"toolchain.tls.ja3s_server",
|
|
"toolchain.tls.ja4_client",
|
|
"toolchain.tls.ja4s_server",
|
|
"toolchain.tls.jarm_server",
|
|
"toolchain.tls.tls_cert_simhash",
|
|
# toolchain.transport.*
|
|
"toolchain.transport.tcp_stack",
|
|
"toolchain.transport.h2_akamai_fingerprint",
|
|
"toolchain.transport.quic_client",
|
|
# toolchain.ssh.*
|
|
"toolchain.ssh.hassh_client",
|
|
"toolchain.ssh.hassh_server",
|
|
"toolchain.ssh.ssh_client_banner",
|
|
"toolchain.ssh.kex_algorithm_order",
|
|
# toolchain.http.*
|
|
"toolchain.http.user_agent_tool_class",
|
|
"toolchain.http.header_order_fingerprint",
|
|
"toolchain.http.body_oddities",
|
|
# toolchain.c2.*
|
|
"toolchain.c2.beacon_family",
|
|
"toolchain.c2.beacon_interval_ms",
|
|
"toolchain.c2.beacon_jitter_cv",
|
|
"toolchain.c2.sleep_skew",
|
|
"toolchain.c2.c2_callback_endpoint",
|
|
"toolchain.c2.attack_software_id",
|
|
# toolchain.protocol_abuse.*
|
|
"toolchain.protocol_abuse.dns_exfil_tool",
|
|
"toolchain.protocol_abuse.smb_dialect",
|
|
"toolchain.protocol_abuse.kerberos_etype_offer",
|
|
"toolchain.protocol_abuse.ldap_bind_pattern",
|
|
"toolchain.protocol_abuse.responder_signature",
|
|
"toolchain.protocol_abuse.mitm6_signature",
|
|
# toolchain.payload.*
|
|
"toolchain.payload.payload_simhash",
|
|
"toolchain.payload.payload_entropy_class",
|
|
"toolchain.payload.loader_family",
|
|
}
|
|
|
|
|
|
def test_registry_covers_expected_primitives_exactly():
|
|
registry_keys = set(PRIMITIVE_REGISTRY.keys())
|
|
missing = EXPECTED_PRIMITIVES - registry_keys
|
|
extra = registry_keys - EXPECTED_PRIMITIVES
|
|
assert not missing, f"registry missing: {sorted(missing)}"
|
|
assert not extra, f"registry has unexpected entries: {sorted(extra)}"
|
|
|
|
|
|
def test_every_primitive_has_a_valid_spec():
|
|
for primitive, spec in PRIMITIVE_REGISTRY.items():
|
|
if spec.kind is ValueKind.CATEGORICAL:
|
|
assert spec.allowed, f"{primitive}: categorical must define `allowed`"
|
|
assert all(isinstance(v, str) for v in spec.allowed)
|
|
elif spec.kind is ValueKind.ARRAY:
|
|
assert spec.array_of is not None, f"{primitive}: array must define `array_of`"
|
|
assert spec.array_of is not ValueKind.ARRAY, (
|
|
f"{primitive}: nested arrays not supported in v0.1"
|
|
)
|
|
|
|
|
|
def test_primitive_paths_are_dotted_lowercase():
|
|
pattern = re.compile(r"^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$")
|
|
for primitive in PRIMITIVE_REGISTRY:
|
|
assert pattern.match(primitive), f"malformed primitive path: {primitive!r}"
|