Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ Rules:
- `examples` 2-4 teaching cases. Generalize retail and ops specifics. Drop named-exec anecdotes you cannot restate without the company.
- `deepen` 6-12 questions. Each is a full sentence ending with `?`.
- `related` is the union of every `{lp:slug}` token in the prose fields and notes plus any extra curated links. Each `related` id is a slug in this company. Two is a floor, not a cap.
- `blog` is the Further reading list. It holds published tig.log essays that amplify this principle, plus at least one external source: the published principles, a shareholder letter, a book, or an essay or talk by someone who shaped the practice. Same shape on every file, including the catalog. Empty is not allowed. A principle with no tig.log essay still gets its external source.
- `blog` is the Further reading list. It holds published tig.log essays that amplify this principle, plus at least one external source: the published principles, a shareholder letter, a book, or an essay or talk by someone who shaped the practice. Same shape on every file, including the catalog. Empty is not allowed. A principle with no tig.log essay still gets its external source. A reused set may append Further reading after the copied list. Copied entries stay in step with the target. Extra entries are the source set's own. `validate.py` allows a longer `blog` array on the source when the target's entries are the prefix.
- A tig.log essay, a post in the WordPress Essays category (slug `essays`, id 448), is linked at `https://kindel.com/essays/<slug>/` with the trailing slash. A blog post outside that category stays at `https://blog.kindel.com/YYYY/MM/DD/<slug>/`. `scripts/essay_catalog.json` is the checked-in list of essay ids and slugs, from `https://blog.kindel.com/wp-json/wp/v2/posts?categories=448`. Refresh it by fetching every page (`per_page=100`, follow `X-WP-TotalPages`), keeping `id` and `slug`, and sorting by slug. `validate.py` rejects a `blog.kindel.com` URL whose dated slug or `?p=` id is in that list, and leaves every other blog URL alone. An apex essay link has to be exactly `https://kindel.com/essays/<slug>/`, and that slug has to be in the catalog. A typo, a missing trailing slash, `http`, a root-relative path, a path with no leading slash, an extra segment, a doubled slash, a dot segment, or an encoded slash fails. Percent-encoding is decoded once, including on a blog permalink. A stored field is judged before trailing punctuation is removed. A `url` or `href` value is judged as the whole string, before it is split. Only a parsed kindel.com host whose path is under essays has to be the canonical catalog URL. A Markdown destination is read whole, including balanced parentheses and angle brackets, and a title after it is not part of the link. A backslash escapes ASCII punctuation only. One terminal DNS dot on the host is ignored. A kindel.com or blog.kindel.com link with no scheme is checked the same way. A bare URL keeps balanced parentheses. An equals sign before a root-relative path is not part of the link. JSON files are checked after decoding, which lets a sentence end on a canonical essay link.
- Every `blog` link is real. Never invent a URL, and check that each URL resolves before it lands. External titles are the cite on the list. Use the published heading when it already names the work. When it is only a year or a short title, name the author or the source, and the work. Titles may name the source company or an executive. Notes follow the rules below.
- Every string: no em dash, no `---`, Oxford commas, numbers under 10 spelled out.
Expand Down
25 changes: 25 additions & 0 deletions data/teaching/generic/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,31 @@
"title": "Amazon's Leadership Principles",
"url": "https://www.amazon.jobs/content/en/our-workplace/leadership-principles",
"note": "The published source this set is derived from. It lists 16 now. This manual covers the counterparts of the classic 14."
},
{
"title": "Make Your Values Mean Something, Patrick M. Lencioni",
"url": "https://hbr.org/2002/07/make-your-values-mean-something",
"note": "Values only count when they are specific enough to hire, promote, and fire by."
},
{
"title": "Aligning Action and Values, Jim Collins",
"url": "https://www.jimcollins.com/article_topics/articles/aligning-action.html",
"note": "A principle set is real only when daily decisions match the words on the wall."
},
{
"title": "Netflix Culture Memo",
"url": "https://www.jobs.netflix.com/culture",
"note": "A public culture document that names the behaviors the company actually rewards and will not tolerate."
},
{
"title": "Run This Diagnostic to Thoughtfully Build (and Evaluate) Your Startup's Culture",
"url": "https://review.firstround.com/run-this-diagnostic-to-thoughtfully-build-and-evaluate-your-startups-culture/",
"note": "A practical checklist for building and testing the culture you say you want."
},
{
"title": "10 Things Your Corporate Culture Needs to Get Right, Donald Sull and Charles Sull",
"url": "https://sloanreview.mit.edu/article/10-things-your-corporate-culture-needs-to-get-right/",
"note": "Ten operating habits that separate a lived culture from a poster."
}
]
}
25 changes: 25 additions & 0 deletions data/teaching/generic/intentional-about-culture.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,31 @@
"title": "Amazon's Leadership Principles",
"url": "https://www.amazon.jobs/content/en/our-workplace/leadership-principles",
"note": "The published list includes Strive to be Earth's Best Employer. That principle is not in this corpus, so this page was not copied from it."
},
{
"title": "Make Your Values Mean Something, Patrick M. Lencioni",
"url": "https://hbr.org/2002/07/make-your-values-mean-something",
"note": "Values only count when they are specific enough to hire, promote, and fire by."
},
{
"title": "Aligning Action and Values, Jim Collins",
"url": "https://www.jimcollins.com/article_topics/articles/aligning-action.html",
"note": "A principle set is real only when daily decisions match the words on the wall."
},
{
"title": "Netflix Culture Memo",
"url": "https://www.jobs.netflix.com/culture",
"note": "A public culture document that names the behaviors the company actually rewards and will not tolerate."
},
{
"title": "Run This Diagnostic to Thoughtfully Build (and Evaluate) Your Startup's Culture",
"url": "https://review.firstround.com/run-this-diagnostic-to-thoughtfully-build-and-evaluate-your-startups-culture/",
"note": "A practical checklist for building and testing the culture you say you want."
},
{
"title": "10 Things Your Corporate Culture Needs to Get Right, Donald Sull and Charles Sull",
"url": "https://sloanreview.mit.edu/article/10-things-your-corporate-culture-needs-to-get-right/",
"note": "Ten operating habits that separate a lived culture from a poster."
}
]
}
23 changes: 20 additions & 3 deletions scripts/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,22 @@ def _collect_strings(obj, out):
out.append(obj)


def _with_copied_blog(src, mapped_dst):
"""Source may append Further reading after the copied list."""
if not isinstance(src, dict) or not isinstance(mapped_dst, dict):
return src
src_blog = src.get("blog")
dst_blog = mapped_dst.get("blog")
if (isinstance(src_blog, list) and isinstance(dst_blog, list)
and dst_blog
and len(src_blog) >= len(dst_blog)
and src_blog[:len(dst_blog)] == dst_blog):
Comment thread
tig marked this conversation as resolved.
out = dict(src)
out["blog"] = src_blog[:len(dst_blog)]
return out
return src


def _first_diff(a, b, path):
if type(a) != type(b):
return "%s: type" % path
Expand Down Expand Up @@ -817,7 +833,8 @@ def validate_derivation_maps(by_company, principle_to_facets, errs, root=None):
data/maps/<source>-<target>.json names both companies. Lookups and
teaching paths use those ids. Sentence changes are an allowlist on the
map. Everything else in the reused teaching must match the target,
aside from the source principle's id and slug.
aside from the source principle's id and slug. The source may append
Further reading after the copied list.
"""
data = root or DATA
maps_dir = os.path.join(data, "maps")
Expand Down Expand Up @@ -992,7 +1009,7 @@ def take(company, slug):
_collect_strings(src, source_blobs)
got = _map_strings(dst, apply_text)
got["id"] = rec["id"]
diff = _first_diff(got, src, rec["slug"])
diff = _first_diff(got, _with_copied_blog(src, got), rec["slug"])
if diff:
errs.append("%s: reused teaching differs from %s at %s"
% (pw, target_rec["slug"], diff))
Expand All @@ -1009,7 +1026,7 @@ def take(company, slug):
_collect_strings(dst_index, target_blobs)
_collect_strings(src_index, source_blobs)
got = _map_strings(dst_index, apply_text)
diff = _first_diff(got, src_index, "index")
diff = _first_diff(got, _with_copied_blog(src_index, got), "index")
if diff:
errs.append("%s: set teaching index differs from the target at %s" % (where, diff))
elif os.path.isfile(dst_index_path) and not os.path.isfile(src_index_path):
Expand Down
30 changes: 30 additions & 0 deletions tests/test_reading.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
who shaped the practice. See SCHEMA.md.
Amazon is the classic 14. Any Company reuses those lists, and authors
Intentional About Culture because that counterpart is not in this corpus.
The generic catalog and Intentional About Culture also append culture
reading that is not on the Amazon copies.
"""

