BASHAR
Bimodal Autonomous System for Handling and Articulated Robotics
The Spinal Cord for your robot. Drop in a URDF, get stress-tested kinematics, dynamics, and whole-arm collision avoidance — hardware-agnostic, zero boilerplate.
What is BASHAR?
BASHAR is a pure-Python robotics middleware library that separates physics from decisions. You give it a URDF or XACRO file. It handles:
- Product of Exponentials (PoE) forward kinematics — correctly ordered, topology-aware
- Analytical & numerical Jacobians (space and body frames)
- Damped Least Squares IK — stable at singularities and workspace boundaries
- Full Newton-Euler dynamics — mass matrix, Coriolis, gravity, computed-torque control
- Whole-arm collision avoidance — APF repulsion checked at every joint along the chain
- Trajectory generation — cubic, quintic, and trapezoidal time-scaling profiles
- URDF/XACRO compiler — validates topology, decomposes planar/floating joints, detects loops and branches
The design metaphor we use is simple: BASHAR is the Spinal Cord. It does the physics. Whatever makes the high-level decisions — an ML model, a vision script, a keyboard operator — is the Brain. BASHAR doesn't know what a camera is and doesn't need to.
Swapping robots means dropping in a new profile file. The math, the control logic, and your Brain code stay exactly the same.
Quick Start
pip install bashar
from bashar.api import compile_profile, BasharSystem
# Step 1 — compile your URDF once (creates config/profiles/my_robot.json)
compile_profile("my_robot.urdf", "my_robot", verbose=True)
# Step 2 — boot the system
robot = BasharSystem("config/profiles/my_robot.json")
# Step 3 — run your control loop
while True:
robot.update_state(encoder_readings) # sync to hardware
tip = robot.get_tip_position() # [x, y, z] in base frame
# Manual mode: filter velocity commands through the collision guard
safe_joints = robot.manual_step(
desired_dtheta=[0.01, -0.02, 0.0],
obstacles=[[0.3, 0.1, 0.4]] # base-frame coordinates
)
# Autonomous mode: drive to a target while dodging obstacles
new_joints, reached = robot.auto_step(
target_xyz=[0.4, 0.0, 0.3],
obstacles=[[0.2, 0.2, 0.2]]
)
# Computed-torque control: exact motor torques (N·m)
torques = robot.calculate_motor_torques(
current_dtheta, desired_theta, desired_dtheta
)
Requires Python >= 3.10 and NumPy. XACRO support requires ROS 2 to be sourced (uses the xacro command). Plain .urdf files work standalone.
Architecture
BASHAR/
├── src/bashar/
│ ├── api.py ← The only file you import
│ ├── core/
│ │ ├── kinematics.py ← PoE forward/inverse kinematics, Jacobians
│ │ ├── dynamics.py ← RNEA: mass matrix, Coriolis, gravity, torque
│ │ ├── controller.py ← RobotState, CollisionGuard, AutoPilot, CTC
│ │ └── trajectory.py ← Cubic / quintic / trapezoidal time-scaling
│ └── utils/
│ ├── model_compiler.py ← URDF/XACRO → validated JSON profile
│ ├── config_parser.py ← Profile loader and schema validator
│ └── logger.py
├── tests/
│ ├── test_fixes.py ← Bug-fix regression suite (23 tests)
│ ├── test_core.py ← Core math unit tests
│ └── urdfs/ ← Test URDFs for each bug scenario
├── examples/
│ ├── basic_usage.py
│ ├── ros2_node.py
│ └── hardware_loop.py
└── config/profiles/ ← Compiled robot JSON profiles (gitignored)
Module Reference
compile_profile — URDF/XACRO Compiler
from bashar.api import compile_profile
compile_profile(
model_path="my_robot.urdf", # or .xacro
output_name="my_robot",
output_dir="config/profiles", # default
verbose=True # prints ASCII joint tree
)
The compiler walks the URDF, validates it, and writes a clean JSON profile that all downstream components read. What it does specifically:
- Topological ordering — builds the actual parent→child link graph and walks it from
base_framevia BFS, so joints are always emitted in physical traversal order regardless of how they are declared in the file. This is the single fix point that keeps FK, IK, Jacobians, and RNEA consistent. See CHANGELOG for the v1.x bug this addresses. - Branch detection — if two or more active (moving) joints share the same parent link, the compiler raises a
RuntimeErrorat compile time rather than producing a silently broken profile. - Continuous joint limits —
type="continuous"joints without explicit<lower>/<upper>default to(-inf, +inf). Wheels and turrets are no longer frozen at zero. - Virtual DOF decomposition —
planarjoints become 3 virtual 1-DOF joints (Tx, Ty, Rz);floatingjoints become 6 (Tx, Ty, Tz, Rx, Ry, Rz). The downstream PoE math only ever sees one degree of freedom per entry. - Validation — axis normalization, limit sanity (lower < upper, non-negative velocity/effort), and closed-loop detection.
- Inertial pipeline — every
<link>tag's mass, origin, and 6-element inertia tensor is extracted and stored in the profile. The dynamics engine reads these directly to build spatial inertia matrices.
BasharSystem — The Main API
from bashar.api import BasharSystem
robot = BasharSystem("config/profiles/my_robot.json", initial_positions=[0.0, 0.0, 0.0])
State Management
| Method | Description |
|---|---|
update_state(positions) |
Sync internal state from hardware encoders. Validates DOF count, clips to limits. |
get_state() → list[float] |
Current joint positions after clipping. |
get_tip_position() → [x, y, z] |
End-effector position in the robot's base frame. |
Navigation & Control
| Method | Description |
|---|---|
manual_step(dtheta, obstacles) → list[float] |
Filter a velocity command through the whole-arm collision guard. |
auto_step(target_xyz, obstacles) → (list[float], bool) |
Autonomous step toward a target. Returns (positions, reached). |
calculate_motor_torques(dtheta, theta_des, dtheta_des) → list[float] |
Computed-torque control. Returns torques in N·m. |
BasharKinematics — The Math Engine
from bashar.core.kinematics import BasharKinematics
kin = BasharKinematics(profile_dict)
T = kin.forward_kinematics_space([0.1, -0.3, 0.5]) # 4x4 SE(3) transform
Js = kin.jacobian_space(theta) # 6xn Space Jacobian
theta_sol, converged = kin.ik_body(T_target, theta_guess)
| Method | Description |
|---|---|
forward_kinematics_space(theta) |
PoE FK: T = prod(exp([Si]ti)) * M |
jacobian_space(theta) |
Space Jacobian: Js in R^(6xn) |
jacobian_body(Js, T) |
Body Jacobian derived from the space Jacobian |
ik_body(T_sd, theta_guess) |
Newton-Raphson IK with DLS. Returns (theta, converged). |
dls_pinv(J, damping) |
Damped Least Squares pseudo-inverse: J+ = J'(JJ' + λ²I)⁻¹ |
ellipsoid_analysis(J) |
Manipulability metrics (condition number, volume measure) |
All Jacobian inversions use Damped Least Squares by default (λ = 0.05). Near singularities where plain pseudo-inverse diverges, DLS stays bounded by trading a small amount of accuracy for guaranteed numerical stability. The damping factor is a tunable constructor parameter on both CollisionGuard and AutoPilot.
CollisionGuard — Whole-Arm Safety
from bashar.core.controller import CollisionGuard
guard = CollisionGuard(
kin,
influence_radius=0.20, # obstacle influence sphere (m)
repulse_gain=1.5, # APF gain
body_radius=0.08, # physical arm thickness (m)
damping=0.05 # DLS damping for Jacobian inversion
)
safe_state = guard.filter_command(state, d_theta, obstacles)
Frame requirement: All obstacle coordinates must be in the robot's base frame. Frame transformation is the Brain's responsibility.
As of v2.0, the guard checks every active joint's workspace position along the chain, not just the end-effector tip. For each checkpoint k, it uses a Jacobian truncated to only the joints upstream of that point (columns 0 through k) to map the repulsion wrench into joint-space corrections. An obstacle on the elbow produces a real response. Obstacles far from all checkpoints produce no correction.
The body_radius parameter inflates the influence sphere around each checkpoint to account for the physical thickness of the arm.
Trajectory — Motion Planning
from bashar.core.trajectory import Trajectory
# Joint-space trajectory (returns list of N joint configs)
path = Trajectory.joint_trajectory(
start=[0.0, 0.0, 0.0],
end=[1.0, -0.5, 0.8],
Tf=3.0, # total time (s)
N=100, # number of waypoints
method='quintic' # 'cubic' | 'quintic' | 'trapezoidal'
)
# With velocities (for computed-torque control)
path_with_vel = Trajectory.joint_trajectory_velocities(start, end, Tf=3.0, N=100)
# Via-point path through multiple waypoints
path = Trajectory.via_point_trajectory(points, times, N_per_segment=50)
# Task-space screw trajectory (list of N SE(3) transforms)
path = Trajectory.screw_trajectory(T_start, T_end, Tf=3.0, N=100)
| Profile | Boundary conditions | When to use |
|---|---|---|
| Cubic | Zero velocity at start and end | General point-to-point motion |
| Quintic | Zero velocity and acceleration at start and end | Payload handling, torque-sensitive moves |
| Trapezoidal | Bang-coast-bang velocity profile | Maximum throughput, cycle time matters |
Safety Notes for Hardware
A few things worth knowing before connecting to a real robot:
- Verify FK against a known pose before trusting any autonomous motion. Measure a physical configuration, call
update_state, callget_tip_position, and compare against what you measured. - Obstacle coordinates are base-frame only. Everything passed to
manual_step,auto_step, orCollisionGuardmust already be in the robot's base frame. Camera-frame or world-frame coordinates need to be transformed by your Brain code before the call. manual_stepapplies delta-position, not velocity. The docstring says "velocity command" but the implementation addsd_thetadirectly to joint positions with nodtmultiplication. If your loop passes genuine rad/s, pre-multiply by your tick period.- No tunneling protection. The guard checks distance at the start of each tick, not along a swept path. Fast motion at a low control rate can pass through a thin obstacle in one step. Keep a physical e-stop within reach.
- Branching robots fail at compile time. Grippers, dual-arm configurations, and any robot where two active joints share a parent link will now raise a
RuntimeErrorduringcompile_profile. The fix is to split them into separate serial chains and compile each independently.
Running the Tests
git clone https://github.com/ziad-ashraf-abdu/bashar
cd bashar
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
python -m pytest tests/ -v
23 tests, 0 failures — covering all three v2.0 bug fixes and regression checks for FK, IK, Jacobian finite-difference, and RNEA.
Changelog
See CHANGELOG.md for the full bug report, root-cause analysis, and fix descriptions.
v2.0.0 — Major release. Four bugs identified and fixed:
- Critical: Topological joint ordering (FK was silent-wrong for out-of-order URDF declarations)
- Critical:
continuousjoints frozen at 0 (wheels, turrets permanently locked) - Critical: Whole-arm collision guard (only tip was checked; elbow/shoulder obstacles were invisible)
- High: Branching robot detection (now raises
RuntimeErrorat compile time instead of silent garbage output)
Contributing
Issues and pull requests welcome at github.com/ziad-ashraf-abdu/bashar.
The test suite in tests/test_fixes.py uses purpose-built URDFs in tests/urdfs/ that directly reproduce each bug scenario — a useful reference when adding new fixes.
License
MIT — see LICENSE for details.
BASHAR — Changelog
All notable changes to this project are documented here.
Format follows Keep a Changelog.
Versioning follows Semantic Versioning.
[2.0.0] — 2026-08-14
Bug Report & Root-Cause Analysis
A thorough audit of the installed v1.0.0 library — reading every module and running it against purpose-built test URDFs — revealed four bugs in the layer that bridges URDF description to the rigid-body math. The math itself (PoE kinematics, Jacobians, RNEA dynamics, trajectory time-scaling) was verified numerically correct throughout. The bugs were all in the compiler and controller, not in the core algorithms.
Bug #1 — Topological joint ordering ignored
File: src/bashar/utils/model_compiler.py → _extract_kinematics()
File: src/bashar/core/kinematics.py → _build_poe_from_profile()
Root cause:
_extract_kinematics() appended joints to the output list in the exact order they appear in the URDF XML file. _build_poe_from_profile() then consumed that list sequentially, accumulating T_accum = T_accum @ T_local for each joint — treating file order as physical parent→child order.
URDF does not guarantee that joints are declared in tree traversal order. Many authoring tools, XACRO macros, and hand-edited files declare joints in arbitrary order (e.g. end-effector first, base last). When this happens, the PoE product accumulates transforms in the wrong sequence and the resulting screw axes are computed from wrong global positions.
Reproduction:
URDF declares: j_elbow (parent=link1) THEN j_shoulder (parent=base_link)
Physical order: j_shoulder → j_elbow
Expected tip at theta=[0,0]: [0, 0.5, 0.1]
v1.0.0 actual: [0.3, 0.0, 0.2] ← silent wrong answer
Why it was invisible:
The ASCII tree printer (_print_tree) reads parent/child attributes correctly and displayed the right hierarchy. verbose=True showed a tree that looked correct while the math underneath used a different, wrong order. The system gave no indication that anything was wrong.
Fix:
After collecting all joints, build a parent → [joints] adjacency map and perform a BFS walk from base_frame to emit joints in strict parent→child traversal order, regardless of XML declaration order. This is the single fix point: every downstream consumer (kinematics, RobotState, dynamics) reads the list in order, so fixing it once at the source keeps everything consistent.
# model_compiler.py — _extract_kinematics()
parent_to_joints = {}
for j in joints:
parent_to_joints.setdefault(j['parent'], []).append(j)
ordered_joints = []
queue = [base_frame]
while queue:
current_link = queue.pop(0)
for j in parent_to_joints.get(current_link, []):
ordered_joints.append(j)
queue.append(j['child'])
Verified: FK on the out-of-order URDF now returns [0, 0.5, 0.1].
Bug #2a — continuous joints frozen at zero
File: src/bashar/utils/model_compiler.py → _extract_kinematics()
File: src/bashar/core/controller.py → RobotState.clip()
Root cause:
The compiler defaulted limits = {"lower": 0.0, "upper": 0.0, ...} for all joints. For type="continuous" joints (wheels, turrets, continuously-rotating joints) the URDF standard explicitly says <lower>/<upper> are omitted because the joint is unbounded. The compiler's 0.0 defaults were then written to the JSON profile. RobotState.clip() then applied np.clip(position, 0.0, 0.0) on every tick, permanently clamping the joint to exactly 0.
Reproduction:
# Wheel joint with type="continuous", no lower/upper in <limit>
robot.update_state([3.14])
print(robot.get_state()) # v1.0.0: [0.0] ← commanded 3.14 rad, got 0
Fix:
continuous joints now default to (-inf, +inf). Explicit <lower>/<upper> attributes in <limit> are respected if present; otherwise the bounds stay infinite.
_is_continuous = (j_type == 'continuous')
limits = {
"lower": float('-inf') if _is_continuous else 0.0,
"upper": float('+inf') if _is_continuous else 0.0,
...
}
RobotState.clip() now short-circuits the np.clip call for joints whose limits are (-inf, +inf):
def clip(self):
for i in range(self.num_joints):
lo, hi = self.limits[i]['lower'], self.limits[i]['upper']
if lo == float('-inf') and hi == float('inf'):
continue # unbounded joint — never clamp
self.positions[i] = np.clip(self.positions[i], lo, hi)
Verified: Wheel joint accepts 3.14 rad and -100 rad (multiple full turns) with no clamping.
Bug #2b — Branching robots compiled silently to garbage
File: src/bashar/utils/model_compiler.py → _extract_kinematics()
Root cause:
The PoE / Jacobian / RNEA math is defined for open serial chains only — a single path from base to tip. A gripper with two fingers, a dual-arm, or any robot where two active joints share a parent link has no single tip. In v1.0.0, such robots compiled without error. The kinematics engine then chained the finger joints sequentially as if right_finger were physically downstream of left_finger_link, and get_tip_position() returned a meaningless point for a topology that has no unique tip.
Fix:
The compiler now detects branching among active joints and raises RuntimeError at compile time with a descriptive message.
for link, children in parent_to_joints.items():
active_children = [j for j in children if j['type'] in _active_types]
if len(active_children) > 1:
raise RuntimeError(
f"[BASHAR] Branching robot detected at link '{link}': "
f"active joints {[j['name'] for j in active_children]} share the same parent. "
"Split into separate serial chains and compile each independently."
)
Supporting tree topologies correctly in PoE/Jacobian/RNEA would require rearchitecting the math layer, which is out of scope for this release. Converting the failure mode from silent garbage to a loud RuntimeError is the right call for now.
Verified: Branching gripper URDF raises RuntimeError during compile_profile().
Bug #3 — Collision guard only checks end-effector tip
File: src/bashar/core/controller.py → CollisionGuard.filter_command()
Root cause:
filter_command() computed the end-effector tip position, checked it against obstacles, and mapped any repulsion wrench to joint space. The body_radius parameter inflated the influence sphere around that single tip check. An obstacle placed anywhere along the arm — elbow, forearm, shoulder — produced zero response as long as the tip itself was outside the influence sphere.
Reproduction:
# 3-DOF arm: tip at [0,0,1.2], elbow at [0,0,0.9]
# Obstacle placed at elbow: [0, 0.05, 0.9]
guard.filter_command(state, [0,0,0], obstacles=[[0, 0.05, 0.9]])
# v1.0.0: correction = [0.0, 0.0, 0.0] ← no reaction at all
Fix:
Complete rewrite of filter_command(). A new _checkpoint_positions(theta) method computes the world-frame position of each active joint's axis point under the current configuration:
def _checkpoint_positions(self, theta):
positions = []
T_pre = np.eye(4)
for k in range(len(theta)):
S_k = self.kin.S_list[k]
omega_k, v_k = S_k[:3], S_k[3:]
omega_norm_sq = float(omega_k @ omega_k)
if omega_norm_sq > 1e-10: # revolute / continuous
q_at_zero = np.cross(omega_k, v_k) / omega_norm_sq
p_world = T_pre[:3, :3] @ q_at_zero + T_pre[:3, 3]
else: # prismatic
p_world = T_pre[:3, 3]
positions.append(p_world)
T_pre = T_pre @ self.kin.matrix_exp_6(S_k, theta[k])
positions.append((T_pre @ self.kin.M)[:3, 3]) # true tip
return positions
For each checkpoint k, only columns 0 through k of the full Space Jacobian are used. Joints downstream of checkpoint k cannot physically move it, so including them in the inversion is wrong:
Js_full = self.kin.jacobian_space(theta)
for cp_idx, cp_xyz in enumerate(checkpoints):
n_effective = min(cp_idx + 1, state.num_joints)
Js_cp = Js_full[:, :n_effective] # truncated Jacobian
# ... compute V_repulse_cp for this checkpoint ...
dq_cp = self.kin.dls_pinv(Js_cp, self.damping) @ V_repulse_cp
d_theta_correction[:n_effective] += dq_cp
The actual_distance == 0 edge case (obstacle exactly at a checkpoint) now uses a fixed diagonal fallback direction instead of skipping repulsion entirely.
Verified:
- Obstacle at elbow region: non-zero joint correction
- Obstacle far away (5 m): zero correction, no false positive
- Obstacle exactly at tip: non-zero response, no ZeroDivisionError
- Mid-chain obstacle: only proximal joints respond
Regression — Verified Working Math
All three fixes were regression-tested against a clean 3-DOF serial arm. The following were confirmed unaffected:
| Test | Result |
|---|---|
| FK identity at home configuration | pass |
| Space Jacobian shape (6xn) | pass |
| Space Jacobian via finite-difference cross-check | pass |
| IK round-trip (theta → FK → IK converges to 1e-4 m) | pass |
| Mass matrix: symmetric, positive semi-definite | pass |
| Gravity torques: ~0 for collinear (non-moment-arm) configs | pass |
RobotState limit clipping for bounded revolute joints |
pass |
Full suite: 23 tests, 0 failures.
Added
tests/test_fixes.py— regression suite covering all three fixes (18 tests)tests/urdfs/out_of_order.urdf— Bug #1 reproduction URDFtests/urdfs/continuous_wheel.urdf— Bug #2a reproduction URDFtests/urdfs/branching_gripper.urdf— Bug #2b reproduction URDFtests/urdfs/serial_3dof.urdf— clean 3-DOF arm for regression testsCollisionGuard._checkpoint_positions()— whole-arm checkpoint computationCollisionGuard._sanitize_obstacles()— obstacle normalization (was previously inline)
Changed
ModelCompiler._extract_kinematics()— BFS topological joint orderingModelCompiler._extract_kinematics()— continuous joint limits default to(-inf, +inf)CollisionGuard.filter_command()— complete rewrite for whole-arm checkingRobotState.clip()— short-circuits for unbounded jointssetup.py— version bumped to2.0.0- PyPI
Development Statuspromoted to5 - Production/Stable
[1.0.0] — 2026-06-24
Added
- Initial release
- Product of Exponentials forward kinematics (
BasharKinematics) - Space and body Jacobians
- Damped Least Squares IK (
ik_body) - Manipulability ellipsoid analysis
- Recursive Newton-Euler dynamics (
BasharDynamics)- Mass matrix, Coriolis/centripetal, gravity, forward dynamics
- Computed-Torque Controller (
ComputedTorqueController) - Artificial Potential Field collision guard (
CollisionGuard) - Autonomous navigation (
AutoPilot) - Trajectory generation — cubic, quintic, trapezoidal time-scaling (
Trajectory) - URDF/XACRO model compiler with validation (
ModelCompiler) - Full inertial pipeline: mass, COM, and 6-element inertia tensor from URDF to dynamics
BasharSystemhigh-level API- ROS 2 node example, hardware loop example, basic usage example
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bashar-2.0.0.tar.gz.
File metadata
- Download URL: bashar-2.0.0.tar.gz
- Upload date:
- Size: 47.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c33eb62f00b4d60f6dcbb834e5538a08796668889457b426a81fd26c92f513ac
|
|
| MD5 |
cea1a059e4b1b6edd2b0df8fcdc381d5
|
|
| BLAKE2b-256 |
b113451bcc9d4f28b62f4bfb07e5004f0ed25e20a62eeed7fd1ab6b5a01d4c91
|
Provenance
The following attestation bundles were made for bashar-2.0.0.tar.gz:
Publisher:
publish.yml on Ziad-Ashraf-Abdu/BASHAR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bashar-2.0.0.tar.gz -
Subject digest:
c33eb62f00b4d60f6dcbb834e5538a08796668889457b426a81fd26c92f513ac - Sigstore transparency entry: 2464073638
- Sigstore integration time:
-
Permalink:
Ziad-Ashraf-Abdu/BASHAR@bd9f864561107015fa20df07e1b60e1e0e6d7169 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Ziad-Ashraf-Abdu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bd9f864561107015fa20df07e1b60e1e0e6d7169 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bashar-2.0.0-py3-none-any.whl.
File metadata
- Download URL: bashar-2.0.0-py3-none-any.whl
- Upload date:
- Size: 34.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
526318a6db85584ae6ebeefa3e7b12a327649e39513c7b1449bd7032e0e744d1
|
|
| MD5 |
21bea13b0a7c7e6d0e981a02333cc813
|
|
| BLAKE2b-256 |
cb16fe3610eb70cdfaa6b86761c21e8eea41eb99d8db42990571cc77c9c45b0c
|
Provenance
The following attestation bundles were made for bashar-2.0.0-py3-none-any.whl:
Publisher:
publish.yml on Ziad-Ashraf-Abdu/BASHAR
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bashar-2.0.0-py3-none-any.whl -
Subject digest:
526318a6db85584ae6ebeefa3e7b12a327649e39513c7b1449bd7032e0e744d1 - Sigstore transparency entry: 2464073677
- Sigstore integration time:
-
Permalink:
Ziad-Ashraf-Abdu/BASHAR@bd9f864561107015fa20df07e1b60e1e0e6d7169 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Ziad-Ashraf-Abdu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bd9f864561107015fa20df07e1b60e1e0e6d7169 -
Trigger Event:
push
-
Statement type: