Powerful ToS Hurt Companies and Lawyers, Not Just Users

I recently found myself reading Mark Lemley’s paper The Benefit of the Bargain while also helping a friend put together the Terms of Service (ToS) for their new startup. Lemley’s paper essentially argues that modern ToS - documents that are written by services to be one sided and essentially imposed on users as a take-it-or-leave-it offer - should no longer be enforced as contracts because they have lost important fairness elements of what make contracts contracts.

This argument, which I found fairly compelling, mostly focuses on the harm that the modern ToS regime does to users. ToS allow companies to impose a wide range of conditions on users that are beyond the scope of what users would ever reasonably agree to if they were offered a meaningful choice. That is in addition to the unreasonable expectation that everyday people are reading the millions of words worth of contracts they agree to in any given week.

Since I was reading this article while helping to draft ToS for a new service, I was also drawn to something the article did not mention: the ways in which these unilaterally imposed ToS hurt the other entities connected to them. Specifically, the lawyers who draft them and the companies that offer them.[1]

The Drafting Lawyers

I want to start with the most sympathetic characters in this drama: the lawyers hired to write ToS that are heavily skewed in favor of their client. Spare a thought!

It is possible to imagine such a lawyer who is pulled between two competing forces.

On one hand, they know various types of clauses in these agreements are Bad Policy, or at the least unfair to users. Such a lawyer might agree with Lemley that the world would be better without a default set of fair rules, and with a presumption that those rules could only change if the users made a meaningful choice. In their heart of hearts, they might want to draft ToS that they thought more fairly balanced the interests of the company and the company’s users.

On the other hand, that same lawyer is bound by some form of a duty to vigorously represent their client. These unbalanced terms are clearly in the company’s (at least short term) interest. Furthermore, they are essentially industry standard. As a result, this lawyer might worry that it could be a form of malpractice to fail to include the unbalanced terms in the ToS.

When faced with this tension, the lawyer might try to explain to their client that there are long term benefits to maintaining a balanced agreement with users, and therefore to leave out the most one-sided clauses. However, and there is no small irony here, it could be hard to give the client enough information so that they could meaningfully opt out of the tilted ToS arms race.

It is hard to understand the cost of giving up the short, medium, and long-term advantages provided by unbalanced ToS in service of a larger principle of social fairness. There are some startup founders who are interested in the discussion and have the bandwidth to actually process it. There are many more who will never prioritize the discussion enough to meaningfully consent to giving away the advantage.

Thus, the lawyer may face two options: a) vigorously represent their client’s interest and support the bad equilibrium by writing an industry-standard, unbalanced ToS, or 2) get out of the writing ToS business for anyone without the time and inclination to wade through the larger policy arguments.

Those feel like bad options!

The Company

This state of affairs can also harm the company, and not just in the “forcing your customers into unbalanced agreements is bad karma” kind of way.

As Lemley’s paper points out, in the offline world most businesses operate without any sort of formal written contracts at all (“you didn’t sign a contract governing the purchase of an apple from the grocery store.”). The ability to append a ToS to every digital transaction has helped to create an expectation that companies will do exactly that.

In order to do so, the companies need to take the time to write those ToS in the first place. Suddenly, instead of focusing on building and shipping their widgets, companies spend time with lawyers making sure that their ToS include all of the advantages they could possibly claim.

That’s probably a waste of time for just about everyone involved. This is made even more of a waste of time because, when faced with this new obligation, most small companies don’t hire lawyers (which would be one type of waste of resources). Instead, they tap someone without a legal background to semi-arbitrarily assemble their ToS from random corners of the internet (a slightly different type of waste of resources). And I suspect that person will increasingly outsource that task to generative AI (a third type of waste of resources).

These companies don’t really understand what unbalanced terms they are imposing on their users, what advantages they receive from them, and probably would not miss them if they were not there. They are just checking a box they don’t fully understand because it has ended up on the “things startups do” list.

I think all of these behaviors argue in favor of Lemley’s ultimate suggestion that we make a policy choice to move towards a default set of balanced rules and away from unbalanced ToS. Until then, the current system is so broken that it might make you feel bad for the lawyers and companies supposedly benefitting from it.

[1] I’ve been to enough “your paper should actually be my paper” peer reviews that I want to be clear that nothing in this post is intended to suggest that the Lemley paper is incomplete without including these points, or even that they did not occur to him. Word counts, and time, are limited in this life. Something that is interesting to me does not need to be interesting to everyone else.

hero image: a portion of Lawyers in dispute from the Met’s open access collection.

Why Can't You Own an Ebook?

Earlier this summer the Engelberg Center released a new study on ebook ownership. The study was motivated by a superficially simple question: “why can’t you own an ebook?”.

It is very easy to pay money to access an ebook. However, if you want to own that ebook - and own in the traditional sense of ownership, giving you the ability to resell it, or give it away, or simply read it without someone tracking what you are up to - in the vast majority of cases you are out of luck. Instead, your money will buy you a license that gives you access to the ebook on a specific platform under terms that prevent you from doing the ownership things I just mentioned.

This is a fairly well documented problem. In fact, one of my co-authors co-wrote an entire book about it. And another one of my co-authors wrote an entire other book touching on the surveillance aspects of these types of agreements.

What is less well documented is the source of the problem. There are a number of different stakeholders in the world of ebooks, including publishers, authors, ebook platforms, readers, and libraries. If you talk to any one of those stakeholders about this market dynamic, you will often get two things. First, you will get a fairly detailed description of the incentives and constraints that influence how they come to the ebook market. Second, you will get less detailed projections about the incentives and constraints that the other stakeholders bring to the market.

One goal of our investigation was to pull together all of the detailed first person descriptions of incentives and constraints in order to replace the less detailed projections.

You can read the report yourself to see if we succeeded. Instead of rehashing our findings, I wanted to use this post to flag one other thing that emerged during the investigation (I will probably touch on this other thing during the conference the Engelberg Center will be hosting on this topic later on this month - you should come!).

The thing that emerged was this: the key question of why ebooks are licensed instead of sold was a pretty obscure one. When we talked to stakeholders, many of them did not fully understand what we were asking at first (this was not the fault of my third co-author, who did an amazing job with these interviews). They often understood some of the second order ramifications of licensing over ownership, and might have some thoughts about how that decision connected to something else they were worried about. However, they were rarely fluent in the specifics of licensing vs sales itself, and often needed some time to deeply engage with the questions before providing in-depth answers.

I am not mentioning this to belittle any of the stakeholders we talked to, or even to suggest that they should be fluent in the limitations of licensing as compared to sales. Instead, I think it is noteworthy because this lack of familiarity with the concepts might present an opportunity.

One of the ideas guiding our investigation was that all of the stakeholders involved were responding in good faith to the incentives and constraints they faced. No one was twirling their mustaches trying to eliminate ownership as an end in and of itself. Instead, the current (non-optimal, at least in my view) licensing-based market structure was the result of an equilibrium between all of those incentives and constraints.

The fact that the specifics of licensing vs ownership were secondary to so many of those stakeholders may be a sign of hope because it suggests that it is rarely part of anyone’s primary incentives and constraints. They don’t care about licensing vs ownership as an end. It just happens to be that licensing has become part of the way they achieve their primary goals. That could mean that there are other equilibria that balance everyone’s incentives that do not require licensing instead of ownership.

How likely is that? I’m not sure. But I’ll take the hope where I can get it.

AQI Sensor

The air quality in NYC has been . . . not great this summer. This presents and opportunity. Why settle for knowing that the air is not great in the city when you can know how not-great it is in your very own home?

Wow’d by Marty McGuire’s ability to check the air quaility of his apartment on his phone, I decided I would copy him by building a worse implementation of his setup. The features of my version of this setup include:

  • Check the PM2.5 levels in my apartment
  • Check the local AQI
  • Display the PM2.5 and AQI levels with LEDs
  • Display the PM2.5 and AQI levels on a screen
  • Chart the curent and historical PM2.5 levels on a website that I can access with my phone outside the house

In order to make this happen I needed:

The process is pretty straightforward. Every few minutes, the board checks the PM2.5 level. It then changes the LEDs at the top of the FunHouse accordintly, displays the number on the screen, and uploads the data to an Adafruit IO dashboard. At the same time, it also pulls the local AQI levels from the AQI API and updates the LEDs and screen accordingly.

The entire script is available in this repo. In addition to the script you will need:

  • The library files, which are also in the repo (make sure everything in the /lib folder in the repo is in the /lib folder on the board)
  • A secrets.py file to hold your wifi and Adafruit IO credentials. You can learn how to create that here.
  • A seperate keys.py file. This is for the AQI API. I’m sure there’s a way to incorporate this into the secrets.py file, but I couldn’t quite figure out the syntax. In any event, the entire contents of the file is AQI_URL = "the_url_with_your_api_key". You can create your URL by playing around with the AirNow API.

The Code

Here’s a walkthrough of the code.

This first block just imports all of the libraries and sets up the FunHouse object. If you are running into problems with libraries, make sure you have the library in you /lib folder on the device.

import time
import board
import busio
from digitalio import DigitalInOut, Direction, Pull
from adafruit_pm25.i2c import PM25_I2C
from adafruit_funhouse import FunHouse

#for the external API
import adafruit_requests as requests
import keys
import socketpool
import ssl
import wifi

#for the light sensor mapping
from adafruit_simplemath import map_range


reset_pin = None

funhouse = FunHouse(default_bg=None)

The next chunk creates a few more objects, turns on wifi, and sets up variables for the AQI download. It uses the existing funhouse network elements to set up the requests object.

# Create library object, use 'slow' 100KHz frequency!
i2c = board.I2C()
# Connect to a PM2.5 sensor over I2C
pm25 = PM25_I2C(i2c, reset_pin)

print("Found PM2.5 sensor, reading data...")

# Turn on WiFi
funhouse.network.enabled = True
print("wifi on")
# Connect to WiFi
funhouse.network.connect()
print("wifi connected")

#these variables sets up the requests
pool = socketpool.SocketPool(wifi.radio)
requests = funhouse.network._wifi.requests

These are the variables for the various sensor readings.

#IO Stuff
FEED_2_5 = "2pointfive"
TEMP_FEED = "temp"
HUM_FEED = "humidity"
TEMPERATURE_OFFSET = (
    3  # Degrees C to adjust the temperature to compensate for board produced heat
)

These are the RGB color values as variables to make them slightly easier to work with.

#Colors
BLACK = (0,0,0)
GREEN = (0,228,0)
YELLOW = (255, 255, 0)
ORANGE = (255,40,0)
RED = (255,0,0)
PURPLE = (143,63,151)
MAROON = (126,0,35)

This next bit creates the text blocks that will be used to display the readings. The first and third ones are the reading labels. The second and fourth are the actual readings. They are much larger. The last line pushes them to the screen.

#text
funhouse.display.show(None)
pm_label = funhouse.add_text(
    text_scale = 2, text_position = (10,10), text_color = 0x606060
)
pm_value = funhouse.add_text(
    text_scale = 12, text_position = (90,60), text_color = 0x606060
)
aqi_label = funhouse.add_text(
    text_scale = 2, text_position = (10,110), text_color = 0x606060
)
aqi_value = funhouse.add_text(
    text_scale = 12, text_position = (60,180), text_color = 0x606060
)
funhouse.display.show(funhouse.splash)

With all of that set up, the rest of the code is in a While loop that just runs forever.

First, it reads the PM2.5 data from the sensor

try:
    aqdata = pm25.read()
    # print(aqdata)
except RuntimeError:
    print("Unable to read from sensor, retrying...")
    continue

Then it pushes the PM2.5, temp, and humidity data to Adafruit IO. The temp and humidity come from sensors that are built into the FunHouse.

# Push to IO using REST
    try:
        funhouse.push_to_io(FEED_2_5, aqdata["pm25 env"])
        funhouse.push_to_io(TEMP_FEED, funhouse.peripherals.temperature - TEMPERATURE_OFFSET)
        funhouse.push_to_io(HUM_FEED, funhouse.peripherals.relative_humidity)
        print("data pushed")
    except:
        print("error uploading data, moving on")

