Ver código fonte

docs: omit model experience for generic packages

Tianyi Cui 2 meses atrás
pai
commit
89d8d56785

+ 1 - 1
docs/cookbook/adding-a-package.md

@@ -66,7 +66,7 @@ Stable system-prompt prose of any length, or another long non-generated literal,
 
 Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use one H3 block per context surface; each block has the exact bold-led `What the model sees` and `Token effect` fields shown above. A structured section grounds at least one surface in concrete model-visible text through inline code, a nested `markdown` block, or an anchored tool-catalog link. Put every stable system-prompt paragraph, including a one-liner, in a titled H4 plus `markdown` fence immediately after those fields inside the owning H3 whose title contains `system prompt`; never leave prompt prose in inline code. Quote other short stable model-visible source literals inline, using named placeholders such as `<mode>` only for interpolated values, and attach other long non-generated literals to their owning H3 in the same H4-plus-fence form. Describe an attached literal as the text "below" instead of linking between Model Experience subsections; the physical nesting already records ownership. A tool-schema surface uses `schema` in its H3 and links the relevant anchored package section in the generated [tool schema catalog](../tool-catalog.md) instead of copying its default descriptions or JSON Schema; describe only configuration or composition deltas the catalog does not contain. A runtime-only definition outside the catalog's stated scope links that scope and explains the exception before reproducing its stable text. Summarize data-dependent payloads or provider-owned text by identifying their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction.
 
