Skip to content

Latest commit

 

History

History
501 lines (422 loc) · 27.4 KB

SCHEMA.md

File metadata and controls

501 lines (422 loc) · 27.4 KB

Schema

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.

index.json

{
  "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.

data/<company>/<slug>.json

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": "..." }
  ]
}

data/facets.json

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" }
      ]
    }
  ]
}
  • id is kebab-case, unique across all facets.
  • label is the human name, and id must be its slug.
  • principles lists numeric principle ids from any company, including multiple principles from the same company. At least one.
  • rows lists 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 carry principle. At least one source ref when any listed principle has record rows to point at. A facet whose every principle has rows: [] 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.

Consumer contract

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.

Rules

  • id is a number, and it is unique across the whole repository. A consumer addresses a principle by id alone 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. block in scripts/companies.py is 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.
  • slug is kebab-case and matches the filename without .json. It is unique within a company and not across companies. dive-deep belongs 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.py prints the list rather than hiding it.
  • company is kebab-case and matches the parent directory. Current companies are generic, amazon, arm, coupang, delivery-hero, gitlab, dawn, toyota, blue-origin, and usmc.
  • group is optional, and a record carries it when the company publishes its set under lenses. Arm has two, one-arm and accelerate-impact, five principles each. Toyota has two, continuous-improvement and respect-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 no group, and GROUP_BY_COMPANY in validate.py is the list of the ones that do.
  • sort is 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.
  • definition is the short statement of the principle, transcribed from source under 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.
  • source is 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/*.json except index.json.

terms

The vocabulary a person might use to name this principle. One list, three kinds.

  • alias is the short form used at that company. Abbreviations and everyday names, bfa, own it, one arm.
  • equivalent is how everyone else says it, and it means the whole principle. move fast, do more with less.
  • facet also means how everyone else says it, but it names one slice of the principle rather than all of it. A facet must carry rows, 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.

rows

The calibration taxonomy, and the anchor a facet points at.

  • Real situations, at least one, each with a kebab id and a short situation label. 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 set calibration to unpublished in scripts/companies.py when the set is definitions only and the record-level calibration has not been written. Those records carry rows: []. 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, and over are 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.
  • words records whose words the calibration is: quoted for the company's, authored for a person's, generated for an app's. It is optional, and a row without it is authored. When any row in a record carries words, 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.

Style

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

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.

Files

  • data/teaching/<company>/index.json catalog. Every company has one.
  • data/teaching/<company>/<slug>.json one 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.

Cross-refs

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.

Principle object

{
  "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:

  • id is the numeric id of the principle record in this company.
  • slug matches the filename without .json, and that slug exists on a principle record for the company.
  • why is 3-6 short paragraphs. Each array item is one paragraph. Omit it on a reading-only file.
  • examples 2-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.
  • deepen 6-12 questions. Each is a full sentence ending with ?. Omit them on a reading-only file.
  • 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. Omit it on a reading-only file. A reading-only note must not carry a {lp:slug} token, because there is no related list to put it on.
  • 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.
  • 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.

Sourcing

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.

Consuming this repository

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.