a-b-street/abstreet · error

access restrictions have changed

Error message

{:?} access restrictions have changed

What it means

When upgrading a ChangeAccessRestrictions command, the recorded old access restrictions (obj.old) are compared against the road's current access_restrictions. A mismatch means the edit's precondition is stale, so upgrade bails instead of applying the change on the wrong baseline.

Solutions

  1. Update the command's `old` field in the edit JSON to the road's current access restrictions.
  2. Remove the stale command and re-apply the restriction change in the app.
  3. Regenerate the map to match the edit's recorded old restrictions.
Defensive patterns

Strategy: validation

Validate before calling

// confirm recorded old access restrictions match the current road
let r = map.find_r_by_osm_id(cmd.id)?;
if map.get_r_edit(r).access_restrictions != cmd.old { /* update or drop cmd */ }

Prevention

When it happens

Trigger: Loading an edit file whose ChangeAccessRestrictions.old differs from road.access_restrictions in the current map — after basemap regeneration, OSM updates, or prior edits changed restrictions.

Common situations: Replaying old edits onto a regenerated map; applying the same edit file twice; sharing edit files between different map versions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13). Data as JSON: /api/errors/2fcb2f1a8ea421ff. Report an issue: GitHub.

Appendix: source

Thrown at map_model/src/edits/compat.rs:257

            };
            if road.lanes_ltr[idx].dir == edits_dir {
                bail!("{:?}'s road already points to dst_i", obj);
            }
            road.lanes_ltr[idx].dir = edits_dir;
        } else if let Some(obj) = cmd.remove("ChangeSpeedLimit") {
            let obj: ChangeSpeedLimit = serde_json::from_value(obj).unwrap();
            let r = map.find_r_by_osm_id(obj.id)?;
            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
            if road.speed_limit != obj.old {
                bail!("{:?} speed limit has changed", obj);
            }
            road.speed_limit = obj.new;
        } else if let Some(obj) = cmd.remove("ChangeAccessRestrictions") {
            let obj: ChangeAccessRestrictions = serde_json::from_value(obj).unwrap();
            let r = map.find_r_by_osm_id(obj.id)?;
            let road = modified.entry(r).or_insert_with(|| map.get_r_edit(r));
            if road.access_restrictions != obj.old {
                bail!("{:?} access restrictions have changed", obj);
            }
            road.access_restrictions = obj.new.clone();
        } else {
            commands.push(orig);
        }
    }

    for (r, new) in modified {
        let old = map.get_r_edit(r);
        commands
            .push(serde_json::to_value(EditCmd::ChangeRoad { r, old, new }.to_perma(map)).unwrap());
    }
    value.as_object_mut().unwrap()["commands"] = Value::Array(commands);
    Ok(())
}

// a3af291b2966c89d63b719e41821705077d063d2 added a map-wide merge_zones field
fn fix_merge_zones(value: &mut Value) {

View on GitHub (pinned to 0964f29315)