-An audited package with no context effect or one simple consumer-owned path belongs in [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and uses one line beginning `None, as ` or `Indirectly, through `. If a tiny package's lack of runtime and model behavior is already self-evident from its package contract, it may instead join the narrow `BARE_NONE_MODEL_EXPERIENCE` allowlist and use exactly `None.`; the allowlist retains the audit reason so the README need not repeat it. Pure routing, transport, and keyless test-support packages otherwise use `None, as ` when they create no model-bound content even if consumers use them during composition. A provider backend whose single context path is formatted and inserted entirely by a named consumer uses `Indirectly, through ` even when it caps or filters data before returning it; a wiring bundle whose model effects all belong to named children uses the same form. Do not give these packages a structured block describing another package's work. Packages that own model input, output shaping, multiple context paths, or an auxiliary request keep context-surface blocks; the verifier gates their H3 headings, field labels, spacing, concrete literal evidence, nested H4-plus-`markdown` blocks, absence of local subsection links, system-prompt literals, and schema-surface-to-catalog links. A package with genuinely no limitations joins the separate allowlist in [`verify-package-readme-limitations.ts`](../../scripts/verify-package-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale.
+An audited package with no context effect or one simple consumer-owned path belongs in [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and uses one line beginning `None, as ` or `Indirectly, through `. A generic package whose public contract is model-agnostic may instead join the narrow `NO_MODEL_EXPERIENCE_SECTION` allowlist and omit the heading entirely; the allowlist retains the audit reason so absence cannot mean forgotten documentation. Pure transport and keyless test-support packages otherwise use `None, as ` when they create no model-bound content even if consumers use them during composition. A provider backend whose single context path is formatted and inserted entirely by a named consumer uses `Indirectly, through ` even when it caps or filters data before returning it; so does a wiring bundle whose model effects all belong to named children. Do not give these packages a structured block describing another package's work. Packages that own model input, output shaping, multiple context paths, or an auxiliary request keep context-surface blocks; the verifier gates their H3 headings, field labels, spacing, concrete literal evidence, nested H4-plus-`markdown` blocks, absence of local subsection links, system-prompt literals, and schema-surface-to-catalog links. A package with genuinely no limitations joins the separate allowlist in [`verify-package-readme-limitations.ts`](../../scripts/verify-package-readme-limitations.ts); the two omission allowlists are independent. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale.
 
 ## 5. Verify
 

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md


+ 0 - 4
packages/core/scope/README.md

@@ -19,10 +19,6 @@ Ownership and visibility derive from ONE fact — which context a registration w
 
 Handing out a scoped context hands out the minting plugin's service-resolution surface (resolution walks the minting fiber's dependency chain, not the holder's) — mint it from the plugin whose dependencies the scoped registrations need to resolve.
 
-## Model Experience
-
-None, as this scoping primitive emits no model-bound text, schema, or message; it only routes registrations and restrictions owned by other plugins.
-
 ## Known Limitations and Deferred Work
 
 - **Only scope-aware surfaces isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context.

+ 0 - 4
packages/util/brand/README.md

@@ -24,7 +24,3 @@ Construction goes through the per-id factory in the OWNING package (a plain cast
 A package brands the ids it OWNS — `CallId` in `dsh-llm` (tool-call correlation), `SessionId` in `dsh-session`, `AgentId` in `dsh-agent`, `BashTaskId`/`OwnerToken` in `dsh-bash`. Branding is for ids that cross package boundaries and could plausibly be confused; **not every string needs a brand.**
 
 This package owns ONLY the primitive — no concrete id, no runtime code beyond the (erased) type. Keeping the primitive dependency-free is the point: a capability package can brand its ids without depending on an unrelated package. `dsh-bash`, for example, brands `BashTaskId`/`OwnerToken` by depending on `dsh-brand` alone — it never pulls in `dsh-llm` (or `dsh-session`) just to reach `Branded`.
-
-## Model Experience
-
-None.

+ 23 - 26
scripts/verify-package-readme-model-experience.ts

@@ -4,8 +4,8 @@
  * canonical context-surface blocks with optional nested verbatim H4 blocks.
  * Direct system-prompt surfaces must contain exact `markdown` blocks,
  * tool-schema surfaces must link generated catalog sections, local subsection
- * links are rejected, and audited allowlists use either one concise sentence
- * or the exact bare `None.` form.
+ * links are rejected, and audited package classifications either use one
+ * concise sentence or omit the section entirely.
  *
  * Run: `tsx scripts/verify-package-readme-model-experience.ts`.
  */
@@ -29,11 +29,12 @@ interface SentenceContract {
 }
 
 /**
- * Packages whose lack of model behavior is already self-evident from their
- * package contract, so repeating that contract after `None.` adds no value.
- * The reason stays here as reviewable audit evidence.
+ * Generic packages whose public contract is model-agnostic. Their READMEs omit
+ * Model Experience entirely; the reason stays here as reviewable audit evidence
+ * so an absent section cannot be mistaken for forgotten documentation.
  */
-const BARE_NONE_MODEL_EXPERIENCE: Readonly<Record<string, string>> = {
+const NO_MODEL_EXPERIENCE_SECTION: Readonly<Record<string, string>> = {
+  'packages/core/scope': 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.',
   'packages/util/brand': 'The package is a type-only primitive erased at compile time.',
 }
 
@@ -48,7 +49,6 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
   'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
   'packages/core/agent-core': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
-  'packages/core/scope': { kind: 'none', reason: 'The routing primitive emits no model-bound content.' },
   'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' },
   'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
   'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
@@ -155,22 +155,22 @@ const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort()
 const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length)))
 let structuredCount = 0
 let contextSurfaceCount = 0
-let bareNoneCount = 0
+let omittedSectionCount = 0
 let explainedNoneCount = 0
 let indirectCount = 0
 let verbatimBlockCount = 0
 let systemPromptSurfaceCount = 0
 let toolSchemaSurfaceCount = 0
 
-for (const [pkg, reason] of Object.entries(BARE_NONE_MODEL_EXPERIENCE)) {
+for (const [pkg, reason] of Object.entries(NO_MODEL_EXPERIENCE_SECTION)) {
   if (!scannedPackages.has(pkg)) {
-    failures.push({ path: `${pkg}/README.md`, message: 'bare-none allowlist entry does not name a scanned package' })
+    failures.push({ path: `${pkg}/README.md`, message: 'no-section allowlist entry does not name a scanned package' })
   }
   if (reason.trim().length === 0) {
-    failures.push({ path: `${pkg}/README.md`, message: 'bare-none allowlist entry must retain its audit justification' })
+    failures.push({ path: `${pkg}/README.md`, message: 'no-section allowlist entry must retain its audit justification' })
   }
   if (SENTENCE_MODEL_EXPERIENCE[pkg] !== undefined) {
-    failures.push({ path: `${pkg}/README.md`, message: 'package cannot appear in both Model Experience sentence allowlists' })
+    failures.push({ path: `${pkg}/README.md`, message: 'package cannot appear in both Model Experience allowlists' })
   }
 }
 
@@ -188,7 +188,7 @@ for (const packageJson of packageJsons) {
   const readme = packageJson.replace(/package\.json$/, 'README.md')
   const abs = resolve(root, readme)
   if (!existsSync(abs)) {
-    failures.push({ path: readme, message: `missing package README; add one with ${HEADING}` })
+    failures.push({ path: readme, message: 'missing package README' })
     continue
   }
 
@@ -197,6 +197,14 @@ for (const packageJson of packageJsons) {
   const lines = markdownProseLines(text)
   const h2Headings = lines.filter(line => H2_HEADING.test(line.raw))
   const modelHeadings = h2Headings.filter(line => line.raw === HEADING)
+  if (NO_MODEL_EXPERIENCE_SECTION[pkg] !== undefined) {
+    if (modelHeadings.length !== 0) {
+      failures.push({ path: readme, message: `audited model-agnostic package must omit ${HEADING}` })
+    } else {
+      omittedSectionCount += 1
+    }
+    continue
+  }
   if (modelHeadings.length !== 1) {
     failures.push({
       path: readme,
@@ -227,17 +235,6 @@ for (const packageJson of packageJsons) {
   const nextH2Line = nextH2 < 0 ? rawLines.length + 1 : (body[nextH2] as Line).index
   const rawSection = rawLines.slice(modelHeading.index, nextH2Line - 1)
   const content = section.filter(line => line.raw.trim().length > 0)
-  const bareNoneReason = BARE_NONE_MODEL_EXPERIENCE[pkg]
-  if (bareNoneReason !== undefined) {
-    const rawContent = rawSection.filter(line => line.trim().length > 0)
-    if (content.length !== 1 || rawContent.length !== 1 || content[0]?.raw !== 'None.') {
-      failures.push({ path: readme, message: 'must contain exactly the bare sentence `None.`' })
-      continue
-    }
-    bareNoneCount += 1
-    continue
-  }
-
   const sentenceContract = SENTENCE_MODEL_EXPERIENCE[pkg]
   if (sentenceContract !== undefined) {
     const pattern = sentenceContract.kind === 'none' ? /^None, as .+\.$/ : /^Indirectly, through .+\.$/
@@ -254,7 +251,7 @@ for (const packageJson of packageJsons) {
 
   const shortSentence = content.find(line => line.raw === 'None.' || /^None, as |^Indirectly, through /.test(line.raw))
   if (shortSentence !== undefined) {
-    failures.push({ path: readme, message: `line ${shortSentence.index}: short Model Experience form requires an audited entry in BARE_NONE_MODEL_EXPERIENCE or SENTENCE_MODEL_EXPERIENCE` })
+    failures.push({ path: readme, message: `line ${shortSentence.index}: short Model Experience form requires an audited entry in SENTENCE_MODEL_EXPERIENCE` })
     continue
   }
 
@@ -373,7 +370,7 @@ for (const packageJson of packageJsons) {
 }
 
 if (failures.length === 0) {
-  console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${structuredCount} structured, ${contextSurfaceCount} context surfaces, ${systemPromptSurfaceCount} fenced system-prompt surfaces, ${toolSchemaSurfaceCount} catalog-linked tool-schema surfaces, ${bareNoneCount} bare none, ${explainedNoneCount} explained none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim markdown blocks), all conform.`)
+  console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${omittedSectionCount} audited omissions, ${structuredCount} structured, ${contextSurfaceCount} context surfaces, ${systemPromptSurfaceCount} fenced system-prompt surfaces, ${toolSchemaSurfaceCount} catalog-linked tool-schema surfaces, ${explainedNoneCount} explained none, ${indirectCount} indirect, ${verbatimBlockCount} verbatim markdown blocks), all conform.`)
   process.exit(0)
 }
 

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff