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#15427hotlong wants to merge 2 commits into
Conversation
…ing 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
…p-vs-platform-boundary # Conflicts: # scripts/pm/check-skill-line-ratchet.mjs
660ed00 to
087f60a
Compare
|
Memo from the This PR adds Read against that ruling, the new file carries three rules — the deciding question with its rows, the publication test ("one consumer is a use; two is a contract"), and the fixed order ("make the derived half trustworthy first, then take the hand-written half back") — and roughly half of its lines are the story behind each rule ("decided ad hoc three times in one day…", "Measured twice. A card wanting…", the Two adjacent facts so nobody re-derives them: the customer-facing half of the same question (the deciding question only) is in flight on #15428 against Generated by Claude Code |
|
Ordering memo from the skills seat (session Generated by Claude Code |
Part of #15420.
The boundary was decided ad hoc three times in one day, by three seats, each from scratch, and the three derivations differed. This states it once.
AGENTS.md's 1162 ceiling is unchanged, and every gate is green.⛔
AGENTS.mdand.claude/skills/**are governed surfaces: no auto-merge is armed, nothing is self-approved, and landing is the maintainer's by hand.The arithmetic, spelled out
AGENTS.mdorigin/mainWhole diff: 3 files, 76 insertions, 0 deletions. ⛔ Nothing was deleted to make room — no existing rule was touched, shortened or relocated.
The real run, verdict lines quoted verbatim
node scripts/pm/check-skill-line-ratchet.mjsat the final head087f60a46— exit 0:Self-test alongside it:
✓ check-skill-line-ratchet self-test: 155 cases pass.The max-line-bytes pin — measured before pushing, not discovered by the gate
The second map (
MAX_TABLE_ROW_BYTES) pinsAGENTS.mdat 1081, andmainsits exactly on it. Measured both sides withawk '{print length}' AGENTS.md | sort -rn | head -1:The pin is untouched, and the row I added is a third of it.
Why the rule is not 8-12 lines of
AGENTS.mdproseHeadroom was 1 line, and both funding routes an author may take alone are closed here:
.claude/agents/os-dev.mdre-wrap funding was available (three lines of wrap artifacts, exactly the three needed) and was refused.AGENTS.mdgives upNlines its ceiling falls byN, so the test becomes1161 − N + K ≤ 1162 − N, i.e.K ≤ 1for anyN. The move buys exactly nothing beyond the one line of headroom that already existed. That is by design — the header says a move is "never as a way to grow the corpus".So the split follows the ratchet's own division of labour, which is also the remedy sentence it prints when it goes red: principles in the instruction file, on-demand detail in
references/.AGENTS.mdgains one line — aContext Routingrow carrying the deciding question itself plus the pointer. That section's whole job is "apply the right role per path", and the boundary genuinely isexamples/**app metadata versuspackages/**capability..claude/skills/pm-dispatch/references/app-platform-boundary.md: the deciding question with its table, the publication test, and both anti-patterns with the measurement behind each.references/grew +31% in one shift).mainlanded the identical shape while this was in flight.#15402addedreferences/core-rules.md(150 lines) with new rows at the same two anchors, commented "it is a NEW file, so this is an added row and no other row moves". That produced the only merge conflict here, resolved by keeping both entries; it is also independent confirmation that a new-file row is the accepted convention rather than a raise.Traceability — carried self-contained, not by issue number
check:pm-skill-id-lintwent red on the first draft with 6 issue-ID citations. Maintainer ruling 2026-08-12, verbatim and untranslated:「立一张结构卡,我觉的处理 issue 时犯的错应该总结成经验,保留 issue id没有意义,如果ai去查原始issue,得不偿失。」
That gate scans everything under
.claude/skills/pm-dispatch/, so it governs the new reference file too. Both files therefore carry each lesson self-contained — failure mode, discipline, boundary — and cite source paths rather than issue numbers, which is the more durable anchor anyway. Provenance for review:packages[]path (#15005) #15261, which published nothing@objectstack/clisubpaths but ratifies only./console—extractHookBody(and./package.json) have no public entry, and an app's hook-body fidelity harness breaks with no replacement #15325 (half the need was alreadyos build --strict-body; the other half named anos lintrule that exists nowhere in the tree) and finding — a gating lint rule shipped claiming "0 findings over the corpus" and names an object that fires it; the corpus copy is not the real app #15357 (a rule shipped claiming "0 findings over the corpus" against a corpus that was not the app it named)@objectstack/verify: an option-B artifact makesos verifyreport a green run that measured nothing #15229 that landed asc550bafc2Re-verified against the tree rather than taken from the card:⚠️
os build --strict-bodyis real (packages/cli/src/commands/compile.ts, of whichbuildis an alias);verify.tsalready carries the anti-pattern's own sentence — "a verifier that under-verifies reports success it never established".os verify's zero-case defect was closed onmaintwo commits before this branch's base, so it is written as a landed lesson, not a live defect.Verdict:
CLAUDE.md— NO, do not mirrorCLAUDE.mdonmainis 36 lines, not the 86-line version some contexts still carry.For: the rule is repo-wide, applies to every seat, and the file has 50 lines of ratchet headroom — the cheapest place in the repo to put anything.
Against, and this wins:
poptakes another's stash entry; your release-notes row conflicts with eighteen merges. This rule is different in kind — getting it wrong costs your own card's hours and yields a reviewable PR. feat(runtime): every top-level collection read gains apackages[]path (#15005) #15261 is the proof: caught on contract review, rejected, rewritten, no other agent harmed.Verdict: the 11 published skills — read, not assumed
objectstack-platformalready owns this question. It carries a section titled "The App / Platform Boundary" (line 229) whose first bullet is "Business features belong in the app; capability belongs in the platform", and it already carries anti-pattern 1 nearly verbatim at line 240: "no hand-written predicate re-implementing a platform rule". The card's guess was right — the doctrine has a home.objectstack-pm-dispatch— NO. Its "Upstream reporting" section (line 648) defers to that section by name: "The doctrine lives inobjectstack-platformunder The App / Platform Boundary". Single-owner is already the arrangement; a second copy is a drift site.objectstack-upgrade— NO. Its⛔ The boundarysection (line 28) is a different boundary — conversion chain versus hand edits — and already carries its own scoped instance of anti-pattern 1 ("Never hand-write a rewrite the chain already applies"). The general rule there would duplicate platform's, in a skill loaded only during a major upgrade.The other eight (
ai,api,automation,data,formula,i18n,query,ui) — NO. Each is a metadata-authoring domain skill; the boundary is not a per-domain authoring question, and eight copies is eight drift sites.The publication half must NOT ship to customers. "Would a second app copy the implementation?" decides what a package in this monorepo exports. A customer app author cannot act on it — contributor guidance, pure noise there. It stays repo-internal.
The one genuine app-facing gap —
objectstack-platformstates which side owns what but not how to tell; it has the conclusion, not the discriminator. Filed as #15428 rather than ridden in here, becauseskills/**is a separate customer-visible governed surface with its own budget (check:skills-token-ratchet: 12868 / 12984 tokens, 116 headroom — a three-line amendment fits, measured).Verification
Gate family re-derived at the final head, not recalled:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack→ 3 paths, 34 runnable families. All 34 run at087f60a46, every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?).check:pm-skill-ratchet,check:pm-skill-id-lint,check:ratchet-remedy-authority,check:skill-frame-sync,check:pm-dispatch-gates,check:pm-governed-prose,check:pm-governed-merges,check:nul-bytes,check:cross-package-test-inputs,check:self-test-wired, and bothcheck-closing-keyword-parityspellings with their self-tests. The five scripts that read the ratchet's maps are all in this set, so the map edit is mirror-checked.check-required-contexts.mjs --verify-required-setexits 2 against GitHub's API (401 without a token, 403 retried through the session proxy). Its own words: "NOT VERIFIED is not a pass and not a failure of the tree; exit 2 classifies the ENVIRONMENT." ⛔ Not counted as either colour; CI runs it with a token.check:doc-formula-expressionsneeded@objectstack/formulaand@objectstack/lintbuilt. Green after building both.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'matches nothing in either edited markdown file; no non-table line in either exceeds 120 bytes.Conflict check re-run after the merge —
git merge-tree --write-tree --name-only origin/main HEADis clean (exit 0, tree OID only). The earlier #15290 hazard the card named is moot forAGENTS.md: that PR edits lines 457-477, this PR adds one row at 836.⛔ No code, no test and no
os verifychange. ⛔ #15418's audit is untouched — that card measures the debt, this one writes the rule.Changeset
None, deliberately —⚠️
skip-changesetis the right label; an empty changeset would be wrong. The diff is one root governance document, one internal agent reference, and one internal gate script: it publishes nothing from any released package.Check Changesethas exactly two exemptions — the label and the changesets release branch — and no path-based exemption, so this PR is red without the label. The dispatch order reserved applying it, and the additive REST endpoint is unavailable to my session (HTTP 403), leaving only a whole-set write that can strip a concurrent label — so it is flagged here rather than applied.🤖 Generated with Claude Code
https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m