import json
Expand Down Expand Up @@ -114,6 +116,34 @@ def test_external_cites(self):
for url, (want, n) in expected.items():
self.assertEqual(seen[url], n, want)

def test_generic_culture_cites(self):
expected = {
"https://hbr.org/2002/07/make-your-values-mean-something":
"Make Your Values Mean Something, Patrick M. Lencioni",
"https://www.jimcollins.com/article_topics/articles/aligning-action.html":
"Aligning Action and Values, Jim Collins",
"https://www.jobs.netflix.com/culture":
"Netflix Culture Memo",
"https://review.firstround.com/run-this-diagnostic-to-thoughtfully-build-and-evaluate-your-startups-culture/":
"Run This Diagnostic to Thoughtfully Build (and Evaluate) Your Startup's Culture",
"https://sloanreview.mit.edu/article/10-things-your-corporate-culture-needs-to-get-right/":
"10 Things Your Corporate Culture Needs to Get Right, Donald Sull and Charles Sull",
}
seen = {url: 0 for url in expected}
for name, items in lists("generic"):
for item in items or []:
url = item["url"]
if url not in expected:
continue
with self.subTest(file=name, url=url):
self.assertEqual(item["title"], expected[url])
self.assertNotIn("\u2014", item["note"])
self.assertNotIn("DEI", item["note"])
self.assertNotIn("DEI", item["title"])
seen[url] += 1
for url, title in expected.items():
self.assertEqual(seen[url], 2, title)

def test_no_duplicate_urls_in_a_list(self):
for name, items in lists():
urls = [i["url"] for i in items or []]
Expand Down
45 changes: 45 additions & 0 deletions tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,51 @@ def test_source_copy_drift_is_rejected(self):
errs = self.check(root)
self.assertTrue(any("reused teaching differs" in e for e in errs), errs)

def test_appended_further_reading_on_the_source_passes(self):
extra = {
"title": "Extra",
"url": "https://example.com/extra",
"note": "Source-only reading.",
}
with tempfile.TemporaryDirectory() as root:
self.plant(root)
for slug in ("index", "one"):
path = os.path.join(root, "teaching", "acme", slug + ".json")
with open(path, encoding="utf-8") as f:
doc = json.load(f)
doc["blog"].append(extra)
with open(path, "w", encoding="utf-8") as f:
json.dump(doc, f)
self.assertEqual([], self.check(root))

def test_an_empty_target_reading_list_is_not_a_copied_prefix(self):
with tempfile.TemporaryDirectory() as root:
self.plant(root)
path = os.path.join(root, "teaching", "other", "one.json")
with open(path, encoding="utf-8") as f:
doc = json.load(f)
doc["blog"] = []
with open(path, "w", encoding="utf-8") as f:
json.dump(doc, f)
errs = self.check(root)
self.assertTrue(any("reused teaching differs" in e for e in errs), errs)

def test_a_missing_copied_reading_item_is_rejected(self):
with tempfile.TemporaryDirectory() as root:
self.plant(root)
path = os.path.join(root, "teaching", "acme", "one.json")
with open(path, encoding="utf-8") as f:
doc = json.load(f)
doc["blog"] = [{
"title": "Not the copied source",
"url": "https://example.com/other",
"note": "Replaced the copied list.",
}]
with open(path, "w", encoding="utf-8") as f:
json.dump(doc, f)
errs = self.check(root)
self.assertTrue(any("reused teaching differs" in e for e in errs), errs)

def test_an_allowlisted_sentence_edit_passes(self):
with tempfile.TemporaryDirectory() as root:
doc = self.mapping(edits=[["Old sentence.", "New sentence."]])
Expand Down
Loading