Skip to content

Commit e4916fc

Browse files
claude[bot]claude
andauthored
docs(skills): land the app-repo working principles in the published catalog (#14052)
* docs(skills): land the app-repo working principles in the published catalog Encode the maintainer's 2026-08-31 metadata-app principles on the surface the ruling names — the published `skills/` catalog — plus the PM decision-analysis recommendation-order clause they imply. - platform: a new "App / Platform Boundary" section — what an app IS, where a capability gap gets fixed, and what a platform defect obliges (wait for the fix; no workaround, no half-landing; verify the pin and re-run the repro before resuming). - ui: the section escape-hatch ladder (derive -> group reference -> hand enumeration, last) in Record Presentation, and the docs rule that a doc explains business concepts rather than hand-copying a machine inventory. The file's two worked `sections` examples now reference a declared group instead of enumerating members, so the page stops teaching the rung it demotes. - data: the invariant-vs-transition-gate choice beside `requiredWhen`, and blocking-rests-on-a-human-judgement beside the severity levels. - automation: keep a screen flow at `runAs: 'user'` and move an elevated write into a `subflow`; pin an organization predicate on a `runAs: 'system'` sweep. - pm-dispatch decision-analysis: the app-repo exception to the recommendation order, funded entirely by reflow within the file's 46-line ceiling. Token ceilings rise by 900 across five rows under the ruling quoted verbatim in the CEILINGS block; the SKILL.md subtotal pin shifts by the same amount so the id-strip's lowering claim keeps its original slack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2 * fix(scripts): keep the ratchet's SHRINK testimony adjacent to its declaration The authority block added by the previous commit sat between the `SHRINK-ONLY` doc comment and `export const CEILINGS`, pushing that testimony outside the 400-char anchor window check-ratchet-remedy-authority.mjs searches around every `CEILINGS` mention. The sweep then classified this gate `excluded` instead of `marked` — a MISCLASSIFIED failure, the farm reporting that a gate had silently left the maintainer-only convention. Measured green on origin/main and red on the branch, so the regression was this PR's. Move the block above the doc comment, restoring adjacency, and record the constraint where the next author will hit it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fe5c4e2 commit e4916fc

7 files changed

Lines changed: 172 additions & 19 deletions

File tree

.claude/skills/pm-dispatch/references/decision-analysis.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44

55
## 适用面(边界)
66

7-
- 只约束 **`needs-user-decision` 卡与决策箱讨论**的四维分析;
8-
内部工具卡(dev 派发用)的四维照旧,
9-
不强加行业类比。四条轴本身不变,轴定义与绑定句在主文件「升级与决策」。
7+
- 只约束 **`needs-user-decision` 卡与决策箱讨论**的四维分析;内部工具卡(dev 派发用)的四维照
8+
旧,不强加行业类比。四条轴本身不变,轴定义与绑定句在主文件「升级与决策」。
109
- 只适用新记录,存量分析 ⛔ 不回改(new-records-only,与四维中文化同款先例)。
1110

1211
## 常设决裁批流程(2026-08-26 裁;裁决原话与归席见主文件「升级与决策」)
@@ -34,13 +33,14 @@
3433

3534
## 四棱卡面块固定形状(落卡即带,⛔ 不留待维护者到场再补)
3635

37-
首行**机器可寻固定标记** `<!-- os-decision-facets -->`(转义拼写写入,
38-
写后回读核验存活;提取按字面文本 grep,不依赖注释形状 —— sanitizer 纪律见平台读数);
39-
四棱各一行,用中文写(语言例外见主文件全体座位的不变量)——
40-
① 项目长远合理性(缩小还是扩大特例/契约增生);② 实际业务拉动(今天谁撞上;
41-
零拉动默认 defer/remove);③ 防 AI 犯错(闭合枚举优于自由结构、响亮拒绝优于静默容忍);
42-
④ 创业阶段不扩散(remove 优于 declare-and-maintain,每个已声明的键都是永久义务);
43-
一行推荐 + 字母选项(A/B/…);**一行强制置信缺口(「本分析看不见什么」)**
44-
四棱行同受六项约束:每行论据从业务立场写,机制名词只作括号补充。
36+
首行**机器可寻固定标记** `<!-- os-decision-facets -->`(转义拼写写入,写后回读核验存活;提取按
37+
字面文本 grep,不依赖注释形状 —— sanitizer 纪律见平台读数);四棱各一行,用中文写(语言例
38+
外见主文件全体座位的不变量)—— ① 项目长远合理性(缩小还是扩大特例/契约增生);② 实
39+
际业务拉动(今天谁撞上;零拉动默认 defer/remove);③ 防 AI 犯错(闭合枚举优于自由结构、响亮
40+
拒绝优于静默容忍);④ 创业阶段不扩散(remove 优于 declare-and-maintain,每个已声明的键都是永久
41+
义务);一行推荐 + 字母选项(A/B/…);**一行强制置信缺口(「本分析看不见什么」)**。四棱行同
42+
受六项约束:每行论据从业务立场写,机制名词只作括号补充。
4543
**四棱分歧推荐序**(2026-08-27「tong y 4」):②实测拉动⇒荐①长远终态;零拉动⇒荐④不扩散;
4644
③破余下平局向响亮/结构;安全与难逆恒人工;⛔ 只排推荐,分歧照旧升级、代裁面不扩。
45+
**应用仓推荐序特例**(2026-08-31 裁「既然是平台缺陷,就应该等待平台处理」):阻塞源是平台
46+
缺陷 ⇒ 恒荐等待,⛔ 不荐绕行(形状容错/复刻平台规则)、不荐劈半落地。

scripts/check-skills-token-ratchet.mjs

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,44 @@ export const CEILING_BASIS = {
234234
bundleExtension: 'c026b0d2d',
235235
};
236236

237+
// ── The 2026-08-31 app-repo-principles raise, shared by five rows below ─────
238+
//
239+
// ⚠️ PLACEMENT IS LOAD-BEARING: this block sits ABOVE the doc comment below, not
240+
// between it and the declaration. check-ratchet-remedy-authority.mjs anchors this
241+
// gate's expanding-remedy offer by finding SHRINK testimony within 400 chars of a
242+
// `CEILINGS` mention in source, and that testimony is the "SHRINK-ONLY" line in
243+
// that doc comment. Inserting prose between the two pushed it out of the window
244+
// and flipped this gate's classification from `marked` to `excluded` — a MISCLASSIFIED
245+
// failure, i.e. the farm reporting that a gate silently left the convention.
246+
// Measured both ways while landing this raise. Keep new prose on this side.
247+
//
248+
// The maintainer ruled that day that the way a metadata app works is itself
249+
// published-skill material. Verbatim and untranslated:
250+
//
251+
// 「元数据应用就应该是在平台的规范下,基于skills 写元数据,并且使用平台提供的
252+
// os命令校验元数据的合法性,而不是重新造轮子。这个也应该进入 hotcrm 的规范,
253+
// 甚至是 objectstack 的skills」
254+
//
255+
// and, on the layout escape hatch (same day):
256+
//
257+
// 「或者说 skills 应该说明,逃生仓是极端场景按照客户需求自定义的场景下才需要,
258+
// 应该尽量避免。」
259+
//
260+
// That is the authorization this raise runs on, and it names THIS surface —
261+
// `skills/`. The app-side half of the same ruling landed first in the app repo's
262+
// own AGENTS.md; these rows are the platform half, carrying only the items that
263+
// are universal to any ObjectStack app (the app/platform boundary, the docs
264+
// discipline, the invariant-vs-transition-gate choice, blocking on a human
265+
// judgement, flow privilege, the org predicate, and the section ladder).
266+
//
267+
// PAID DOWN FIRST, then raised for the remainder. The only genuine deletion
268+
// available was in the ui row and it is recorded there; every other file's new
269+
// text is a live fact with no existing text it makes redundant, so deleting to
270+
// fund would have removed a fact with no other home. 27 tokens of existing
271+
// headroom across three rows absorb part of the growth, so the ceilings move by
272+
// 900 while the bundle grows by 927. The raising PR's body carries the same
273+
// arithmetic, per-file and summed.
274+
237275
/**
238276
* Measured counts, in the convention above. SHRINK-ONLY: lower freely, and see
239277
* the header for what the other direction costs.
@@ -258,11 +296,35 @@ export const CEILINGS = new Map([
258296
// published teaching is the only guard. +132 tokens, compressed to minimum.
259297
// Maintainer ruling 2026-08-25 (option B1, raise the ceiling), verbatim and
260298
// untranslated: 「我看到了,你分析过了,接受你的建议」.
261-
['skills/objectstack-automation/SKILL.md', 12643],
262-
['skills/objectstack-data/SKILL.md', 13783], // -34 (was 13817)
299+
// 12643 -> 12768 (2026-08-31 app-repo-principles raise, see the block above).
300+
// Two flow rules an app author gets wrong in the direction that leaks data.
301+
// The `readonly` blockquote above them says "a flow that maintains a readonly
302+
// field must run runAs:'system'" — true of the SCHEDULED flow it was written
303+
// for, and read by an agent writing a SCREEN flow it says "elevate the whole
304+
// screen flow", which elevates every other write in it. The second rule has no
305+
// prior statement anywhere in the bundle: a `runAs:'system'` sweep has no
306+
// trigger user to narrow it, so one with no organization predicate reads and
307+
// writes across every tenant. +150 tokens, of which the row's 25 tokens of
308+
// headroom absorb 25; the CEILING moves 125.
309+
['skills/objectstack-automation/SKILL.md', 12768],
310+
// 13783 -> 13892 (2026-08-31 app-repo-principles raise, see the block above).
311+
// The invariant-vs-transition-gate choice, placed in the field-conditional-rule
312+
// bullet list where the tool is actually picked. Both wrong picks are silent:
313+
// a transition gate written as a `validations[]` invariant bricks rows that
314+
// were legal when stored, and an invariant written as `requiredWhen` never
315+
// enforces itself at all. +110 tokens, 1 absorbed by headroom, ceiling +109.
316+
['skills/objectstack-data/SKILL.md', 13892],
263317
['skills/objectstack-formula/SKILL.md', 6002], // -53 (was 6055)
264318
['skills/objectstack-i18n/SKILL.md', 6338], // -11 (was 6349)
265-
['skills/objectstack-platform/SKILL.md', 12705], // -11 (was 12716)
319+
// 12705 -> 12984 (2026-08-31 app-repo-principles raise, see the block above).
320+
// The largest of the five and the frame the other four are read under: what an
321+
// app IS (metadata under the platform's spec, authored from these skills,
322+
// checked with `os`), where a capability gap gets fixed (upstream, never
323+
// compensated for locally), and what a platform defect obliges — wait for the
324+
// fix; no defensive coding, no shape tolerance, no hand-written predicate
325+
// re-implementing a platform rule, and no landing of the half that fits.
326+
// Nothing in this file said any of it. +280 tokens, 1 absorbed, ceiling +279.
327+
['skills/objectstack-platform/SKILL.md', 12984],
266328
// 14239 -> 14391: the pull-directed split-resolution order joined the decision
267329
// frame (maintainer ruling 2026-08-27, verbatim and untranslated: 「tong y 4」 —
268330
// accepting the four-rule set), and this file carries TWO enforced frame copies
@@ -286,7 +348,23 @@ export const CEILINGS = new Map([
286348
// that restated report binding a second time inside its own paragraph — +120
287349
// net, 25113 -> 25143 tokens. The row's 12 tokens of headroom absorb part of
288350
// it, so the CEILING moves 18. The raising PR's body carries the same numbers.
289-
['skills/objectstack-ui/SKILL.md', 25143],
351+
// 25143 -> 25445 (2026-08-31 app-repo-principles raise, see the block above).
352+
// Two additions, one of them the escape-hatch ruling quoted there. The section
353+
// ladder joins Record Presentation, which already tells an author to declare
354+
// `fieldGroups` + `Field.group` and let the platform lay it out but never said
355+
// what `sections` are FOR: derive (author none) -> reference a declared group
356+
// (`{ group: '…' }`) -> enumerate members by hand, last, for a named-customer
357+
// shape a reference cannot express. Corpus gravity is the whole reason the
358+
// teaching has to move: every existing tutorial enumerates, so an agent copies
359+
// enumeration unless the page says otherwise. The docs rule is the second
360+
// addition — a doc explains business concepts, and a hand-copied inventory of
361+
// objects/fields/components has no producer and goes stale.
362+
// THE ONE GENUINE DELETION in this raise is here too: the file's two worked
363+
// `sections` examples enumerated their members, i.e. taught rung 3 as the
364+
// default in the very PR that demotes it. Both now reference a declared group,
365+
// which is shorter — -13 tokens, real, and the fix the ruling asks for rather
366+
// than a payment invented to fund it. +315 gross, -13, ceiling +302.
367+
['skills/objectstack-ui/SKILL.md', 25445],
290368
['skills/objectstack-upgrade/SKILL.md', 8333], // -2 (was 8335)
291369

292370
// ── the #12392 extension: the rest of the AUTHORED bundle ────────────────
@@ -313,7 +391,13 @@ export const CEILINGS = new Map([
313391
['skills/objectstack-data/rules/lifecycle.md', 1590],
314392
['skills/objectstack-data/rules/naming.md', 773],
315393
['skills/objectstack-data/rules/relationships.md', 3778],
316-
['skills/objectstack-data/rules/validation.md', 3024],
394+
// 3024 -> 3109 (2026-08-31 app-repo-principles raise, see the block above).
395+
// Severity Levels listed the three values and left the CHOICE unstated: a
396+
// block rests on a judgement a person made, so a machine-inferred signal — a
397+
// score, a duplicate guess — warns and never errors, and an override flag
398+
// added to soften a block is evidence the block should have been a warning.
399+
// +85 tokens, no headroom to absorb any of it, ceiling +85.
400+
['skills/objectstack-data/rules/validation.md', 3109],
317401

318402
// objectstack-platform
319403
['skills/objectstack-platform/evals/README.md', 514],
@@ -735,8 +819,15 @@ function selfTest() {
735819
// 117943 -> 118095: shifted by exactly the +152 ruling-authorized raise on
736820
// the pm-dispatch row (2026-08-27 「tong y 4」, see that row), so the strip's
737821
// lowering claim keeps its original slack instead of being silently eaten.
822+
// 118095 -> 118910: same operation for the 2026-08-31 app-repo-principles
823+
// raise (see the block above the map), which lands on FOUR SKILL.md rows —
824+
// platform +279, ui +302, data +109, automation +125 = +815. The fifth row
825+
// of that raise, `objectstack-data/rules/validation.md`, is not a SKILL.md
826+
// and correctly does not shift this pin. Subtotal after: 118830, i.e. the
827+
// same 80 tokens of slack the strip's claim had before either raise — which
828+
// is the point of shifting rather than widening.
738829
['the re-measure lowered the SKILL.md subtotal',
739-
skillMdCeilings.reduce((a, [, n]) => a + n, 0) < 118095, true],
830+
skillMdCeilings.reduce((a, [, n]) => a + n, 0) < 118910, true],
740831

741832
// ── the #12392 extension basis ───────────────────────────────────────
742833
// Same reasoning as the pins above: the gate never reads this sha, so it

skills/objectstack-automation/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ variables: [
175175
> flagged as a warning.) Do **not** work around this by removing `readonly`;
176176
> that loses the field's edit protection.
177177
178+
> **Elevate the write, not the flow.** A `screen` flow stays `runAs: 'user'`.
179+
> When one step in it must write a `readonly` field, move that step into a
180+
> dedicated `runAs: 'system'` flow and call it from a `subflow` node — raising
181+
> the whole flow silently elevates every other write in it.
182+
>
183+
> **A `runAs: 'system'` sweep must pin its organization.** System context has no
184+
> trigger user, so nothing narrows the query: a scan or rollup with no
185+
> organization predicate reads and writes across every tenant. The tenant column
186+
> is platform-injected — filter on it, never re-declare it per object.
187+
178188
```typescript
179189
{
180190
name: 'escalate_overdue_cases',

skills/objectstack-data/SKILL.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ export const Invoice = ObjectSchema.create({
291291
- Use `requiredWhen` for conditional requiredness; the ObjectQL validator
292292
enforces it on submit. The `conditionalRequired` alias was REMOVED in
293293
protocol 17 — emitting it is a parse error.
294+
- **Choose by intent — invariant or transition gate.** A fact that must hold for
295+
*every stored record* is an invariant: express it in `validations[]`. A
296+
condition on a *transition* ("required once the record reaches `paid`") is
297+
`requiredWhen` / field bounds, which let already-stored rows through. A
298+
transition gate written as an invariant bricks existing data; an invariant
299+
written as a transition gate never enforces itself.
294300
- For inline `master_detail` grids, predicates are evaluated row-by-row against
295301
the child row's `record`, so line-item rules should live on child fields.
296302
- For complex predicates, load **objectstack-formula** and emit CEL via

skills/objectstack-data/rules/validation.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ severity: 'warning' // Allows save, shows warning
260260
severity: 'info' // Informational only
261261
```
262262

263+
**Blocking rests on a human judgement.** A machine-inferred signal — a score, a
264+
duplicate guess, anything the system decided — may `warning`, never `error`.
265+
Only a value a person wrote may block a save. Do not add an override flag to
266+
soften a block either: an escape hatch around a rule is evidence the rule should
267+
have been a warning.
268+
263269
### Events
264270

265271
```typescript

skills/objectstack-platform/SKILL.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ they live in one skill.
4949

5050
---
5151

52+
## The App / Platform Boundary
53+
54+
An ObjectStack app is a **simplified implementation of business features**:
55+
author metadata under the platform's spec, guided by these skills, and check it
56+
with the `os` commands ([Verify your work](#verify-your-work)). Never rebuild
57+
what the platform owns.
58+
59+
- **Business features belong in the app; capability belongs in the platform.**
60+
A missing default, a wrong diagnostic, a shape the spec refuses — the fix is
61+
upstream. Raise it there; do not compensate for it here.
62+
- **A platform defect means waiting for the platform fix.** No defensive coding,
63+
no shape tolerance, no hand-written predicate re-implementing a platform rule,
64+
and never "land the half we can" — that spends the contract-first option and
65+
leaves a decision half-executed. Record the block against the platform issue
66+
so it is machine-visible; before resuming, confirm the version you **pin**
67+
carries the fix (merged upstream ≠ present on your pin) and re-run the
68+
defect's own reproduction.
69+
- **A bad platform default is a default to fix**, not something to work around
70+
at every call site.
71+
72+
---
73+
5274
## The Template
5375

5476
`blank` is the only template `create-objectstack` offers, and it is the default:

skills/objectstack-ui/SKILL.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ custom page or form config. Prefer, in order:
9595
formViews: {
9696
default: {
9797
type: 'simple',
98-
sections: [{ label: 'Invoice', fields: ['number', 'account'] }],
98+
sections: [{ group: 'invoice_header' }], // a declared fieldGroup
9999
subforms: [
100100
{ childObject: 'invoice_line', // relationshipField + columns are
101101
title: 'Line Items', // derived from the child object;
@@ -208,7 +208,7 @@ export const CaseViews = defineView({
208208
filter: [{ field: 'status', operator: 'equals', value: 'open' }] },
209209
},
210210
formViews: {
211-
edit: { type: 'simple', data, sections: [{ label: 'Case', fields: ['subject', 'status'] }] },
211+
edit: { type: 'simple', data, sections: [{ group: 'case_detail' }] },
212212
},
213213
});
214214
```
@@ -945,6 +945,17 @@ at runtime from how heavy the record is + the client viewport, because an author
945945
via container queries — the same form is 1 column in a narrow drawer and up to 4
946946
on a wide page. Author *grouping* with `fieldGroups` + `Field.group`; the columns
947947
adapt themselves.
948+
- **`sections` are the escape hatch — reach for them last.** The ladder, in
949+
order: (1) **derive** — declare `fieldGroups` + `Field.group` and author no
950+
`sections` at all; (2) **reference** — when one surface needs a local
951+
arrangement, a section may name a declared group, `{ group: 'contact_info' }`,
952+
and inherits its members, label and presentation (restating a key the group
953+
declares is refused at parse); (3) **enumerate**`{ label, fields: [...] }`
954+
only for a named-customer requirement a group reference genuinely cannot
955+
express (a cross-group entry combination, a wizard/pane structure), with that
956+
reason in a comment beside it. A hand-enumerated section re-copies membership
957+
the object already owns and goes stale on the next field added, so rung 3 is
958+
an exception, never a default.
948959

949960
> **Rule of thumb: presentation (surface / width / columns) is not metadata.**
950961
> Write fields + semantic roles; the renderer decides the pixels. Reach for
@@ -1300,6 +1311,13 @@ src/docs/
13001311
The console rewrites `*.md``/docs/<target>` (anchors preserved);
13011312
broken same-package links fail the build.
13021313

1314+
**Write business concepts, not machine inventories.** A hand-copied table of
1315+
objects, fields or components has no producer and drifts; the self-describing
1316+
metadata is the one source. A doc answers *what is this, what business problem
1317+
does it solve, how do I use it*. Boundary: a fact the reader sees on screen
1318+
(the view list in an app's navigation) is documentable; the semantic layer
1319+
behind the screen is not.
1320+
13031321
### Routing model — platform-level viewer, opt-in entry
13041322

13051323
The viewer is **platform-level**: one global `/docs/<name>` route

0 commit comments

Comments
 (0)