Skip to content

Commit c24e2d3

Browse files
hotlongclaude
andauthored
docs(agents): state the app-vs-platform boundary once, so it stops being re-derived (#15427)
* docs(agents): state the app-vs-platform boundary once, so it stops being re-derived The boundary was decided ad hoc three times in one day, by three seats, each from scratch, and the three derivations differed. Nothing stated the rule. Written to fit the ratchet rather than raise it. AGENTS.md had exactly one line of headroom (1161 against a 1162 ceiling), and both funding routes an author may take alone are closed here: re-wrap funding is banned by the 2026-08-17 ruling, and a declared cross-file move cannot fund new content because the source decrease cancels against the destination raise. So the split follows the ratchet's own division of labour — principles in the instruction file, on-demand detail in references/: - AGENTS.md gains ONE line, a Context Routing row carrying the deciding question and pointing at the rule. 1161 -> 1162, exactly the ceiling, which is unchanged. - The rule itself lands in a new reference file: the deciding question, the publication test, and the two anti-patterns with the measurement behind each. - The new file is entered in both ratchet maps at its landed count, so it arrives metered rather than as an un-ceilinged file in a ratcheted directory. Every lesson is carried self-contained (failure mode, discipline, boundary) with no issue-ID citation, per the 2026-08-12 ruling that check:pm-skill-id-lint enforces. No deletions: nothing was removed to make room. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m * docs(agents): re-pay the boundary pointer against the new 1058 ceiling The rules-only rewrite compressed AGENTS.md 1161 -> 1058 and locked the ceiling to 1058, which took back the single line of headroom this pointer was paid from. Git merged the two changes without a conflict, so nothing textual flagged it: the file came out at 1059 against a 1058 ceiling and would only have gone red in the merge queue, after being queued and kicked. Re-paid at net ZERO lines rather than by raising the ceiling: the standalone Scope Triage row is folded into the `examples/**` row that was already there. The Example Author constraint is preserved verbatim, and the boundary question is worded "on any tree" so folding it onto that row does not scope the rule to that directory. ⛔ The ceiling is untouched, ⛔ no line was reclaimed by re-wrapping (banned), and no existing rule was dropped: AGENTS.md is +1/-1 against main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3e270d4 commit c24e2d3

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# App or platform — which work belongs where
2+
3+
Routed here from `AGENTS.md`. This boundary was decided ad hoc three times in one day, by
4+
three seats, each from scratch, and the three derivations differed. It is one rule, not new
5+
policy: the rule those decisions already followed, written down so it stops being re-derived.
6+
7+
Lessons here are self-contained by the 2026-08-12 ruling — failure mode, discipline and
8+
boundary in the text, no issue number to dereference.
9+
10+
## The deciding question
11+
12+
**Could this be written by something that has only the metadata, and no knowledge of this
13+
company?**
14+
15+
| Answer | Belongs in |
16+
|:--|:--|
17+
| **No** — it encodes the company's own judgement | the **metadata app**: a discount ceiling, who a case is assigned to, how won/lost is booked; its own objects, views and flows |
18+
| **Yes** — it only asks whether the metadata is self-consistent | the **platform**: reference integrity, translation coverage, view rosters, sharing-rule coverage, CRUD round-trips, RLS probes per declared position |
19+
| the subject is platform behaviour, the cost lands on the app | the **platform**, and it is a **gap** until it does — asserting what a hook does inside the platform's own sandbox is one |
20+
21+
The second row is the one that pays. An app that hand-writes it is hand-writing a
22+
consistency proof the platform can derive once, for every app, and check on every build.
23+
24+
## The second question, for a capability an app wants published
25+
26+
**If a second app needed this, would it copy the implementation?** Yes ⇒ platform.
27+
28+
One consumer is a **use**; two is a **contract**. A resolution wanted by four call sites
29+
inside a single package stays package-private on exactly this test — the home question gets
30+
decided when a second consumer is actually in hand, and a symbol that was never published
31+
can move without a major. ⛔ The dependency graph is not the test: check the real edges
32+
before claiming two packages cannot import each other, because three of six directed edges
33+
usually already exist.
34+
35+
⛔ This question decides what a package **in this monorepo** exports. It is contributor
36+
guidance and must not ship to customers, who cannot act on it.
37+
38+
## ⛔ Anti-pattern 1 — an app hand-copies a platform rule
39+
40+
The copy diverges from the rule the build actually applies, so the app stays green while
41+
the platform refuses. The divergence is invisible until the two disagree, and then the
42+
app's own test suite is the thing arguing for the wrong answer.
43+
44+
Measured twice. A card wanting the platform's hook-body lowering pass turned out to want
45+
half a command that already ships (`os build --strict-body`, which turns "bundled instead
46+
of lowered" into exit 1 — `packages/cli/src/commands/compile.ts`, of which `build` is an
47+
alias) and half an `os lint` rule that exists nowhere in the tree. One level up, the same
48+
shape ships a rule claiming "0 findings over the corpus" against a corpus that is not the
49+
app it names.
50+
51+
⇒ Before hand-writing against platform behaviour, grep the CLI for the command that already
52+
does it, and confirm the rule you are citing exists.
53+
54+
## ⛔ Anti-pattern 2 — a capability that under-delivers silently
55+
56+
Worse than none. `os verify` once derived **zero** cases on a multi-package app and reported
57+
success. `packages/cli/src/commands/verify.ts` states the shape in its own header: *a
58+
verifier that under-verifies reports success it never established*.
59+
60+
An app that meets one of these writes its own harness and never comes back — so the loss is
61+
permanent, and it is invisible, because the capability is still reporting green.
62+
63+
**The order is therefore fixed: make the derived half trustworthy first, then take the
64+
hand-written half back.** Reversing it just deletes protection. The fix for the `os verify`
65+
case landed as an ordered pair — ledger the four losses first so the fix has something to be
66+
checked against, then close them — which is this rule executed.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ their output is current; the wrapper reports that each run rather than staying s
758758
| `packages/adapters/**` | Integration Engineer | Framework bindings, zero business logic |
759759
| `packages/client*/**` | SDK Engineer | Public API, DX, type safety |
760760
| `apps/docs/**` | Docs Engineer | Fumadocs + Next.js, MDX |
761-
| `examples/**` | Example Author | Minimal, runnable, uses `defineStack` |
761+
| `examples/**` | Example Author | Minimal, runnable, uses `defineStack`. App or platform, on any tree: could this be written from the metadata alone, with no knowledge of this company? No ⇒ the app; yes ⇒ the platform, and a **gap** until it does — `.claude/skills/pm-dispatch/references/app-platform-boundary.md` |
762762
| `content/docs/**` | Technical Writer | Respect auto-gen boundaries |
763763
| `../objectui/**` (sibling repo) | Studio UI Engineer | React + Shadcn + Tailwind, dark mode default |
764764

scripts/pm/check-skill-line-ratchet.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,14 @@ export const CEILINGS = new Map([
664664
// convention as every entry above); it is a NEW file, so this is an added row
665665
// and no other row moves.
666666
['.claude/skills/pm-dispatch/references/core-rules.md', 150],
667+
// The app-vs-platform boundary — the deciding question (could this be written
668+
// from the metadata alone, with no knowledge of this company?), the publication
669+
// test (one consumer is a use, two is a contract) and the two anti-patterns.
670+
// It is on-demand DETAIL by this header's own division of labour: AGENTS.md
671+
// carries the one routing row, this file carries the rule and the measurement
672+
// behind each clause. A NEW file, so this is not a raise and funds nothing —
673+
// set at the landed line count, headroom 0, same convention as above.
674+
['.claude/skills/pm-dispatch/references/app-platform-boundary.md', 66],
667675
// Lane job descriptions (maintainer ruling 2026-08-19: per-lane PM job
668676
// descriptions move from seat-post prose into versioned skill references).
669677
// Set at landed line counts (headroom 0, same convention as above).
@@ -1036,6 +1044,7 @@ export const MAX_TABLE_ROW_BYTES = new Map([
10361044
['.claude/skills/pm-dispatch/references/true-green.md', 0],
10371045
['.claude/skills/pm-dispatch/references/compile-surfaces.md', 352],
10381046
['.claude/skills/pm-dispatch/references/core-rules.md', 0],
1047+
['.claude/skills/pm-dispatch/references/app-platform-boundary.md', 219],
10391048
['.claude/skills/pm-dispatch/references/lanes/engine.md', 0],
10401049
['.claude/skills/pm-dispatch/references/lanes/services.md', 0],
10411050
['.claude/skills/pm-dispatch/references/lanes/cli.md', 0],

0 commit comments

Comments
 (0)