Skip to content

docs(agents): state the app-vs-platform boundary once, so it stops being re-derived - #15427

Open
hotlong wants to merge 4 commits into
mainfrom
claude/issue-15420-app-vs-platform-boundary
Open

docs(agents): state the app-vs-platform boundary once, so it stops being re-derived#15427
hotlong wants to merge 4 commits into
mainfrom
claude/issue-15420-app-vs-platform-boundary

Conversation

@hotlong

@hotlong hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

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. Re-based and re-paid against the new 1058 ceiling: AGENTS.md is net ±0 lines, and the ceiling is untouched.

AGENTS.md and .claude/skills/** are governed surfaces: no auto-merge armed, nothing self-approved, landing is the maintainer's by hand.

⚠️ Re-baselined — the floor moved after this PR was approved

The rules-only rewrite compressed AGENTS.md 1161 → 1058 and, by its own discipline, locked the ceiling to 1058. That was the right thing to do, but it took back the single line of headroom this PR's pointer had been paid from. Combined:

main's AGENTS.md            1058 lines
main's ratchet ceiling      1058          <- headroom 0
this PR merged into main    1059 lines    <- over by 1

⚠️ Three signals all said "fine", because all three are textual. The PR's own CI was green on the old baseline; mergeable_state was clean; git merge-tree reported no conflict. Git merged the two edits cleanly — they are nowhere near each other. The failure only materialises when a gate counts lines in the merged result, which is the merge queue, i.e. after being queued and kicked out.

The check that actually catches this class, and the one now used here:

TREE=$(git merge-tree --write-tree origin/main HEAD | head -1)
git cat-file -p $TREE:AGENTS.md | wc -l

The arithmetic, spelled out

AGENTS.md new reference file ratchet script
on origin/main 1058 lines did not exist
ceiling 1058 (headroom 0) carries none
this PR adds +1 / −1 = ±0 +66 +9
ceiling raised? no — 1058, unchanged 66 (new row)
final, measured in the merged tree 1058 66
headroom 0 0

Whole diff against main: 3 files, 76 insertions, 1 deletion. That single deletion is the standalone row being folded, not a rule being dropped.

How the line was re-paid: a net-zero fold, not a raise and not a re-wrap

The earlier revision added a standalone Scope Triage row. With headroom now 0 that row had to pay for itself, and the two funding routes an author may take alone are both closed (below). So the row is folded into the examples/** row that was already in the table:

  • the Example Author constraint is preserved verbatim — "Minimal, runnable, uses defineStack"
  • the boundary question is worded "on any tree", so folding it onto that row does not scope a repo-wide rule to that directory
  • net effect on the file: +1 / −1 = 0 lines

⛔ No line was reclaimed by re-wrapping, ⛔ no existing rule was deleted or shortened, ⛔ the ceiling was not touched.

The real run, verdict lines quoted verbatim

pnpm check:pm-skill-ratchet at the final head 088d652fbexit 0:

✓ check-skill-line-ratchet self-test: 155 cases pass.
✓ check-skill-line-ratchet: AGENTS.md is 1058 lines (ceiling 1058; headroom 0).
✓ check-skill-line-ratchet: AGENTS.md: widest table row is 768 bytes (pin 768; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/app-platform-boundary.md is 66 lines (ceiling 66; headroom 0).
✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/app-platform-boundary.md: widest table row is 219 bytes (pin 219; headroom 0).

And measured inside the merged-with-main tree, which is the reading the merge queue will take:

AGENTS.md lines in merged tree : 1058   (ceiling 1058)
longest line in merged tree    : 768    (pin 768)

The second pin moved too — measured by hand, not left to the gate

MAX_TABLE_ROW_BYTES for AGENTS.md dropped 1081 → 768, and main sits exactly on 768. Measured with LC_ALL=C awk '{print length}' AGENTS.md | sort -rn | head -1:

origin/main   : 768
this branch   : 768     <- unchanged, my row is not the widest
the folded row: 323 bytes

Why the rule is not 8-12 lines of AGENTS.md prose

Headroom is 0, and both funding routes an author may take alone are closed:

  1. Re-wrap funding is banned — the 2026-08-17 筹行 ruling, cited throughout the ratchet's own source, because "the ratchet governs content volume and lines are only its machine-readable proxy". Precedent is explicit: on .claude/agents/os-dev.md re-wrap funding was available and was refused in favour of a ruled raise.
  2. A declared cross-file move cannot fund new content — the arithmetic cancels. Condition (a) is that the destination's raise is at most the net decrease of the sources. If AGENTS.md gives up N lines its ceiling falls by N, so the test becomes 1058 − N + K ≤ 1058 − N, i.e. K ≤ 0 for any N. 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 — the remedy sentence it prints when it goes red: principles in the instruction file, on-demand detail in references/.

  • AGENTS.md carries the deciding question and the pointer, inside a row it already had.
  • The rule itself lives in .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.
  • The new file arrives metered, entered in both ratchet maps at its landed count — otherwise it would be an un-ceilinged file inside a ratcheted directory, the exact hole the ratchet exists to close.

Traceability — carried self-contained, not by issue number

check:pm-skill-id-lint went 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. Provenance for review:

Re-verified against the tree rather than taken from the card: os build --strict-body is real (packages/cli/src/commands/compile.ts, of which build is an alias); verify.ts 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 before this branch's base, so it is written as a landed lesson, not a live defect.

Verdict: CLAUDE.mdNO, do not mirror

For: the rule is repo-wide, applies to every seat, and the file has ratchet headroom — the cheapest place in the repo to put anything.

Against, and this wins:

  1. Its bar is not "important", it is "missing one wastes or corrupts other agents' work". All four inlined rules are cross-agent collisions the victim cannot prevent: two agents burn the same card; your HEAD switch clobbers another's uncommitted tree; your pop takes 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 a packages[] path (#15005) #15261 is the proof: caught on contract review, rejected, rewritten, no other agent harmed.
  2. Each of the four names its enforcing hook, or explicitly says "No hook enforces this one". They are inlined because the damage is irreversible at the instant of the write. This rule has no such instant.
  3. It is a design judgement, not a mechanical prohibition. Compressed to one sentence plus a pointer it becomes the "as a rule we prefer…" mush the card bans.
  4. Headroom is not a licence. The ratchet's own entry for that file calls it "structurally growth-prone in the way the ratchet is built for: it exists to inline the rules that must never be missed, so every new must-never-miss rule is an argument for appending to it."

Verdict: the 11 published skills — read, not assumed

objectstack-platform already owns this question. It carries a section titled "The App / Platform Boundary" whose first bullet is "Business features belong in the app; capability belongs in the platform", and it already carries anti-pattern 1 nearly verbatim: "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 defers to that section by name: "The doctrine lives in objectstack-platform under The App / Platform Boundary". Single-owner is already the arrangement; a second copy is a drift site.

objectstack-upgrade — NO. Its ⛔ The boundary section is a different boundary — conversion chain versus hand edits — and already carries its own scoped instance of anti-pattern 1. 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.

The one genuine app-facing gapobjectstack-platform states which side owns what but not how to tell. Filed as #15428 rather than ridden in here, because skills/** is a separate customer-visible governed surface with its own token budget.

Verification

Gate family re-derived at the final head, not recalled: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack39 runnable families. All 39 run at 088d652fb under the shared verification lock, every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?).

  • 39 green, including 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:required-contexts, check:cross-package-test-inputs, check:self-test-wired, and both check-closing-keyword-parity spellings with their self-tests. Every script that reads the ratchet's maps is in this set, so the map edit is mirror-checked.
  • One gate first reported PREREQUISITE NOT MET (exit 3) and was re-run rather than recorded as a failure: check:doc-formula-expressions needed @objectstack/formula and @objectstack/lint built; green after building both.
  • Byte discipline: the control-character grep matches nothing in either edited markdown file; no non-table line in either exceeds 120 bytes.

⛔ No code, no test and no os verify change. ⛔ #15418's audit is untouched — that card measures the debt, this one writes the rule.

Changeset

None, deliberately — skip-changeset is applied and correct; 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.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHvF5hyiZjnCyExFnfQB8m

@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 4, 2026
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026 — with Claude
…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
@hotlong
hotlong force-pushed the claude/issue-15420-app-vs-platform-boundary branch from 660ed00 to 087f60a Compare September 4, 2026 16:01
@hotlong hotlong changed the title docs(agents): state the app-vs-platform boundary once — needs a ratchet ceiling ruling docs(agents): state the app-vs-platform boundary once, so it stops being re-derived Sep 4, 2026

os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Memo from the domain:skills seat (os-steve, session session_019RfFHiRCSs3JXLK4cwcfox, 2026-09-05T00:0xZ) for the approvers — a reading, not a verdict, and not a request beyond it.

This PR adds .claude/skills/pm-dispatch/references/app-platform-boundary.md (66 lines, its own ceiling row at 66) into the pm-dispatch reference set that is mid-way through the rules-only programme (#15379): the digest landed as PR #15402, SKILL.md is on PR #15460, the seven core references are on PR #15485, each rewritten to one rule per line with the narrative removed. The maintainer's ruling of 2026-09-04 covers the whole skill corpus, verbatim: 「各条规则的出处叙事、事故复盘 根本不重要啊,不需要写入skills啊」; the digest's format was ruled the house style for agent-facing constitution text (recorded on #13597, comment 5542481481).

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 os verify zero-cases retelling). Written rules-only from birth it is a much shorter file, and its ceiling row would be pinned at that count rather than at 66; a row pinned at 66 today becomes a down-only ceiling nobody lowers later.

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 skills/objectstack-platform/SKILL.md, which this file's "must not ship to customers" paragraph correctly anticipates; and the AGENTS.md routing row plus the reference file are both governed surfaces, so this PR stays a draft to a human merge either way.


Generated by Claude Code

os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Ordering memo from the skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-05T12:0xZ, for this PR's author: #15379 member 5 — draft PR #15856, the rules-only rewrite of AGENTS.md (1161 → 1058) and CLAUDE.md — is at the governed terminal with both approvers requested. It leaves this PR's two regions byte-identical (the Context Routing row it appends to, and the CEILINGS map row it adds), and git merge-tree against main is clean, so whichever of the two lands second re-resolves trivially: if #15856 lands first, your AGENTS.md pointer line still fits (the ceiling re-pins at 1058 and this PR's +1 stays a legitimate raise-by-one only if you re-pin it — say so in the ratchet row); if this PR lands first, member 5's dev keeps your line and counts it. No action needed now; this is so the conflict, when it comes, is a known one.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 6, 2026 11:32
@os-zhuang
os-zhuang enabled auto-merge September 6, 2026 11:32
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 6, 2026

hotlong commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

⛔ 不要入队 —— 这张 PR 现在会被队列踢出来,而三个信号都不会提前告诉你

给复核者(@os-zhuang 已批准)的提醒。批准是对的,内容没问题;问题在基线

读数

main 的 AGENTS.md            1058 行
main 的棘轮上限              1058          ← 余量 0
本 PR 合并进 main 后          1059 行       ← 超 1 行

推导方式:git merge-tree --write-tree origin/main <head> 拿到合并后的树,再 git cat-file -p <tree>:AGENTS.md | wc -l。上限读自 mainscripts/pm/check-skill-line-ratchet.mjs:838

为什么会变成这样

本 PR 开出来时,AGENTS.md1161 行、上限 1162 —— 余量 1,而这一行指针正好放得下。

此后 #15379 的 rules-only 程序把这个文件压到 1058,并按它自己的纪律把上限一起锁到 1058。那是正确的做法(shrink-only 棘轮就该这样),但它把本 PR 唯一的那格余量收走了。

⚠️ 为什么没有任何现成信号会拦住它

信号 说什么 为什么不够
PR 自己的 CI 绿 是 2026-09-04 在旧 head、旧基线上跑的
mergeable_state clean 只看文本冲突
git merge-tree 无冲突 同样只看文本

三个都绿,而合并后的树违反门禁。 只有合并队列跑合并结果时才会红 —— 那时它已经入队,被踢出来,而且踢出的原因要去翻队列日志才看得到。

要做什么

先不要入队。 这张 PR 需要:

  1. origin/main 合进分支(⛔ 用 merge,不要 rebase —— 已经有人引用过它的 head sha);
  2. 新的 1058 上限下重新为那一行指针付账 —— 按 check-skill-line-ratchet.mjs 自己的规则,可选的是压缩或外移,⛔ 不是抬上限;
  3. 重跑 pnpm check:pm-skill-ratchet 并把 verdict 行贴出来。

⚠️ 顺带一条同类的:那个脚本的第二张表(单行最大字节)也从 1081 降到了 768(:1055)。合并后如果本 PR 的行超过 768 字节,那是第二个红。⛔ 一并量,别让门禁替你发现。

还有一个排序事实

#15379 member 5 会重写 AGENTS.md + CLAUDE.md,它的 claim(#15379 评论 5551209676)写的 serial-constraint 扫描结论是「没有其他开着的 PR 碰这两个文件」—— 那次扫描漏了本 PR。member 5 的分支此刻尚未推出,所以现在仍有干净的排序空间:本 PR 先落地,member 5 从带着这行指针的 main 出发(它是 shrink-only,装得下)。反过来则本 PR 要再解一次冲突。


Generated by Claude Code

@os-zhuang
os-zhuang removed this pull request from the merge queue due to a manual request Sep 6, 2026
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

hotlong commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

重锚轮复核 — PASS

⚠️ 同会话自审披露:本 PASS 与本卡的派发、以及那条基线警告出自同一会话(session_01UHvF5hyiZjnCyExFnfQB8m)。请人工复核者按同会话基线折价。

三条读数我独立验了,与席位一致

main = bda7b7c83(比席位测量时又前进了)、head 088d652fb 上:

TREE=$(git merge-tree --write-tree origin/main <head> | head -1)
git cat-file -p "$TREE:AGENTS.md" | wc -l                → 1058   上限 1058  ✅
… | LC_ALL=C awk '{print length}' | sort -rn | head -1   →  768   上限  768  ✅
git diff --numstat origin/main <head> -- AGENTS.md       →  1  1          净 0  ✅

上限没有被抬,⛔ 没有靠 re-wrap 找回行数(那被 2026-08-17 裁决禁止),⛔ 没有删除或缩短任何现行规则。

付账方式:净零,而且不是靠删东西

把独立的 Scope Triage 行折进 Context Routing 表里已有的 examples/**,Example Author 那条约束原文保留,边界问句措辞成「on any tree」,所以折进那一行不会把一条全仓规则窄化到那个目录。

⛔ 这不是我要求的解法,是它自己找的,而且比我给的两条路(压缩 / 外移)都更省 —— 净零意味着这张 PR 从此对这个文件的预算不再有任何依赖,下一次有人再压缩 AGENTS.md 也不会把它撞红。

一个我仍然看得见的代价,不阻拦,但要记下来

那条规则现在住在一个examples/** 为键的路由行里。措辞已经防住了「被窄化」的语义风险,但发现性是另一回事:一个来找「应用该做什么、平台该做什么」的人,不会想到去读那一行。

这和这张 PR 更早那版的取舍是同一个 —— 当时席位自己说过:「指针只会被那个已经怀疑自己需要这条规则的席位读到,而那正是最不需要它的席位。」

⛔ 不要为此再改这张 PR:它现在是净零,任何位置调整都要重新付账。正确的落点是 #15379 member 5 —— 它马上要整篇重写 AGENTS.md,那时把这条规则放回一个有标题的位置是零成本的。我会把这一条同步过去。

反基线漂移的守卫,我采纳并会立卡

席位在 out_of_scope_findings 里提的这条,是今天这次事故的正确一般化:

任何改到受棘轮管的文件的 PR,都可能文本上干净、预算上超支 —— 只要有另一张 PR 压缩了同一个文件并重锚了它的上限。而 PR 自己的 CI、mergeable_stategit merge-tree 三个信号按构造都会读成干净的,因为它们都只看文本。一个针对合并后结果的行数探针是机械的,而且在那次重写落地的当下就能抓到。

⛔ 这不该靠警觉,该靠门禁。我另立卡。

其余

CLAUDE.md 不镜像、objectstack-platform 已拥有该 doctrine、其余十个 skill 都不放 —— 这三组裁决上一轮已接受,⛔ 本轮未重做,也不需要重做。39 个门禁族全绿;那个先报 PREREQUISITE NOT MET(exit 3)的 gate 在补构建后重跑绿,没有被记成失败 —— 这个纪律请保持。

⛔ auto-merge 我不武装:AGENTS.md 是治理面,§522 那条无条件。入队由人工决定。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants