feat: add knowledge-driven joint design studio
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Mechanical Transmission Knowledge Base
|
||||
|
||||
This directory is the detached, read-only knowledge source for future
|
||||
knowledge-assisted generation. It does not participate in the current CLI,
|
||||
solver, CAD, validation, or joint-module execution paths.
|
||||
|
||||
## Safety boundary
|
||||
|
||||
- `knowledge_enabled` is conceptually `false` until an explicit integration
|
||||
layer is added later.
|
||||
- Existing code under `src/` must not import this directory.
|
||||
- Knowledge entries describe facts and bind stable capability IDs to existing
|
||||
implementations; they do not execute Python code.
|
||||
- A mechanism is not considered supported merely because it has a topology
|
||||
entry. Its `maturity` and capability bindings define how far it can run.
|
||||
- Generated experiments must eventually use `output/experiments/`, never the
|
||||
existing `output/runs/` production path.
|
||||
|
||||
## Layers
|
||||
|
||||
```text
|
||||
schemas/ machine-readable contracts for every entry kind
|
||||
ontology/ common vocabulary: ports, relations, units, maturity
|
||||
components/ reusable parts and virtual references
|
||||
relations/ local physical and kinematic connections
|
||||
mechanisms/ executable mechanism units/topologies
|
||||
compositions/ legal ways to connect mechanism units
|
||||
capabilities/ bindings to solvers, layouts, CAD, and validators
|
||||
evidence/ regression baselines and validation provenance
|
||||
tools/ read-only consistency checks
|
||||
```
|
||||
|
||||
The initial catalog only records capabilities already present in the project:
|
||||
|
||||
- `simple_2k_h`
|
||||
- `simple_2k_h_cascade`
|
||||
- `ferguson_wolfrom`
|
||||
- the six currently registered topology relations
|
||||
|
||||
No new transmission structure is claimed to be executable in this first
|
||||
version.
|
||||
|
||||
## Validate
|
||||
|
||||
From `backend/`:
|
||||
|
||||
```bash
|
||||
python3 knowledge/tools/validate_knowledge.py
|
||||
```
|
||||
|
||||
The validator uses the Python standard library. It checks entry identity,
|
||||
cross-references, mechanism graph integrity, capability source locations, and
|
||||
the hashes of the three legacy topology templates.
|
||||
@@ -0,0 +1,187 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.registry.legacy",
|
||||
"version": "0.1.0",
|
||||
"entries": [
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.parameter_solver.simple_2kh",
|
||||
"version": "1.0.0",
|
||||
"kind": "parameter_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/parameter_solver.py", "symbol": "solve_parameters"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h"],
|
||||
"limitations": ["Dispatch is currently selected by legacy topology_family."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.parameter_solver.simple_2kh_cascade",
|
||||
"version": "1.0.0",
|
||||
"kind": "parameter_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/parameter_solver.py", "symbol": "solve_parameters"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h_cascade", "composition.coaxial_serial.two_simple_2kh"],
|
||||
"limitations": ["Exactly two stages are supported."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.parameter_solver.ferguson_wolfrom",
|
||||
"version": "1.0.0",
|
||||
"kind": "parameter_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/parameter_solver.py", "symbol": "solve_parameters"},
|
||||
"supports": ["mechanism.planetary.ferguson_wolfrom"],
|
||||
"limitations": ["Required gear tooth counts are supplied explicitly by the requirement."],
|
||||
"maturity": "parameter_solved"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.kinematics.graph",
|
||||
"version": "1.0.0",
|
||||
"kind": "kinematic_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/kinematics.py", "symbol": "solve_instance"},
|
||||
"supports": ["relation.mesh.external_cylindrical", "relation.mesh.internal_cylindrical", "relation.joint.fixed", "relation.joint.rigid"],
|
||||
"limitations": ["Only registered legacy relation equations are executable."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.layout.simple_2kh",
|
||||
"version": "1.0.0",
|
||||
"kind": "layout_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/placement/simple_2k_h.py", "symbol": "solve_simple_2kh_placements"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.layout.simple_2kh_cascade",
|
||||
"version": "1.0.0",
|
||||
"kind": "layout_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/placement/simple_2k_h_cascade.py", "symbol": "solve_cascade_placements"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h_cascade", "composition.coaxial_serial.two_simple_2kh"],
|
||||
"limitations": ["Exactly two stages are supported."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.layout.ferguson_wolfrom",
|
||||
"version": "1.0.0",
|
||||
"kind": "layout_solver",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/placement/ferguson_wolfrom.py", "symbol": "solve_ferguson_wolfrom_placements"},
|
||||
"supports": ["mechanism.planetary.ferguson_wolfrom"],
|
||||
"limitations": [],
|
||||
"maturity": "geometry_validated"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.cad.simplecad_parts",
|
||||
"version": "1.0.0",
|
||||
"kind": "component_cad_generator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/cad/simplecad_parts.py", "symbol": null},
|
||||
"supports": ["component.gear.cylindrical.external", "component.gear.cylindrical.internal", "component.carrier.planetary"],
|
||||
"limitations": ["Component generation is orchestrated by family-specific assembly generators."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.cad.simple_2kh",
|
||||
"version": "1.0.0",
|
||||
"kind": "assembly_cad_generator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/cad/simplecad_generator.py", "symbol": "generate_simplecad"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h"],
|
||||
"limitations": ["Spur and helical tooth forms only."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.cad.simple_2kh_cascade",
|
||||
"version": "1.0.0",
|
||||
"kind": "assembly_cad_generator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/cad/simplecad_cascade_generator.py", "symbol": "generate_simplecad_cascade"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h_cascade", "composition.coaxial_serial.two_simple_2kh"],
|
||||
"limitations": ["Exactly two stages are supported."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.cad.ferguson_wolfrom",
|
||||
"version": "1.0.0",
|
||||
"kind": "assembly_cad_generator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/cad/simplecad_ferguson_wolfrom_generator.py", "symbol": "generate_simplecad_ferguson_wolfrom"},
|
||||
"supports": ["mechanism.planetary.ferguson_wolfrom"],
|
||||
"limitations": ["Kinematic-demo detail only; not supported by the coaxial joint-module adapter."],
|
||||
"maturity": "geometry_validated"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.external_mesh",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/mesh.py", "symbol": "ExternalMeshValidator"},
|
||||
"supports": ["relation.mesh.external_cylindrical"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.internal_mesh",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/mesh.py", "symbol": "InternalMeshValidator"},
|
||||
"supports": ["relation.mesh.internal_cylindrical"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.revolute_joint",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/joints.py", "symbol": "RevoluteJointValidator"},
|
||||
"supports": ["relation.joint.revolute"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.fixed",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/joints.py", "symbol": "FixedValidator"},
|
||||
"supports": ["relation.joint.fixed", "component.reference.housing"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.rigid",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/joints.py", "symbol": "RigidValidator"},
|
||||
"supports": ["relation.joint.rigid"],
|
||||
"limitations": [],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.coaxial",
|
||||
"version": "1.0.0",
|
||||
"kind": "relation_validator",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/relation_validators/joints.py", "symbol": "CoaxialValidator"},
|
||||
"supports": ["relation.alignment.coaxial", "component.reference.main_axis"],
|
||||
"limitations": ["Current topology validator assumes the project main Z axis."],
|
||||
"maturity": "production"
|
||||
},
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "capability.validation.reducer",
|
||||
"version": "1.0.0",
|
||||
"kind": "validation_pipeline",
|
||||
"implementation": {"mode": "legacy_binding", "source_path": "src/validators.py", "symbol": "validate_from_files"},
|
||||
"supports": ["mechanism.planetary.simple_2k_h", "mechanism.planetary.simple_2k_h_cascade", "mechanism.planetary.ferguson_wolfrom"],
|
||||
"limitations": ["Validation coverage remains family dependent."],
|
||||
"maturity": "production"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "catalog.mechanical_transmission",
|
||||
"version": "0.1.0",
|
||||
"mode": "detached_read_only",
|
||||
"integration_state": "not_connected",
|
||||
"default_enabled": false,
|
||||
"scope": "general_mechanical_transmission",
|
||||
"initial_domain": "planetary_reducers",
|
||||
"entry_roots": [
|
||||
"components",
|
||||
"relations",
|
||||
"mechanisms",
|
||||
"compositions",
|
||||
"capabilities",
|
||||
"evidence"
|
||||
],
|
||||
"stable_kernel": {
|
||||
"path": "src",
|
||||
"may_import_knowledge": false,
|
||||
"knowledge_may_reference_kernel": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "component.carrier.planetary",
|
||||
"version": "1.0.0",
|
||||
"kind": "carrier",
|
||||
"virtual": false,
|
||||
"roles": ["carrier", "static_carrier", "output_carrier"],
|
||||
"parameters": {
|
||||
"planet_count": {"type": "integer", "minimum": 1},
|
||||
"planet_orbit_radius_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"plate_thickness_mm": {"type": "number", "exclusive_minimum": 0}
|
||||
},
|
||||
"ports": [
|
||||
{"id": "rotation_axis", "type": "port.rotation.coaxial", "multiplicity": "one"},
|
||||
{"id": "planet_pin_joint", "type": "port.joint.revolute", "multiplicity": "many"},
|
||||
{"id": "stationary_mount", "type": "port.mount.stationary", "multiplicity": "one"}
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.cad.simplecad_parts",
|
||||
"capability.validation.revolute_joint"
|
||||
],
|
||||
"applicability": {
|
||||
"current_mechanisms": [
|
||||
"mechanism.planetary.simple_2k_h",
|
||||
"mechanism.planetary.ferguson_wolfrom"
|
||||
]
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "component.gear.cylindrical.external",
|
||||
"version": "1.0.0",
|
||||
"kind": "gear",
|
||||
"virtual": false,
|
||||
"roles": ["sun", "planet", "parallel_axis_gear"],
|
||||
"parameters": {
|
||||
"teeth": {"type": "integer", "minimum_exclusive": 17},
|
||||
"module_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"pressure_angle_deg": {"type": "number", "exclusive_minimum": 0, "maximum": 45},
|
||||
"face_width_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"tooth_form": {"enum": ["spur", "helical"]},
|
||||
"helix_angle_deg": {"type": "number", "minimum": 0, "maximum": 35},
|
||||
"helix_hand": {"enum": ["left", "right"]}
|
||||
},
|
||||
"ports": [
|
||||
{"id": "rotation_axis", "type": "port.rotation.coaxial", "multiplicity": "one"},
|
||||
{"id": "external_teeth", "type": "port.mesh.external_cylindrical", "multiplicity": "many"},
|
||||
{"id": "journal_rotation", "type": "port.joint.revolute", "multiplicity": "one"},
|
||||
{"id": "radial_support", "type": "port.support.radial", "multiplicity": "many"}
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.cad.simplecad_parts",
|
||||
"capability.validation.external_mesh"
|
||||
],
|
||||
"applicability": {
|
||||
"current_project_tooth_forms": ["spur", "helical"],
|
||||
"note": "Sun and planet are mechanism roles, not separate component families."
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "component.gear.cylindrical.internal",
|
||||
"version": "1.0.0",
|
||||
"kind": "gear",
|
||||
"virtual": false,
|
||||
"roles": ["ring", "internal_gear"],
|
||||
"parameters": {
|
||||
"teeth": {"type": "integer", "minimum_exclusive": 17},
|
||||
"module_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"pressure_angle_deg": {"type": "number", "exclusive_minimum": 0, "maximum": 45},
|
||||
"face_width_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"rim_thickness_mm": {"type": "number", "exclusive_minimum": 0},
|
||||
"tooth_form": {"enum": ["spur", "helical"]},
|
||||
"helix_angle_deg": {"type": "number", "minimum": 0, "maximum": 35},
|
||||
"helix_hand": {"enum": ["left", "right"]}
|
||||
},
|
||||
"ports": [
|
||||
{"id": "rotation_axis", "type": "port.rotation.coaxial", "multiplicity": "one"},
|
||||
{"id": "internal_teeth", "type": "port.mesh.internal_cylindrical", "multiplicity": "many"},
|
||||
{"id": "housing_mount", "type": "port.mount.stationary", "multiplicity": "one"}
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.cad.simplecad_parts",
|
||||
"capability.validation.internal_mesh"
|
||||
],
|
||||
"applicability": {
|
||||
"current_project_tooth_forms": ["spur", "helical"]
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "component.reference.housing",
|
||||
"version": "1.0.0",
|
||||
"kind": "housing",
|
||||
"virtual": true,
|
||||
"roles": ["housing", "ground_reference"],
|
||||
"parameters": {},
|
||||
"ports": [
|
||||
{"id": "stationary_mount", "type": "port.mount.stationary", "multiplicity": "many"},
|
||||
{"id": "main_axis", "type": "port.reference.axis", "multiplicity": "one"}
|
||||
],
|
||||
"capability_refs": ["capability.validation.fixed"],
|
||||
"applicability": {
|
||||
"note": "Topology reference; physical housing details are represented by industrial assembly knowledge."
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "component.reference.main_axis",
|
||||
"version": "1.0.0",
|
||||
"kind": "axis",
|
||||
"virtual": true,
|
||||
"roles": ["main_axis"],
|
||||
"parameters": {},
|
||||
"ports": [
|
||||
{"id": "axis", "type": "port.reference.axis", "multiplicity": "many"}
|
||||
],
|
||||
"capability_refs": ["capability.validation.coaxial"],
|
||||
"applicability": {
|
||||
"default_axis_xyz": [0.0, 0.0, 1.0]
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "composition.coaxial_serial.two_simple_2kh",
|
||||
"version": "1.0.0",
|
||||
"composition_type": "serial_coaxial",
|
||||
"source_port_types": ["port.rotation.coaxial"],
|
||||
"target_port_types": ["port.rotation.coaxial"],
|
||||
"connection_relation": "relation.joint.rigid",
|
||||
"allowed_role_pairs": [
|
||||
{"source": "configured_stage_output", "target": "configured_next_stage_input"}
|
||||
],
|
||||
"constraint_rules": [
|
||||
"exactly_two_simple_2k_h_stages",
|
||||
"axes_coaxial",
|
||||
"no_conflicting_fixed_members",
|
||||
"rigid_endpoint_speeds_equal",
|
||||
"axial_stack_clearance"
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.parameter_solver.simple_2kh_cascade",
|
||||
"capability.layout.simple_2kh_cascade",
|
||||
"capability.cad.simple_2kh_cascade"
|
||||
],
|
||||
"execution_support": {
|
||||
"level": "full",
|
||||
"legacy_family": "simple_2k_h_cascade",
|
||||
"generic_n_stage": false
|
||||
},
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "evidence.legacy_baseline.2026_08_13",
|
||||
"version": "1.0.0",
|
||||
"evidence_type": "source_and_regression_baseline",
|
||||
"subject_refs": [
|
||||
"mechanism.planetary.simple_2k_h",
|
||||
"mechanism.planetary.simple_2k_h_cascade",
|
||||
"mechanism.planetary.ferguson_wolfrom"
|
||||
],
|
||||
"artifacts": [
|
||||
{"path": "src/configurations/simple_2k_h/topology.template.json", "sha256": "a444fb2ea3ef16929f99a2f2a8cbd05442cedd91e7e3e67ec95cf344e3e282c3"},
|
||||
{"path": "src/configurations/simple_2k_h_cascade/topology.template.json", "sha256": "308f60804aa1da2be791ce3e90e3d93c9a5f1f5cacdd0379818e908cbefb9613"},
|
||||
{"path": "src/configurations/ferguson_wolfrom/topology.template.json", "sha256": "82a19e71d776628c79d9d16871d7e7a07f093ecaff84d1114d77f620d3ae074c"},
|
||||
{"path": "tests/test_topology_and_kinematics.py", "purpose": "topology, parameter, and graph-kinematics regression"},
|
||||
{"path": "tests/test_cad_integration.py", "purpose": "real CAD integration regression"},
|
||||
{"path": "tests/test_validation.py", "purpose": "relation and physical validation regression"},
|
||||
{"path": "tests/test_joint_module.py", "purpose": "joint-module and URDF regression"}
|
||||
],
|
||||
"claims": [
|
||||
"The knowledge directory is detached from the existing src execution path.",
|
||||
"The initial knowledge entries describe existing project behavior and do not add a new executable topology.",
|
||||
"Legacy topology hashes must remain stable unless an intentional baseline update is reviewed."
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "mechanism.planetary.ferguson_wolfrom",
|
||||
"version": "1.0.0",
|
||||
"legacy_family": "ferguson_wolfrom",
|
||||
"description": "Ferguson-Wolfrom mechanical-paradox planetary reducer represented by two coupled sun/ring/planet sets.",
|
||||
"members": [
|
||||
{"id": "sun1", "component_ref": "component.gear.cylindrical.external", "role": "sun_fixed_side", "repeat": "single"},
|
||||
{"id": "sun2", "component_ref": "component.gear.cylindrical.external", "role": "sun_output_side", "repeat": "single"},
|
||||
{"id": "planet1", "component_ref": "component.gear.cylindrical.external", "role": "input_planet", "repeat": "planet_count"},
|
||||
{"id": "planet2", "component_ref": "component.gear.cylindrical.external", "role": "output_planet", "repeat": "planet_count"},
|
||||
{"id": "ring1", "component_ref": "component.gear.cylindrical.internal", "role": "ring_fixed_side", "repeat": "single"},
|
||||
{"id": "ring2", "component_ref": "component.gear.cylindrical.internal", "role": "ring_output_side", "repeat": "single"},
|
||||
{"id": "static_carrier", "component_ref": "component.carrier.planetary", "role": "static_carrier", "repeat": "single"},
|
||||
{"id": "output_carrier", "component_ref": "component.carrier.planetary", "role": "output_carrier", "repeat": "single"},
|
||||
{"id": "housing", "component_ref": "component.reference.housing", "role": "housing", "repeat": "single"},
|
||||
{"id": "main_axis", "component_ref": "component.reference.main_axis", "role": "main_axis", "repeat": "single"}
|
||||
],
|
||||
"relations": [
|
||||
{"id": "sun1_planet1", "source": "sun1", "target": "planet1", "relation_ref": "relation.mesh.external_cylindrical", "carrier_ref": "static_carrier"},
|
||||
{"id": "ring1_planet1", "source": "ring1", "target": "planet1", "relation_ref": "relation.mesh.internal_cylindrical", "carrier_ref": "static_carrier"},
|
||||
{"id": "sun2_planet2", "source": "sun2", "target": "planet2", "relation_ref": "relation.mesh.external_cylindrical", "carrier_ref": "output_carrier"},
|
||||
{"id": "ring2_planet2", "source": "ring2", "target": "planet2", "relation_ref": "relation.mesh.internal_cylindrical", "carrier_ref": "output_carrier"},
|
||||
{"id": "planet1_carrier", "source": "planet1", "target": "static_carrier", "relation_ref": "relation.joint.revolute"},
|
||||
{"id": "planet2_carrier", "source": "planet2", "target": "output_carrier", "relation_ref": "relation.joint.revolute"},
|
||||
{"id": "sun_pair", "source": "sun1", "target": "sun2", "relation_ref": "relation.joint.rigid"},
|
||||
{"id": "ring_pair", "source": "ring1", "target": "ring2", "relation_ref": "relation.joint.rigid"},
|
||||
{"id": "sun1_axis", "source": "sun1", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "sun2_axis", "source": "sun2", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "ring1_axis", "source": "ring1", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "ring2_axis", "source": "ring2", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "static_carrier_axis", "source": "static_carrier", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "output_carrier_axis", "source": "output_carrier", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "static_carrier_ground", "source": "static_carrier", "target": "housing", "relation_ref": "relation.joint.fixed"}
|
||||
],
|
||||
"external_ports": [
|
||||
{"id": "input_rotation", "member": "planet1", "type": "port.joint.revolute"},
|
||||
{"id": "output_rotation", "member": "output_carrier", "type": "port.rotation.coaxial"},
|
||||
{"id": "housing_mount", "member": "housing", "type": "port.mount.stationary"}
|
||||
],
|
||||
"boundary_profiles": [
|
||||
{"id": "planet1_in_static_carrier_fixed_output_carrier_out", "input": "planet1", "fixed": ["static_carrier"], "output": "output_carrier", "production_evidence": true}
|
||||
],
|
||||
"constraint_rules": [
|
||||
"paired_suns_rigid",
|
||||
"paired_rings_rigid",
|
||||
"required_tooth_counts_explicit",
|
||||
"minimum_teeth_exclusive_17",
|
||||
"graph_kinematics_residual_within_tolerance"
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.parameter_solver.ferguson_wolfrom",
|
||||
"capability.kinematics.graph",
|
||||
"capability.layout.ferguson_wolfrom",
|
||||
"capability.cad.ferguson_wolfrom",
|
||||
"capability.validation.reducer"
|
||||
],
|
||||
"source_topology": {
|
||||
"path": "src/configurations/ferguson_wolfrom/topology.template.json",
|
||||
"sha256": "82a19e71d776628c79d9d16871d7e7a07f093ecaff84d1114d77f620d3ae074c"
|
||||
},
|
||||
"limitations": [
|
||||
"Current implementation supports kinematic_demo detail level only.",
|
||||
"The eccentric planet-pin input is not compatible with the current coaxial joint-module adapter."
|
||||
],
|
||||
"maturity": "geometry_validated"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "mechanism.planetary.simple_2k_h",
|
||||
"version": "1.0.0",
|
||||
"legacy_family": "simple_2k_h",
|
||||
"description": "Single simple 2K-H planetary cell with interchangeable input, fixed, and output boundary members.",
|
||||
"members": [
|
||||
{"id": "sun", "component_ref": "component.gear.cylindrical.external", "role": "sun", "repeat": "single"},
|
||||
{"id": "planet", "component_ref": "component.gear.cylindrical.external", "role": "planet", "repeat": "planet_count"},
|
||||
{"id": "ring", "component_ref": "component.gear.cylindrical.internal", "role": "ring", "repeat": "single"},
|
||||
{"id": "carrier", "component_ref": "component.carrier.planetary", "role": "carrier", "repeat": "single"},
|
||||
{"id": "housing", "component_ref": "component.reference.housing", "role": "housing", "repeat": "single"},
|
||||
{"id": "main_axis", "component_ref": "component.reference.main_axis", "role": "main_axis", "repeat": "single"}
|
||||
],
|
||||
"relations": [
|
||||
{"id": "sun_planet", "source": "sun", "target": "planet", "relation_ref": "relation.mesh.external_cylindrical", "carrier_ref": "carrier"},
|
||||
{"id": "ring_planet", "source": "ring", "target": "planet", "relation_ref": "relation.mesh.internal_cylindrical", "carrier_ref": "carrier"},
|
||||
{"id": "planet_carrier", "source": "planet", "target": "carrier", "relation_ref": "relation.joint.revolute"},
|
||||
{"id": "sun_axis", "source": "sun", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "ring_axis", "source": "ring", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "carrier_axis", "source": "carrier", "target": "main_axis", "relation_ref": "relation.alignment.coaxial"},
|
||||
{"id": "fixed_member_housing", "source": "ring", "target": "housing", "relation_ref": "relation.joint.fixed", "boundary_substitutable": true}
|
||||
],
|
||||
"external_ports": [
|
||||
{"id": "sun_rotation", "member": "sun", "type": "port.rotation.coaxial"},
|
||||
{"id": "ring_rotation", "member": "ring", "type": "port.rotation.coaxial"},
|
||||
{"id": "carrier_rotation", "member": "carrier", "type": "port.rotation.coaxial"},
|
||||
{"id": "housing_mount", "member": "housing", "type": "port.mount.stationary"}
|
||||
],
|
||||
"boundary_profiles": [
|
||||
{"id": "sun_in_ring_fixed_carrier_out", "input": "sun", "fixed": ["ring"], "output": "carrier", "production_evidence": true},
|
||||
{"id": "ring_in_sun_fixed_carrier_out", "input": "ring", "fixed": ["sun"], "output": "carrier", "production_evidence": true},
|
||||
{"id": "carrier_in_ring_fixed_sun_out", "input": "carrier", "fixed": ["ring"], "output": "sun", "production_evidence": true},
|
||||
{"id": "sun_in_carrier_fixed_ring_out", "input": "sun", "fixed": ["carrier"], "output": "ring", "production_evidence": true}
|
||||
],
|
||||
"constraint_rules": [
|
||||
"z_ring = z_sun + 2*z_planet",
|
||||
"(z_sun + z_ring) % planet_count = 0",
|
||||
"common_module",
|
||||
"common_pressure_angle",
|
||||
"planet_neighbor_clearance",
|
||||
"maximum_outer_diameter"
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.parameter_solver.simple_2kh",
|
||||
"capability.kinematics.graph",
|
||||
"capability.layout.simple_2kh",
|
||||
"capability.cad.simple_2kh",
|
||||
"capability.validation.reducer"
|
||||
],
|
||||
"source_topology": {
|
||||
"path": "src/configurations/simple_2k_h/topology.template.json",
|
||||
"sha256": "a444fb2ea3ef16929f99a2f2a8cbd05442cedd91e7e3e67ec95cf344e3e282c3"
|
||||
},
|
||||
"limitations": [
|
||||
"Current CAD supports spur and helical tooth forms.",
|
||||
"Industrial-core generation currently restricts boundary variants."
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "mechanism.planetary.simple_2k_h_cascade",
|
||||
"version": "1.0.0",
|
||||
"legacy_family": "simple_2k_h_cascade",
|
||||
"description": "Exactly two simple 2K-H stages connected by a rigid stage-output to next-stage-input coupling.",
|
||||
"members": [
|
||||
{"id": "stage_1", "mechanism_ref": "mechanism.planetary.simple_2k_h", "role": "first_stage", "repeat": "single"},
|
||||
{"id": "stage_2", "mechanism_ref": "mechanism.planetary.simple_2k_h", "role": "second_stage", "repeat": "single"},
|
||||
{"id": "housing", "component_ref": "component.reference.housing", "role": "housing", "repeat": "single"},
|
||||
{"id": "main_axis", "component_ref": "component.reference.main_axis", "role": "main_axis", "repeat": "single"}
|
||||
],
|
||||
"relations": [
|
||||
{"id": "stage_output_to_next_input", "source": "stage_1", "target": "stage_2", "relation_ref": "relation.joint.rigid", "composition_ref": "composition.coaxial_serial.two_simple_2kh"}
|
||||
],
|
||||
"external_ports": [
|
||||
{"id": "input_rotation", "member": "stage_1", "type": "port.rotation.coaxial"},
|
||||
{"id": "output_rotation", "member": "stage_2", "type": "port.rotation.coaxial"},
|
||||
{"id": "housing_mount", "member": "housing", "type": "port.mount.stationary"}
|
||||
],
|
||||
"boundary_profiles": [
|
||||
{"id": "per_stage_boundaries", "input": "stage_1.configured_input", "fixed": ["stage_1.configured_fixed", "stage_2.configured_fixed"], "output": "stage_2.configured_output", "production_evidence": true}
|
||||
],
|
||||
"constraint_rules": [
|
||||
"exactly_two_stages",
|
||||
"each_stage_is_simple_2k_h",
|
||||
"stage_output_rigid_to_next_stage_input",
|
||||
"common_main_axis",
|
||||
"axial_stack_clearance"
|
||||
],
|
||||
"capability_refs": [
|
||||
"capability.parameter_solver.simple_2kh_cascade",
|
||||
"capability.kinematics.graph",
|
||||
"capability.layout.simple_2kh_cascade",
|
||||
"capability.cad.simple_2kh_cascade",
|
||||
"capability.validation.reducer"
|
||||
],
|
||||
"source_topology": {
|
||||
"path": "src/configurations/simple_2k_h_cascade/topology.template.json",
|
||||
"sha256": "308f60804aa1da2be791ce3e90e3d93c9a5f1f5cacdd0379818e908cbefb9613"
|
||||
},
|
||||
"limitations": [
|
||||
"This is a fixed two-stage implementation, not a generic N-stage composer.",
|
||||
"Only the existing rigid output-to-input composition is production supported."
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "ontology.maturity_levels",
|
||||
"version": "1.0.0",
|
||||
"ordered_levels": [
|
||||
"declared",
|
||||
"topology_validated",
|
||||
"kinematically_validated",
|
||||
"parameter_solved",
|
||||
"cad_generated",
|
||||
"geometry_validated",
|
||||
"production"
|
||||
],
|
||||
"rule": "An entry may only claim the highest stage supported by recorded evidence and executable capability bindings."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "ontology.port_types",
|
||||
"version": "1.0.0",
|
||||
"entries": [
|
||||
{"id": "port.rotation.coaxial", "domain": "rotation", "axis_relation": "coaxial"},
|
||||
{"id": "port.mesh.external_cylindrical", "domain": "gear_mesh", "axis_relation": "parallel"},
|
||||
{"id": "port.mesh.internal_cylindrical", "domain": "gear_mesh", "axis_relation": "parallel"},
|
||||
{"id": "port.joint.revolute", "domain": "joint", "axis_relation": "coaxial_local"},
|
||||
{"id": "port.mount.stationary", "domain": "mount", "axis_relation": "not_applicable"},
|
||||
{"id": "port.reference.axis", "domain": "reference", "axis_relation": "self"},
|
||||
{"id": "port.support.radial", "domain": "support", "axis_relation": "coaxial"},
|
||||
{"id": "port.support.axial", "domain": "support", "axis_relation": "coaxial"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "ontology.relation_types",
|
||||
"version": "1.0.0",
|
||||
"entries": [
|
||||
{"type": "external_mesh", "knowledge_id": "relation.mesh.external_cylindrical"},
|
||||
{"type": "internal_mesh", "knowledge_id": "relation.mesh.internal_cylindrical"},
|
||||
{"type": "revolute_joint", "knowledge_id": "relation.joint.revolute"},
|
||||
{"type": "coaxial", "knowledge_id": "relation.alignment.coaxial"},
|
||||
{"type": "fixed", "knowledge_id": "relation.joint.fixed"},
|
||||
{"type": "rigid", "knowledge_id": "relation.joint.rigid"}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "ontology.units",
|
||||
"version": "1.0.0",
|
||||
"canonical": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"angular_speed": "rad_per_s",
|
||||
"torque": "N_m",
|
||||
"force": "N",
|
||||
"mass": "kg"
|
||||
},
|
||||
"coordinate_convention": {
|
||||
"handedness": "right_handed",
|
||||
"default_rotation_axis": [0.0, 0.0, 1.0]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.alignment.coaxial",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "coaxial",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.rotation.coaxial",
|
||||
"target_port": "port.reference.axis",
|
||||
"axis_relation": "coincident"
|
||||
},
|
||||
"constraint_rules": ["axes_parallel", "axis_distance_within_tolerance"],
|
||||
"kinematic_effect": null,
|
||||
"capability_refs": ["capability.validation.coaxial"],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.joint.fixed",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "fixed",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.rotation.coaxial",
|
||||
"target_port": "port.mount.stationary",
|
||||
"axis_relation": "mechanism_defined"
|
||||
},
|
||||
"constraint_rules": ["one_endpoint_is_grounded", "fixed_speed_is_zero"],
|
||||
"kinematic_effect": {"equation": "omega_source = omega_target = 0"},
|
||||
"capability_refs": [
|
||||
"capability.kinematics.graph",
|
||||
"capability.validation.fixed"
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.joint.revolute",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "revolute_joint",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.joint.revolute",
|
||||
"target_port": "port.joint.revolute",
|
||||
"axis_relation": "coaxial_local"
|
||||
},
|
||||
"constraint_rules": ["joint_axis_defined", "carrier_pin_orbit_matches_layout"],
|
||||
"kinematic_effect": null,
|
||||
"capability_refs": ["capability.validation.revolute_joint"],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.joint.rigid",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "rigid",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.rotation.coaxial",
|
||||
"target_port": "port.rotation.coaxial",
|
||||
"axis_relation": "coaxial"
|
||||
},
|
||||
"constraint_rules": ["axes_coaxial", "angular_speeds_equal", "torque_capacity_compatible"],
|
||||
"kinematic_effect": {"equation": "omega_source = omega_target"},
|
||||
"capability_refs": [
|
||||
"capability.kinematics.graph",
|
||||
"capability.validation.rigid"
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.mesh.external_cylindrical",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "external_mesh",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.mesh.external_cylindrical",
|
||||
"target_port": "port.mesh.external_cylindrical",
|
||||
"axis_relation": "parallel"
|
||||
},
|
||||
"constraint_rules": [
|
||||
"equal_module",
|
||||
"equal_pressure_angle",
|
||||
"equal_tooth_form",
|
||||
"spur_or_opposite_helical_hand",
|
||||
"center_distance_equals_pitch_radius_sum"
|
||||
],
|
||||
"kinematic_effect": {
|
||||
"equation": "(omega_source-omega_carrier)*z_source + (omega_target-omega_carrier)*z_target = 0"
|
||||
},
|
||||
"capability_refs": [
|
||||
"capability.kinematics.graph",
|
||||
"capability.validation.external_mesh"
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"id": "relation.mesh.internal_cylindrical",
|
||||
"version": "1.0.0",
|
||||
"relation_type": "internal_mesh",
|
||||
"endpoint_rules": {
|
||||
"source_port": "port.mesh.internal_cylindrical",
|
||||
"target_port": "port.mesh.external_cylindrical",
|
||||
"axis_relation": "parallel"
|
||||
},
|
||||
"constraint_rules": [
|
||||
"equal_module",
|
||||
"equal_pressure_angle",
|
||||
"equal_tooth_form",
|
||||
"spur_or_same_helical_hand",
|
||||
"center_distance_equals_pitch_radius_difference"
|
||||
],
|
||||
"kinematic_effect": {
|
||||
"equation": "(omega_source-omega_carrier)*z_source - (omega_target-omega_carrier)*z_target = 0"
|
||||
},
|
||||
"capability_refs": [
|
||||
"capability.kinematics.graph",
|
||||
"capability.validation.internal_mesh"
|
||||
],
|
||||
"maturity": "production"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/capability.schema.json",
|
||||
"title": "Executable capability binding",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "kind", "implementation", "supports", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^capability\\."},
|
||||
"version": {"type": "string"},
|
||||
"kind": {"type": "string"},
|
||||
"implementation": {
|
||||
"type": "object",
|
||||
"required": ["mode", "source_path"],
|
||||
"properties": {
|
||||
"mode": {"enum": ["legacy_binding", "declarative"]},
|
||||
"source_path": {"type": "string"},
|
||||
"symbol": {"type": ["string", "null"]}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"supports": {"type": "array", "items": {"type": "string"}},
|
||||
"limitations": {"type": "array", "items": {"type": "string"}},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/component.schema.json",
|
||||
"title": "Mechanical component knowledge entry",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "kind", "parameters", "ports", "capability_refs", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^component\\."},
|
||||
"version": {"type": "string"},
|
||||
"kind": {"type": "string"},
|
||||
"virtual": {"type": "boolean", "default": false},
|
||||
"roles": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"parameters": {"type": "object"},
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "type"],
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"type": {"type": "string"},
|
||||
"multiplicity": {"type": "string", "enum": ["one", "many"]}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"capability_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"applicability": {"type": "object"},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/composition.schema.json",
|
||||
"title": "Mechanism composition knowledge entry",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "composition_type", "source_port_types", "target_port_types", "connection_relation", "constraint_rules", "capability_refs", "execution_support", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^composition\\."},
|
||||
"version": {"type": "string"},
|
||||
"composition_type": {"type": "string"},
|
||||
"source_port_types": {"type": "array", "items": {"type": "string"}},
|
||||
"target_port_types": {"type": "array", "items": {"type": "string"}},
|
||||
"connection_relation": {"type": "string"},
|
||||
"allowed_role_pairs": {"type": "array", "items": {"type": "object"}},
|
||||
"constraint_rules": {"type": "array", "items": {"type": "string"}},
|
||||
"capability_refs": {"type": "array", "items": {"type": "string"}},
|
||||
"execution_support": {"type": "object"},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/evidence.schema.json",
|
||||
"title": "Knowledge evidence entry",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "evidence_type", "subject_refs", "artifacts", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^evidence\\."},
|
||||
"version": {"type": "string"},
|
||||
"evidence_type": {"type": "string"},
|
||||
"subject_refs": {"type": "array", "items": {"type": "string"}},
|
||||
"artifacts": {"type": "array", "items": {"type": "object"}},
|
||||
"claims": {"type": "array", "items": {"type": "string"}},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/mechanism.schema.json",
|
||||
"title": "Mechanism knowledge entry",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "legacy_family", "members", "relations", "external_ports", "boundary_profiles", "constraint_rules", "capability_refs", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^mechanism\\."},
|
||||
"version": {"type": "string"},
|
||||
"legacy_family": {"type": ["string", "null"]},
|
||||
"description": {"type": "string"},
|
||||
"members": {"type": "array", "items": {"type": "object"}},
|
||||
"relations": {"type": "array", "items": {"type": "object"}},
|
||||
"external_ports": {"type": "array", "items": {"type": "object"}},
|
||||
"boundary_profiles": {"type": "array", "items": {"type": "object"}},
|
||||
"constraint_rules": {"type": "array", "items": {"type": "string"}},
|
||||
"capability_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"source_topology": {"type": "object"},
|
||||
"limitations": {"type": "array", "items": {"type": "string"}},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "kb://schemas/relation.schema.json",
|
||||
"title": "Mechanical relation knowledge entry",
|
||||
"type": "object",
|
||||
"required": ["schema_version", "id", "version", "relation_type", "endpoint_rules", "constraint_rules", "capability_refs", "maturity"],
|
||||
"properties": {
|
||||
"schema_version": {"const": "1.0"},
|
||||
"id": {"type": "string", "pattern": "^relation\\."},
|
||||
"version": {"type": "string"},
|
||||
"relation_type": {"type": "string"},
|
||||
"endpoint_rules": {"type": "object"},
|
||||
"constraint_rules": {"type": "array", "items": {"type": "string"}},
|
||||
"kinematic_effect": {"type": ["object", "null"]},
|
||||
"capability_refs": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"maturity": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only consistency validator for the detached knowledge base."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
KNOWLEDGE_ROOT = Path(__file__).resolve().parents[1]
|
||||
BACKEND_ROOT = KNOWLEDGE_ROOT.parent
|
||||
ENTRY_DIRS = {
|
||||
"component": KNOWLEDGE_ROOT / "components",
|
||||
"relation": KNOWLEDGE_ROOT / "relations",
|
||||
"mechanism": KNOWLEDGE_ROOT / "mechanisms",
|
||||
"composition": KNOWLEDGE_ROOT / "compositions",
|
||||
"evidence": KNOWLEDGE_ROOT / "evidence",
|
||||
}
|
||||
REQUIRED_FIELDS = {
|
||||
"component": {"schema_version", "id", "version", "kind", "parameters", "ports", "capability_refs", "maturity"},
|
||||
"relation": {"schema_version", "id", "version", "relation_type", "endpoint_rules", "constraint_rules", "capability_refs", "maturity"},
|
||||
"mechanism": {"schema_version", "id", "version", "legacy_family", "members", "relations", "external_ports", "boundary_profiles", "constraint_rules", "capability_refs", "maturity"},
|
||||
"composition": {"schema_version", "id", "version", "composition_type", "source_port_types", "target_port_types", "connection_relation", "constraint_rules", "capability_refs", "execution_support", "maturity"},
|
||||
"capability": {"schema_version", "id", "version", "kind", "implementation", "supports", "maturity"},
|
||||
"evidence": {"schema_version", "id", "version", "evidence_type", "subject_refs", "artifacts", "maturity"},
|
||||
}
|
||||
|
||||
|
||||
def load_json(path: Path, errors: list[str]) -> Any | None:
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"invalid_json:{path.relative_to(BACKEND_ROOT)}:{exc}")
|
||||
return None
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def collect_entries(errors: list[str]) -> tuple[dict[str, dict[str, Any]], dict[str, str]]:
|
||||
entries: dict[str, dict[str, Any]] = {}
|
||||
kinds: dict[str, str] = {}
|
||||
|
||||
for kind, root in ENTRY_DIRS.items():
|
||||
for path in sorted(root.rglob("*.json")):
|
||||
data = load_json(path, errors)
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
entry_id = data.get("id")
|
||||
if not isinstance(entry_id, str):
|
||||
errors.append(f"missing_id:{path.relative_to(BACKEND_ROOT)}")
|
||||
continue
|
||||
if entry_id in entries:
|
||||
errors.append(f"duplicate_id:{entry_id}")
|
||||
continue
|
||||
entries[entry_id] = data
|
||||
kinds[entry_id] = kind
|
||||
|
||||
capability_registry_path = KNOWLEDGE_ROOT / "capabilities" / "registry.json"
|
||||
registry = load_json(capability_registry_path, errors)
|
||||
if isinstance(registry, dict):
|
||||
for data in registry.get("entries", []):
|
||||
if not isinstance(data, dict) or not isinstance(data.get("id"), str):
|
||||
errors.append("invalid_capability_registry_entry")
|
||||
continue
|
||||
entry_id = data["id"]
|
||||
if entry_id in entries:
|
||||
errors.append(f"duplicate_id:{entry_id}")
|
||||
continue
|
||||
entries[entry_id] = data
|
||||
kinds[entry_id] = "capability"
|
||||
return entries, kinds
|
||||
|
||||
|
||||
def validate_required_fields(
|
||||
entries: dict[str, dict[str, Any]],
|
||||
kinds: dict[str, str],
|
||||
errors: list[str],
|
||||
) -> None:
|
||||
for entry_id, entry in entries.items():
|
||||
kind = kinds[entry_id]
|
||||
missing = sorted(REQUIRED_FIELDS[kind] - set(entry))
|
||||
if missing:
|
||||
errors.append(f"missing_fields:{entry_id}:{','.join(missing)}")
|
||||
if entry.get("schema_version") != "1.0":
|
||||
errors.append(f"unsupported_schema_version:{entry_id}:{entry.get('schema_version')}")
|
||||
expected_prefix = f"{kind}."
|
||||
if not entry_id.startswith(expected_prefix):
|
||||
errors.append(f"bad_id_prefix:{entry_id}:expected={expected_prefix}")
|
||||
|
||||
|
||||
def referenced_ids(entry: dict[str, Any], kind: str) -> list[str]:
|
||||
refs: list[str] = []
|
||||
refs.extend(entry.get("capability_refs", []))
|
||||
if kind == "mechanism":
|
||||
for member in entry.get("members", []):
|
||||
refs.extend(
|
||||
value
|
||||
for key in ("component_ref", "mechanism_ref")
|
||||
if isinstance((value := member.get(key)), str)
|
||||
)
|
||||
for relation in entry.get("relations", []):
|
||||
refs.extend(
|
||||
value
|
||||
for key in ("relation_ref", "composition_ref")
|
||||
if isinstance((value := relation.get(key)), str)
|
||||
)
|
||||
elif kind == "composition":
|
||||
relation_ref = entry.get("connection_relation")
|
||||
if isinstance(relation_ref, str):
|
||||
refs.append(relation_ref)
|
||||
elif kind == "evidence":
|
||||
refs.extend(entry.get("subject_refs", []))
|
||||
return refs
|
||||
|
||||
|
||||
def validate_references(
|
||||
entries: dict[str, dict[str, Any]],
|
||||
kinds: dict[str, str],
|
||||
port_ids: set[str],
|
||||
errors: list[str],
|
||||
) -> None:
|
||||
for entry_id, entry in entries.items():
|
||||
for reference in referenced_ids(entry, kinds[entry_id]):
|
||||
if reference not in entries:
|
||||
errors.append(f"unknown_reference:{entry_id}:{reference}")
|
||||
|
||||
for port in entry.get("ports", []):
|
||||
port_type = port.get("type")
|
||||
if port_type not in port_ids:
|
||||
errors.append(f"unknown_port_type:{entry_id}:{port_type}")
|
||||
for port in entry.get("external_ports", []):
|
||||
port_type = port.get("type")
|
||||
if port_type not in port_ids:
|
||||
errors.append(f"unknown_port_type:{entry_id}:{port_type}")
|
||||
for key in ("source_port_types", "target_port_types"):
|
||||
for port_type in entry.get(key, []):
|
||||
if port_type not in port_ids:
|
||||
errors.append(f"unknown_port_type:{entry_id}:{port_type}")
|
||||
|
||||
|
||||
def validate_mechanism_graphs(entries: dict[str, dict[str, Any]], errors: list[str]) -> None:
|
||||
for entry_id, mechanism in entries.items():
|
||||
if not entry_id.startswith("mechanism."):
|
||||
continue
|
||||
member_ids = {member.get("id") for member in mechanism.get("members", [])}
|
||||
if None in member_ids or len(member_ids) != len(mechanism.get("members", [])):
|
||||
errors.append(f"invalid_or_duplicate_member:{entry_id}")
|
||||
relation_ids: set[str] = set()
|
||||
for relation in mechanism.get("relations", []):
|
||||
relation_id = relation.get("id")
|
||||
if relation_id in relation_ids or not relation_id:
|
||||
errors.append(f"invalid_or_duplicate_relation:{entry_id}:{relation_id}")
|
||||
relation_ids.add(relation_id)
|
||||
for endpoint in ("source", "target"):
|
||||
if relation.get(endpoint) not in member_ids:
|
||||
errors.append(
|
||||
f"unknown_relation_endpoint:{entry_id}:{relation_id}:{endpoint}={relation.get(endpoint)}"
|
||||
)
|
||||
carrier = relation.get("carrier_ref")
|
||||
if carrier is not None and carrier not in member_ids:
|
||||
errors.append(f"unknown_carrier_ref:{entry_id}:{relation_id}:{carrier}")
|
||||
for port in mechanism.get("external_ports", []):
|
||||
if port.get("member") not in member_ids:
|
||||
errors.append(f"unknown_external_port_member:{entry_id}:{port.get('id')}")
|
||||
for profile in mechanism.get("boundary_profiles", []):
|
||||
boundary_members = [profile.get("input"), profile.get("output"), *profile.get("fixed", [])]
|
||||
for member in boundary_members:
|
||||
root_member = member.split(".", 1)[0] if isinstance(member, str) else member
|
||||
if root_member not in member_ids:
|
||||
errors.append(f"unknown_boundary_member:{entry_id}:{profile.get('id')}:{member}")
|
||||
|
||||
|
||||
def validate_capability_sources(entries: dict[str, dict[str, Any]], errors: list[str]) -> None:
|
||||
for entry_id, entry in entries.items():
|
||||
if not entry_id.startswith("capability."):
|
||||
continue
|
||||
implementation = entry.get("implementation", {})
|
||||
source_path = implementation.get("source_path")
|
||||
if not isinstance(source_path, str):
|
||||
errors.append(f"missing_capability_source:{entry_id}")
|
||||
continue
|
||||
source = BACKEND_ROOT / source_path
|
||||
if not source.is_file():
|
||||
errors.append(f"missing_capability_source:{entry_id}:{source_path}")
|
||||
continue
|
||||
symbol = implementation.get("symbol")
|
||||
if symbol:
|
||||
try:
|
||||
tree = ast.parse(source.read_text(encoding="utf-8"))
|
||||
except SyntaxError as exc:
|
||||
errors.append(f"invalid_capability_source_python:{entry_id}:{exc}")
|
||||
continue
|
||||
top_level_symbols = {
|
||||
node.name
|
||||
for node in tree.body
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
|
||||
}
|
||||
if symbol not in top_level_symbols:
|
||||
errors.append(f"missing_capability_symbol:{entry_id}:{source_path}:{symbol}")
|
||||
|
||||
|
||||
def validate_topology_hashes(entries: dict[str, dict[str, Any]], errors: list[str]) -> None:
|
||||
for entry_id, entry in entries.items():
|
||||
source_topology = entry.get("source_topology")
|
||||
if not source_topology:
|
||||
continue
|
||||
source = BACKEND_ROOT / source_topology["path"]
|
||||
if not source.is_file():
|
||||
errors.append(f"missing_source_topology:{entry_id}:{source_topology['path']}")
|
||||
continue
|
||||
actual = sha256_file(source)
|
||||
if actual != source_topology.get("sha256"):
|
||||
errors.append(
|
||||
f"source_topology_hash_mismatch:{entry_id}:expected={source_topology.get('sha256')}:actual={actual}"
|
||||
)
|
||||
|
||||
|
||||
def validate_detached_boundary(errors: list[str]) -> None:
|
||||
for path in sorted((BACKEND_ROOT / "src").rglob("*.py")):
|
||||
try:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||
except SyntaxError:
|
||||
continue
|
||||
for node in ast.walk(tree):
|
||||
module: str | None = None
|
||||
if isinstance(node, ast.ImportFrom):
|
||||
module = node.module
|
||||
elif isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name == "knowledge" or alias.name.startswith("knowledge."):
|
||||
errors.append(f"stable_kernel_imports_knowledge:{path.relative_to(BACKEND_ROOT)}:{alias.name}")
|
||||
if module == "knowledge" or (module and module.startswith("knowledge.")):
|
||||
errors.append(f"stable_kernel_imports_knowledge:{path.relative_to(BACKEND_ROOT)}:{module}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
errors: list[str] = []
|
||||
for path in sorted(KNOWLEDGE_ROOT.rglob("*.json")):
|
||||
load_json(path, errors)
|
||||
|
||||
catalog = load_json(KNOWLEDGE_ROOT / "catalog.json", errors)
|
||||
if isinstance(catalog, dict):
|
||||
if catalog.get("default_enabled") is not False:
|
||||
errors.append("catalog_default_enabled_must_be_false")
|
||||
if catalog.get("integration_state") != "not_connected":
|
||||
errors.append("catalog_integration_state_must_be_not_connected")
|
||||
|
||||
entries, kinds = collect_entries(errors)
|
||||
validate_required_fields(entries, kinds, errors)
|
||||
|
||||
ports = load_json(KNOWLEDGE_ROOT / "ontology" / "port_types.json", errors)
|
||||
port_ids = {
|
||||
entry["id"]
|
||||
for entry in (ports or {}).get("entries", [])
|
||||
if isinstance(entry, dict) and isinstance(entry.get("id"), str)
|
||||
}
|
||||
validate_references(entries, kinds, port_ids, errors)
|
||||
validate_mechanism_graphs(entries, errors)
|
||||
validate_capability_sources(entries, errors)
|
||||
validate_topology_hashes(entries, errors)
|
||||
validate_detached_boundary(errors)
|
||||
|
||||
if errors:
|
||||
print(f"knowledge validation failed: {len(errors)} error(s)", file=sys.stderr)
|
||||
for error in errors:
|
||||
print(f"- {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
counts: dict[str, int] = {}
|
||||
for kind in kinds.values():
|
||||
counts[kind] = counts.get(kind, 0) + 1
|
||||
count_text = ", ".join(f"{kind}={counts[kind]}" for kind in sorted(counts))
|
||||
print(f"knowledge validation passed: {len(entries)} entries ({count_text})")
|
||||
print("integration_state=not_connected default_enabled=false stable_kernel_imports=0")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user