This section downloads the AQI data from the API. It reads the target URL from the keys.py file, downloads the payload, parses the json, and assigns the AQI value to a new variable. The AQI API website is not the most user friendly UX in the world, but I did end up narrowing my query down to a single monitoring station. AQI will be set to 0 if there is an error, which will serve as a signal that something is wrong.

    # get remote AQI data
    # #https://learn.adafruit.com/adafruit-funhouse/getting-the-date-time   
    target_URL = keys.AQI_URL
    
    try:
        response = requests.get(target_URL, timeout = 10)
        #print(response)
        jsonResponse = response.json()
        print(jsonResponse[0]["AQI"])
        currentAQI = jsonResponse[0]["AQI"]
    except:
        currentAQI = 0
        print('request failed')

The next section sets the text on the display. The labels are just one line each to set the text.

The actual reading display is more complicated. Using the AirNow AQI calculation data sheet, the if/elif statements set the color of the reading to match the alert color.

    #text stuff
    #set the label
    funhouse.set_text("PM 2.5", pm_label)
    #set the color for the pm2.5 reading
    if aqdata["pm25 env"] <= 12.0:
        funhouse.set_text_color(GREEN, pm_value)
    elif 12.0 < aqdata["pm25 env"] <= 35.4:
        funhouse.set_text_color(YELLOW, pm_value)
    elif 35.4 < aqdata["pm25 env"] <= 55.4:
        funhouse.set_text_color(ORANGE, pm_value)   
    elif 55.4 < aqdata["pm25 env"] <= 150.4:
        funhouse.set_text_color(RED, pm_value)
    elif 15.4 < aqdata["pm25 env"] <= 250.4:
        funhouse.set_text_color(PURPLE, pm_value)
    elif 25.4 < aqdata["pm25 env"] <= 500.4:
        funhouse.set_text_color(MAROON, pm_value)
    #set the reading
    funhouse.set_text(aqdata["pm25 env"], pm_value)
    #set the aqi label
    funhouse.set_text("AQI", aqi_label)
    #set the aqi color
    if currentAQI <= 50.0:
        funhouse.set_text_color(GREEN, aqi_value)
    elif 50.0 < currentAQI <= 100:
        funhouse.set_text_color(YELLOW, aqi_value)
    elif 100 < currentAQI <= 150:
        funhouse.set_text_color(ORANGE, aqi_value)   
    elif 150 < currentAQI <= 200:
        funhouse.set_text_color(RED, aqi_value)
    elif 200 < currentAQI <= 300:
        funhouse.set_text_color(PURPLE, aqi_value)
    elif 300 < currentAQI <= 500:
        funhouse.set_text_color(MAROON, aqi_value)
    funhouse.set_text(currentAQI, aqi_value)

After working through the display, things move on to the five LEDs built into the top of the FunHouse. First I create variables and set them all to off.

    #LED Stuff
    #https://www.airnow.gov/sites/default/files/2020-05/aqi-technical-assistance-document-sept2018.pdf
    #set all of the LEDs to black by default
    led_0 = BLACK
    led_1 = BLACK
    led_2 = BLACK
    led_3 = BLACK
    led_4 = BLACK
    print ("2.5 = " + str(aqdata["pm25 env"]))

