Case Study: A License Inventory Endpoint That Fails Closed on Unknown Obligations
You should freeze license labels before a coding agent writes your release inventory endpoint, because fluent code can still invent obligations. This case study walks through one small service that reports third-party package licenses for a single repository snapshot. You will see the background, the goal, the implementation, the checks, and the lessons in that order. The useful outcome is a failing test for unknown licenses, not a longer handler that merely sounds complete. Background Your release checklist asks whether every direct dependency has a known license obligation before you tag a build. A coding agent can scaffold a JSON endpoint quickly, yet it often guesses when a license string is missing or ambiguous. That guess becomes a product bug if your release gate treats the generated response as an authoritative decision. You need a small written contract that fails closed before any assistant is allowed to touch the handler. Goal You want one inventory endpoint that reads a frozen dependency snapshot and returns a stable JSON envelope. Each package must land in exactly one class, which is permissive, weak copyleft, strong copyleft, or unknown. Unknown must fail the release gate with HTTP 422, and it must never be rewritten as permissive. The handler may be drafted later, while the classification table and the fixtures have to come first. Contract you freeze first You write the rules in a table so a reviewer can argue with the policy instead of arguing with generated branches. The table below is an engineering checklist for this case study, not legal advice and not a complete SPDX catalog. You should replace the sample rows with obligation labels that your own counsel has already approved. | Normalized license | Class | Release gate | |---|---|---| | MIT | permissive | allow | | Apache-2.0 | permissive | allow | | LGPL-2.1-only | weak_copyleft | allow_with_notice | | GPL-3.0-only | strong_copyleft | block | | empty or unrecognized | unknown | fail_closed | You also freeze three response rules in writing before any implementation code is allowed to exist. You keep those rules next to the table so a later draft cannot quietly drop a field. You review that short rules page before you accept any generated file into the working branch. - Confirm that every snapshot package appears once in items, including packages whose license string is empty. - Confirm that an unrecognized license string never receives the permissive class or the allow gate value. - Confirm that blocked is true and the HTTP status is 422 whenever any gate is fail_closed. - Confirm that a generated diff does not delete the pure function tests in order to make the suite pass. Implementation The snippets in this section are a proposed workflow you can copy and run, not a log of a production deployment. You keep the classifier in a pure function so the HTTP layer cannot hide a bad label. You then wrap that function in a tiny standard-library server that a free server option can host without extra framework weight. # inventory_rules.py # Proposed example. Not a measured production module. CLASSES = { "MIT": ("permissive", "allow"), "Apache-2.0": ("permissive", "allow"), "LGPL-2.1-only": ("weak_copyleft", "allow_with_notice"), "GPL-3.0-only": ("strong_copyleft", "block"), } def classify(name, license_name): key = (license_name or "").strip() if key not in CLASSES: return { "name": name, "license": key, "class": "unknown", "gate": "fail_closed", } label, gate = CLASSES[key] return { "name": name, "license": key, "class": label, "gate": gate, } def build_report(packages): items = [classify(item["name"], item.get("license")) for item in packages] blocked = any(item["gate"] in {"block", "fail_closed"} for item in items) return {"blocked": blocked, "items": items} You add a handler that refuses to soften an unknown license into a misleading success status code. The status rule is part of the contract, so you do not leave it to the model's taste. You bind the server to port 8080 only for this exercise, and you change it if that port is already taken. # server.py # Proposed example using only the Python standard library. from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer import json from inventory_rules import build_report SNAPSHOT = [ {"name": "left-pad", "license": "MIT"}, {"name": "widget", "license": ""}, ] class Handler(BaseHTTPRequestHandler): def do_GET(self): if self.path != "/inventory": self.send_error(404) return report = build_report(SNAPSHOT) status = 422 if report["blocked"] else 200 body = json.dumps(report).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def main(): ThreadingHTTPServer(("0.0.0.0", 8080), Handler).serve_forever() if name == "main": main() The failure you should force first You should deliberately run a naive classifier that substitutes MIT for a blank license, because that is the bug agents tend to introduce. The function below is an anti-pattern for this case study, and you should not ship it. You keep that anti-pattern in the repo only long enough to watch the unknown-license fixture fail. def naive_classify(name, license_name): # Anti-pattern: a blank license becomes MIT. Do not ship this. key = (license_name or "MIT").strip() or "MIT" return { "name": name, "license": key, "class": "permissive", "gate": "allow", } You then point the unknown-license fixture at naive_classify and expect an assertion error, not a green check. A green check on this anti-pattern means your test is weaker than the contract you already wrote. You fix the classifier until a blank input stays unknown and the recorded gate stays fail_closed. How you brief the assistant You give the assistant the table, the three response rules, and the failing naive test, and you withhold permission to change assertions. You ask for a classifier that imports nothing beyond the Python standard library, so the free server run stays easy to reproduce. You reject any draft that adds a default license, a silent continue, or a catch that returns HTTP 200. You rerun the three contract tests yourself before you read the rest of the generated diff. Checks you run before you trust a draft You write the assertions against the pure function so a generated handler cannot pass by changing the URL only. These tests describe the contract, and you should treat a failing unknown-license case as a release blocker. Run them locally first, then run the same file on whatever free server you actually have. # test_inventory_rules.py # Proposed checks. Execute them; do not treat this article as a test report. from inventory_rules import build_report def test_known_permissive_stays_allowed(): report = build_report([{"name": "left-pad", "license": "MIT"}]) assert report["blocked"] is False assert report["items"][0]["gate"] == "allow" def test_blank_license_fails_closed(): report = build_report([{"name": "widget", "license": ""}]) assert report["items"][0]["class"] == "unknown" assert report["items"][0]["gate"] == "fail_closed" assert report["blocked"] is True def test_input_package_is_never_dropped(): report = build_report([ {"name": "left-pad", "license": "MIT"}, {"name": "widget", "license": "NOT-A-LICENSE"}, ]) names = [item["name"] for item in report["items"]] assert names == ["left-pad", "widget"] assert report["blocked"] is True python -m pytest -q test_inventory_rules.py python server.py curl -sS -D - http://127.0.0.1:8080/inventory You accept the run only when the blank license returns fail_closed and the HTTP status is 422. You reject the run if the body omits widget or if the status becomes 200 while blocked is true. Those two failures are the exact bugs this small case study is designed to catch early. Where a free assistant and a free server fit Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is the assistant you can point at this frozen table when you want a first draft of the handler. The outreach brief describes MonkeyCode as an open-source project that also offers free model access and a free server option. This walkthrough does not name models, quotas, hardware, or a duration, because those terms can change and were not verified here as stable facts. You should read the project page and confirm the current offer before you plan a release around it. You use free model access only after the fixtures exist, and you paste the failing test output back as the review comment. You use a free server to run pytest and the curl check away from your laptop, so a teammate can see the same 422. If either free option is unavailable, the contract and the tests still stand on any Python 3 environment you control. If you want the same split of labor, start from the MonkeyCode project page rather than from a generated handler. Confirm that free model access and the free server option still match the notes used in this article. Run the fixture set there only when those terms still fit the constraints of your release. Results you should record You should record pass or fail for each fixture, not a vanity metric about how fast the draft appeared. In this proposed case, success means three unit checks pass and the live curl shows blocked true for the sample snapshot. Failure means any unknown license is classified as permissive, or the endpoint returns 200 while a gate is fail_closed. Do not publish those checks as completed results until you have actually executed them on your machine. Who should not use this approach You should skip this workflow if you need a lawyer's opinion rather than an engineering gate. The sample table is intentionally tiny, and it will mislead you if you treat it as a full license review. You should also skip it when your release cannot tolerate a hard 422 from the inventory route. Skip it too if you need a capacity guarantee that the free server option does not clai
Comments
No comments yet. Start the discussion.