data/index.json is the manifest. data/facets.json is the cross-company facet
map. data/<company>/<slug>.json is one principle. data/teaching/<company>/
is the teaching prose for a set. Nothing here knows about any application that
consumes it.
A consumer picks one company and shows only that company's set.
{
"version": 5,
"generated": "scripts/build_index.py",
"companies": [
{
"id": "amazon",
"name": "Amazon",
"set": "Leadership Principles",
"source": "https://www.amazon.jobs/content/en/our-workplace/leadership-principles",
"principles": [
{ "id": 1001, "slug": "customer-obsession", "name": "Customer Obsession",
"sort": 1, "file": "data/amazon/customer-obsession.json",
"facets": ["customer-obsession"] }
]
},
{
"id": "arm",
"name": "Arm",
"set": "10x Mindset",
"source": "https://careers.arm.com/life-at-arm",
"principles": [
{ "id": 2008, "slug": "own-it", "name": "Own it", "sort": 8,
"file": "data/arm/own-it.json",
"facets": ["acts-like-an-owner"] }
]
}
]
}The sample shows the object shape for two companies. The live company list,
and which company is first, is data/index.json. The first company in that
file is the default set.
A company's id is its kebab-case directory name. A principle's id is its
number. They are different kinds of thing that share a key name, which is
worth knowing before reading the file.
The first company in companies is the default set. A consumer opened with no
company selected shows that set. name is the display name, and it is the only
place that name is stored. The id does not change when the display name does.
preamble is optional. When present it is a non-empty string, the set's
opening paragraph, markdown emphasis allowed. It is omitted when the company
has none.
facets is the list of cross-company facet ids that include this principle,
projected from data/facets.json. A principle that appears on no facet carries
no facets key. A consumer that generates examples reads the facet ids from
the index and the source-row refs from data/facets.json. A consumer that
shows examples (porridge) reads the generated rows on those facets, not the
source refs and not the human rows on the records.
Generated by scripts/build_index.py, never hand edited. scripts/validate.py
fails when it drifts from the record files or from facets.json. It exists so
a browser can discover the sets without globbing a directory.
Amazon records do not carry group:
{
"id": 1001,
"slug": "customer-obsession",
"company": "amazon",
"name": "Customer Obsession",
"sort": 1,
"definition": "Leaders start with the customer and work backwards.",
"terms": [
{ "id": "co", "label": "CO", "kind": "alias" },
{ "id": "customer-advocacy", "label": "customer advocacy", "kind": "equivalent" },
{ "id": "user-empathy", "label": "user empathy", "kind": "facet",
"rows": ["product-design", "responding-to-a-complaint"] }
],
"rows": [
{ "id": "product-design", "situation": "On product design",
"under": "...", "justRight": "...", "over": "..." }
]
}A record with company-written calibration marks every row:
{
"rows": [
{ "id": "the-companys-own-example", "situation": "...",
"under": "...", "justRight": "...", "over": "...", "words": "quoted" },
{ "id": "one-we-wrote", "situation": "...",
"under": "...", "justRight": "...", "over": "...", "words": "authored" }
]
}Arm records carry group for the lens (one-arm or accelerate-impact):
{
"id": 2008,
"slug": "own-it",
"company": "arm",
"group": "accelerate-impact",
"name": "Own it",
"sort": 8,
"definition": "Be accountable for the outcome; don't wait for instructions. Bring solutions, not problems.",
"terms": [
{ "id": "own-it", "label": "own it", "kind": "alias" }
],
"rows": [
{ "id": "accountable-for-the-outcome", "situation": "Being accountable for the outcome",
"under": "...", "justRight": "...", "over": "..." }
]
}The cross-company facet map. Same facet, same examples: principles that share a facet share the rows mapped to that facet.
{
"version": 1,
"facets": [
{
"id": "acts-like-an-owner",
"label": "acts like an owner",
"principles": [1002, 2008, 4001, 6004],
"rows": [
{ "principle": 1002, "id": "knowing-what-you-own" },
{ "principle": 2008, "id": "accountable-for-the-outcome" },
{ "principle": 6004, "id": "dawns-own-example" }
]
}
]
}idis kebab-case, unique across all facets.labelis the human name, andidmust be its slug.principleslists numeric principle ids from any company, including multiple principles from the same company. At least one.rowslists two kinds of thing, and they are not interchangeable. A ref{principle, id}points at a human row on a record (Amazon authored, Dawn quoted). That is source data for the generator: which behaviors this facet covers, and the voice it should sound like. An inline row{id, situation, under, justRight, over, words: "generated"}is app data. Porridge shows those. Generated rows must not carryprinciple. At least one source ref when any listed principle has record rows to point at. A facet whose every principle hasrows: []may be generated rows only, because there is no human row to reference. Every principle in the corpus must appear on a facet that has at least one generated row. A display consumer that finds none fails the build rather than rendering an empty table. It does not fall back to the refs.
A principle that is on a facet inherits that facet's generated rows in the
app. Human rows on the records, and {principle, id} refs on the facet, stay
in this repository as source. They are not the porridge table. A principle may
also have local human rows that no facet claims; those stay on that principle
only, still as source, not as app rows.
A facet can be a slice: better-every-day maps Dawn's "Better Than Yesterday"
and only the continuous-improvement rows of Amazon's "Insist on the Highest
Standards," not the whole IHS list. The rows array names exactly what belongs
to that facet.
Local terms of kind facet on a principle stay as they are. Those are
intra-principle slices. The facet map is the cross-company layer.
A consumer that shows calibration (porridge, like BIQ example packs) reads
facet ids from index.json and displays only inline generated rows from
facets.json. It does not resolve {principle, id} refs into the table, and
it does not fall back to the principle's own human rows. A real principle
with no generated rows is invalid. The build fails. A fallback sentence may
remain in the template for data that failed this check. It is not a state a
valid manifest can render.
A consumer that generates calibration (porridge's generator) reads those
refs, loads the human rows, and uses them as the quality bar. It writes a full
generated set onto the facet. It does not skip a facet because source refs
already exist. Those refs are why it can generate. It never overwrites
quoted or authored rows on records, and it never copies them into the
generated set marked as generated.
words on a row stays unchanged. A quoted row is the company's writing. A
generated row is the app table. They do not appear side by side in porridge.
idis a number, and it is unique across the whole repository. A consumer addresses a principle byidalone and cannot land on the wrong record.- Each company owns a block of a thousand ids: Amazon 1000, Arm 2000, Coupang
3000, Delivery Hero 4000, GitLab 5000, Dawn 6000, Toyota 7000, Generic 8000,
Blue Origin 9000, U.S. Marine Corps 10000. Blue Origin's published source is
https://www.blueorigin.com/careers/work-culture. The Marine Corps source is
the official TECOM / The Basic School Officership Foundations handout,
https://www.trngcmd.marines.mil/Portals/207/Site%20Images/TBS/B1X0099XQ%20Officership%20Foundations.pdf.
blockinscripts/companies.pyis the registry, and claiming the next free block is the whole of adding a company. A new principle takes the next free number in its company's block. - An id is never reused and never renumbered. A principle keeps its number when it is renamed, resorted, or regrouped. That is the point of it being a number: the name is what changes, the id is what does not.
slugis kebab-case and matches the filename without.json. It is unique within a company and not across companies.dive-deepbelongs to Amazon, Coupang, and Delivery Hero, and each is a different principle.- Two companies sharing a slug is allowed and interesting. It is the first
signal that they may be describing the same behavior, so
validate.pyprints the list rather than hiding it. companyis kebab-case and matches the parent directory. Current companies aregeneric,amazon,arm,coupang,delivery-hero,gitlab,dawn,toyota,blue-origin, andusmc.groupis optional, and a record carries it when the company publishes its set under lenses. Arm has two,one-armandaccelerate-impact, five principles each. Toyota has two,continuous-improvementandrespect-for-people, three then two. Dawn has seven headings over 15 tenets, from its September 2024 poster, and they do not follow the numbering. A company that publishes no lenses carries nogroup, andGROUP_BY_COMPANYinvalidate.pyis the list of the ones that do.sortis unique one through n per company, in teaching order, following the order the company itself publishes. Amazon is Customer Obsession first and Deliver Results last (one through 14). Arm is Passion for the mission first and Optimize for endurance and resilience last (one through 10). Coupang is Wow the Customer first and Hate Waste last (one through 15). Delivery Hero is Own It first and Stay Humble last (one through six). GitLab is Collaboration first and Transparency last (one through six), the order that spells CREDIT. Dawn is Think big, start small first and Race on the racetrack, walk on ice last (one through 15), following the document's numbering. Toyota is Challenge first and Teamwork last (one through five). Blue Origin is Passion for our Mission first and Have Backbone; Disagree and Commit last (one through 14). The U.S. Marine Corps is Know yourself and seek self-improvement first and Seek responsibility and take responsibility for your actions last (one through 11), the numbered doctrine order Marines recite. Generic, shown as Universal Leadership Principles, is Customer Obsession first and Intentional About Culture last (one through 15), the order in kindel/principles issue 71.definitionis the short statement of the principle, transcribed fromsourceunder Sourcing below. For a company set that source is the company's own text. For the generic set it is kindel/principles issue 71. It is a quotation, so it is never condensed, reworded, reordered, or merged. Where the statement runs long, take a leading excerpt of whole sentences rather than summarizing. Three normalizations are allowed and nothing else: strip markup, fold punctuation to ASCII, and correct an obvious typographical error.sourceis where the set was transcribed from. A URL when the company publishes the set itself, the name of the first-party document when it does not, or the issue URL when this repository authored the set. See Sourcing.- File paths in the manifest are
data/<company>/<slug>.json. - Nothing lives at
data/*.jsonexceptindex.json.
The vocabulary a person might use to name this principle. One list, three kinds.
aliasis the short form used at that company. Abbreviations and everyday names,bfa,own it,one arm.equivalentis how everyone else says it, and it means the whole principle.move fast,do more with less.facetalso means how everyone else says it, but it names one slice of the principle rather than all of it. A facet must carryrows, listing the row ids it covers.
id is the slug of label and must be unique within a company, not globally.
A consumer addresses a term by company and term id. Two labels that slug to
the same id are the same term, so think big and think-big may not both
appear in one company.
The distinction between equivalent and facet is the point of the list. A
consumer showing results for an equivalent shows everything it has for the
principle. A consumer showing results for a facet shows only what maps to
those rows, so someone who asked for user empathy is not handed the whole
of Customer Obsession back.
The calibration taxonomy, and the anchor a facet points at.
- Real situations, at least one, each with a kebab
idand a shortsituationlabel. A principle with no rows is not modeled, and that is the only count this file has an opinion about. How many past one is judgment: enough that a person could recognize the behavior in their own week. Most principles land somewhere around five to a dozen, but that is an observation rather than a rule, and a company that published a single triple gets one row. A company may setcalibrationtounpublishedinscripts/companies.pywhen the set is definitions only and the record-level calibration has not been written. Those records carryrows: []. Do not invent record rows to clear the flag. The porridge table is still required: the principle sits on a facet with generated rows. Every other company still fails validation with no record rows. Remove the flag when the record rows exist. under,justRight, andoverare one to three sentences each. They describe one behavior under indexed, balanced, and over done.- Row ids are unique within a principle. A facet may only reference a row on its own principle.
wordsrecords whose words the calibration is:quotedfor the company's,authoredfor a person's,generatedfor an app's. It is optional, and a row without it isauthored. When any row in a record carrieswords, every row in that record carries it, so a reader of one file never has to infer it.
words describes under, justRight, and over. The situation label is
always ours, because it names the row rather than quoting the company, and it
follows the authoring rules whatever the row is marked.
A quoted row's calibration is transcribed from the company, the same standing
definition has, and the same consequences follow. It is never condensed,
reworded, or split to fit, the authoring rules do not apply to it, and neither
does the one-to-three-sentence rule. Eleven of Dawn Aerospace's 45 calibration
strings run past three sentences and one runs to eight; that is the company's
writing, not a defect to correct. The em dash and --- checks still apply,
because those are about what the file may contain, exactly as they are for
definition.
Dawn published one under, just-right, and over per tenet, so its records carry
one row each. Padding those up with our own prose to hit a number would put two
voices in one list, which is the thing words exists to prevent. Rows get
added to a Dawn record when someone has a situation worth adding, not to
satisfy a count.
generated exists because the app table is generated. kindel/porridge is
where that happens, the same way BIQ generates example packs and keeps the
question bank human. Human rows on records (Amazon authored, Dawn quoted) are
the source: they define the facet, set the voice, and validate the model. They
are not what porridge shows.
Generated rows land on the facet, inline in facets.json, not on a company
record. Every facet gets a full generated set. Source refs on the facet are
not a reason to skip. A generated row that arrives here marked authored is
indistinguishable from a person's work, and the next generator trains on it.
Marking it is what keeps the human rows identifiable as the reference they
are. See tests/README.md and kindel/porridge#3.
Every authored string: no em dash, no ---, Oxford commas, numbers under 10
spelled out. That is situation, under, justRight, over, and term
labels.
definition, and the calibration of any row marked words: "quoted", is
quoted rather than authored, so the company's punctuation and grammar stand
even where they disagree with the rules above. The em dash and --- checks are
the exception and run everywhere, so pick an excerpt that carries neither.
Teaching prose for a set that has it. Name, definition, and rows stay on the
principle record. Porridge renders this directory. The field names are the ones
the files already use. blog is the Further reading list. It is not renamed.
data/teaching/<company>/index.jsoncatalog. Every company has one.data/teaching/<company>/<slug>.jsonone principle. Every principle has one.
A catalog principles list may be empty in a fixture. That is Further reading
for the set and no principle teaching files yet. The live corpus lists every
principle. Each entry is id, slug, and file. file is <slug>.json in
that directory, and the file has to exist. A slug is listed once. index is
reserved for the catalog and is not a principle slug. When the catalog exists,
a teaching file other than the catalog has to be listed.
A principle file may be reading-only: id, slug, and blog. Porridge
renders that list on the principle page. Full teaching prose is required when
any of why, calibrationIntro, examples, looksLike, deepen, or
related is present. validate.py fails the live tree when a company is
missing a catalog or a principle file. A missing file is a silent empty
Further reading section in the apps, so the check is the contract.
Amazon is the classic 14. Any Company (generic) reuses that teaching.
data/maps/<source>-<target>.json records the reuse. The file names both
companies, and every lookup uses those ids. generic-amazon.json is the Any
Company map. edits is the allowlist of sentences that differ. A slug rename
is not an edit: the source principle's slug replaces the target slug in the
copied text. A source principle with no target id is not a copy. Teaching
on that principle, when it exists, is authored from its own definition and
is not diffed against a target.
kindelwww mounts this repo's data/ at assets/data/principles, so Hugo
reads data/principles/teaching/<company>/<slug>.json when that file exists.
Amazon is required: a missing Amazon file is an error. The standalone app
fetches https://cdn.jsdelivr.net/gh/kindel/principles@main/data/teaching/amazon/<slug>.json
for Amazon, and the same path with the company directory for a set that has teaching.
Identity is (company, numeric id). The filename is the slug.
When prose mentions another principle, write a token {lp:<slug>} inline.
Example: That is {lp:ownership}, not heroics.
Renderers replace the token with a link. Agents treat tokens as hard links.
Every {lp:slug} token in why, calibrationIntro, examples, looksLike, deepen, a related note, or a Further reading note must also appear in related. Navigation built from related has to see the same slugs the prose links. related may include extra principles that are not tokenized in prose.
Never write raw company names or product names in the prose and notes. Principle names stay. Further reading titles may name the author, the book, or the source company.
{
"id": 1001,
"slug": "customer-obsession",
"why": ["Short paragraph.", "Another paragraph."],
"calibrationIntro": "How to use the rows below.",
"examples": [
{"title": "Short title", "body": "Teaching example, company-agnostic."}
],
"looksLike": {
"individual": "What it looks like for an IC.",
"manager": "What it looks like for a manager."
},
"deepen": [
"Diagnostic question you ask yourself, a teammate, or anyone you are reviewing."
],
"related": [
{"id": "ownership", "note": "Why they connect, one sentence."}
],
"blog": [
{"title": "Essay title", "url": "https://kindel.com/essays/<slug>/", "note": "Why this essay belongs here."},
{"title": "Book or letter title", "url": "https://...", "note": "Why this source belongs here."}
]
}Rules:
idis the numeric id of the principle record in this company.slugmatches the filename without.json, and that slug exists on a principle record for the company.whyis 3-6 short paragraphs. Each array item is one paragraph. Omit it on a reading-only file.examples2-4 teaching cases. Generalize retail and ops specifics. Drop named-exec anecdotes you cannot restate without the company. Omit them on a reading-only file.deepen6-12 questions. Each is a full sentence ending with?. Omit them on a reading-only file.relatedis the union of every{lp:slug}token in the prose fields and notes plus any extra curated links. Eachrelatedid is a slug in this company. Two is a floor, not a cap. Omit it on a reading-only file. A reading-only note must not carry a{lp:slug}token, because there is norelatedlist to put it on.blogis 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.pyallows a longerblogarray 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 athttps://kindel.com/essays/<slug>/with the trailing slash. A blog post outside that category stays athttps://blog.kindel.com/YYYY/MM/DD/<slug>/.scripts/essay_catalog.jsonis the checked-in list of essay ids and slugs, fromhttps://blog.kindel.com/wp-json/wp/v2/posts?categories=448. Refresh it by fetching every page (per_page=100, followX-WP-TotalPages), keepingidandslug, and sorting by slug.validate.pyrejects ablog.kindel.comURL whose dated slug or?p=id is in that list, and leaves every other blog URL alone. An apex essay link has to be exactlyhttps://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. Aurlorhrefvalue 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
bloglink 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. - No source-company names, products, executives, internal tools, or wiki chrome in the prose and notes. Further reading titles may name the author, the book, or the source company.
validate.py checks that the slug exists, that each related id exists, and that every {lp:...} token in the prose and notes resolves to a slug in the company and is listed in related. A token in the catalog must resolve too. A catalog principles list may be empty. Each listed file has to exist in that directory, named <slug>.json, and a slug is listed once. index is reserved for the catalog and is not a principle slug. When the catalog exists, a teaching file other than the catalog has to be listed. A file whose keys are only id, slug, and blog skips the prose counts. Any other teaching field requires those counts. The live tree must have a catalog and a teaching file for every principle. It also checks the counts and object shapes in the rules above, and that blog is a non-empty list of title, url, and note. It rejects an em dash and ---. An en dash is allowed only in a Further reading title. It rejects a blog.kindel.com link whose post is in scripts/essay_catalog.json, and an essays link that is not exactly https://kindel.com/essays/<slug>/ for a slug in that file. Dot segments are folded first. A stored field keeps its trailing punctuation for that check. A url value with other text or surrounding space fails too. Percent-encoding is decoded once. tests/test_reading.py checks the Further reading shape and the approved cites.
A set is transcribed from one source, named in source.
Usually that is a page the company publishes itself, and permission is not in
question. source is the URL.
It can also be a first-party document the company has authorized us to publish,
an internal handbook or wiki page that the company has not put on its marketing
site. source names the document. Dawn Aerospace's Company Tenets arrive this
way.
The generic set is the other case. This repository authored it, in
kindel/principles issue 71, as principles that apply to any company. source
is that issue. The definitions are quotations of the issue text, under the
same transcription rules as a company page.
What it is never is somebody else's account of a set. A set reconstructed from a conference talk, a recruiter's summary, or a page the company has taken down and not replaced does not belong here, however faithful the reproduction looks. Klarna was dropped for exactly this: the page is gone and what remains is other people's copies of it.
It is a Go module, so a Hugo site can mount it:
module github.com/kindel/principles
An application that serves JSON to a browser from its own origin should vendor a pinned copy into its own tree with a script, and check that the copy still matches the pinned tag. A copy is acceptable when it is generated and verified. A copy kept in step by a human reading this file is not, which is the mistake this repository exists to correct.
A consumer must pick a company and show only that company's set.
A file path is built from slug, so URLs and filenames stay readable. Identity
is id. A consumer that keys on slug alone is the bug this schema was
changed to prevent.
Version with tags. A schema change is a major version, so consumers can move
at their own pace. version in the manifest is 5: version 4 resolved
{principle, id} refs into the display table, version 3 had no cross-company
facet map, version 2 had a kebab-case id that was unique only within a
company.