Then the first two are updated based on the local PM2.5 reading and the last two are updated based on the local AQI.

    #update first two leds depending on the 2.5 reading
    if aqdata["pm25 env"] <= 12.0:
        led_0 = GREEN
        led_1 = GREEN
    elif 12.0 < aqdata["pm25 env"] <= 35.4:
        led_0 = YELLOW
        led_1 = YELLOW
    elif 35.4 < aqdata["pm25 env"] <= 55.4:
        led_0 = ORANGE
        led_1 = ORANGE   
    elif 55.4 < aqdata["pm25 env"] <= 150.4:
        led_0 = RED
        led_1 = RED 
    elif 15.4 < aqdata["pm25 env"] <= 250.4:
        led_0 = PURPLE
        led_1 = PURPLE 
    elif 25.4 < aqdata["pm25 env"] <= 500.4:
        led_0 = MAROON
        led_1 = MAROON

    #update the last two LEDs based on AQI
    if currentAQI <= 50.0:
        led_3 = GREEN
        led_4 = GREEN
    elif 50.0 < currentAQI <= 100:
        led_3 = YELLOW
        led_4 = YELLOW
    elif 100 < currentAQI <= 150:
        led_3 = ORANGE
        led_4 = ORANGE   
    elif 150 < currentAQI <= 200:
        led_3 = RED
        led_4 = RED
    elif 200 < currentAQI <= 300:
        led_3 = PURPLE
        led_4 = PURPLE
    elif 300 < currentAQI <= 500:
        led_3 = MAROON
        led_4 = MAROON

Finally, the new colors are pushed to the LEDs themselves

    #update the LEDs
    funhouse.peripherals.set_dotstars(led_0, led_1, led_2, led_3, led_4)

The LEDs are pretty bright. That’s helpful during the day, but it is a bit much at night. The next bit dims the LEDs based on ambient light. It uses the light sensor built into the FunHouse and maps the readings to a 0-1 scale, which is the scale used to control the brightness of the LEDs.

It is possible control the brightness of the LEDs individually (the syntax is (R,G,B,Brightness)), but in this case I want all of them to be the same level.

    #set LED brightness so they aren't super bright at night
    #map_range works (inputnumber, orig min, orig max, new min, new max)
    #right reading bounds appear to be ~1800-54000, real world is closer to 1800-5000)
    #goal here is to make the lights bright when it is bright and dim when it is dark
    brightness = map_range(funhouse.peripherals.light, 1800, 6000, 0, 1)
    print(brightness)
    funhouse.peripherals.dotstars.brightness = brightness

Finally, everything just waits for 2 minutes before starting over again.

time.sleep(120)
More on the Shifting State of Open Source Hardware

Earlier this week PT over at Adafruit published an interesting post on the evolving relationship between some of the larger open source hardware companies and open source itself. The post catalogs various ways in which three marquee open source hardware companies - Arduino, Sparkfun, and Prusa - have explored non-open strategies for various parts of their offerings.

Generally speaking, I think it is fine for companies to use open source when it is useful and not use open source when it is not useful, as long as they are being upfront about it. Open source is a strategy, and no single strategy applies in every situation. This can become a problem when open source messaging is used to draw users into non-open hardware or systems. Although PT groups Arduino, Sparkfun, and Prusa together for the purposes of the post, I don’t think he intends to suggest that they are all guilty of that, or even that they are engaging with non-openness to the same degree. As of today, I think it is fair to say that the three companies represent a broad range of relationships to open source.

In this context, I also worry about recession from open source. If open source is a strategy, and some high profile open source hardware companies feel like they need to move away from it in various ways, that may be a red flag that there are aspects of the open approach that we as a community should be considering more deeply. To that end, the thing I really want from these companies is for them to talk about their thinking and decisionmaking. Just as many of us have learned from their decisions to use open source, there could be a lot to learn from their decisions to not use open source in some cases.

That is why I find the end of PT’s piece so interesting. It includes an interview-style comment with Josef Prusa discussing his article from March (I had some thoughts about it when it was originally published).

I want to give Prusa credit for raising these concerns, and for talking about them publicly. It is easy to not talk about these decisions publicly, precisely because doing so results in people asking the type of follow up questions that PT asks in his post, I ask in my post, and fellow OSHWA Board Member Thea Flowers raised in her post. I also want to recognize that his answers to PT lead with the admission that Prusa hasn’t finished their homework to follow up to their original article, so everything he says should be understood as a work in progress.

And Yet

All that being said, I find parts of Prusa’s answer to be incredibly frustrating! The core of that frustration is that he raises a bunch of facially valid concerns, and then says that those concerns are pushing them to look at closed-source options, but does not explain how going closed-source addresses the concerns.

This is a style of (potentially) backing away from open hardware that goes back at least as far as Makerbot’s switch over a decade ago. Part of me wants to assume that there is something to it, because it feels like a recurring pattern. The other part of me can’t help but notice that no one seems to be able to actually explain why open source is the driver of the problems they describe. I want to see someone connect the dots.

Prusa’s comment identified a number of problems that they feel like they are dealing with:

  • Chinese government subsidies for competitors
  • Patent applications based on open-source community inventions (unclear if he is limiting these to Prusa inventions, or open source inventions generally)
  • Competitors violating existing licenses
  • The need to protect IP and the inability to do so under current licenses

I’m very willing to believe that these are real challenges. I just don’t understand how becoming less open addresses any of them.

Becoming less open will not reduce the Chinese government (or any other country) from supporting competitors. That support is driven by an interest in creating a strong position in an industry, not by the fact that some of the players in the industry are open.

Becoming less open seems unlikely to impact how much you care about other parties applying for patents in other countries. It seems unlikely that Prusa will decide to spend money filing international patents themselves. If they do find themselves accused of infringement (which, again, would only happen if the country they were selling into was also a country where someone else had obtained a patent), they’re going to have to pay to challenge the patent either way. Furthermore, if someone really wants to understand and copy an innovation built into a desktop 3D printer, not having open documentation seems unlikely to be a major barrier (I’d love to understand why/if I’m wrong about that).

Becoming less open will not change the fact that competitors are violating existing licenses. If competitors are already violating existing licenses, how would adding new layers of licenses change the dynamic? If I’m being honest, this is always the one that makes the least sense to me. No one has ever been able to explain the logic chain of:

  • We can’t enforce existing licenses based on our intellectual property when they are being violated
  • We add new license terms to that same intellectual property that mean that people violate our licenses even more
  • Suddenly we are able to enforce our license terms

If there is a magic trick to enforce more restrictive licenses, why doesn’t it work when someone violates your open license? Isn’t a license violation a license violation?

Asking these questions always makes me feel like I’m missing some key part of the decisionmaking process. That’s why I was excited to see Prusa’s original post, and encouraged that he continued to engage with PT’s questions this week. I really hope that Prusa’s full follow up article begins to shed light on some of these questions. While I’m sure doing so will lead to some criticism, it could also be incredibly helpful for the community to really understand the nature of these challenges.

Feature image: Political Discussion in a Lumber Shanty from the Smithsonian Open Access collection

Keep 3D Printers Unlocked (2023)

Update 10/2024: The Copyright Office granted the exemption request! A bit more info in this post.

Update 8/2023: It looks like no one opposed the renewal request (celebrate the absence of opposition comments listed at the bottom of this page). That should mean that the renewal is approved when this process wraps up in a few months. I’ll post an update when that happens, or if something looks like it might prevent it from happening.

Once again, it is time for the US Copyright Office to revisit its decision to legalize breaking Digital Rights Management (DRM) for specific purposes. The default rule in the United States is that breaking digital locks on copyright-protected works is illegal, so the every-three-year Copyright Office process is designed to create exemptions for groups with good reasons to break those digital locks.

In the past, I have helped to create an exemption for breaking DRM that prevents you from using whatever material you want in your 3D printer (here are all of the previous posts discussing that process). Last time around, I helped tweak some of the language in that exemption in order to make it as clear as possible.

This time around could be very straightforward. I’ve just submitted a request to renew the current exemption without any changes. If no one opposes the request, the Copyright Office could recommend it be renewed without any additional work. That would mean it stays legal to break DRM if you want to use materials of your choice in a 3D printer. However, if someone does oppose the renewal, or the Copyright Office declines to recommend renewal for any reason, it will trigger a more expansive process.

I’m hopeful that the request will end up being uncontroversial and the Copyright Office recommends renewal. While there have been companies opposed to the exemption in the past, that opposition dropped off in the most recent cycle.

The Copyright Office maintains a page for this process where you can track all of the exemption requests. Oppositions to renewal requests are due towards the end of the summer, which is probably the next opportunity for any news. I will post about any updates when they come.