Jelajahi Sumber

fix: address concrete terminology review findings

Turtle 1 Minggu lalu
induk
melakukan
2b05a43eca

+ 1 - 1
AGENTS.md

@@ -119,7 +119,7 @@ Real-API tests and demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`,
 - **Trust TypeScript at typed same-process boundaries.** Do not add runtime validation, fallback behavior, or hostile-input tests solely for values the static interface requires; validate at parser/config, queued, model/tool JSON, durable/file, worker, process, and wire boundaries.
 - **Trust TypeScript at typed same-process boundaries.** Do not add runtime validation, fallback behavior, or hostile-input tests solely for values the static interface requires; validate at parser/config, queued, model/tool JSON, durable/file, worker, process, and wire boundaries.
 - **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)).
 - **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)).
 - **Keep compiler faces explicit.** A package with both Host and Client programs exposes face-specific leaf configs and a solution-only root; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)).
 - **Keep compiler faces explicit.** A package with both Host and Client programs exposes face-specific leaf configs and a solution-only root; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)).
-- **An empty `catch` names the swallowed error** and why; keep its `try` to one statement.
+- **An empty `catch` names the error** and why; keep its `try` to one statement.
 - **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)).
 - **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)).
 - **Ban `prove` + `nance`** ([rule](.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md)).
 - **Ban `prove` + `nance`** ([rule](.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md)).
 - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction.
 - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction.

+ 1 - 1
packages/client/ui-conversation/src/client/contract/records.ts

@@ -73,7 +73,7 @@ export interface AssistantMessageNode {
   step: number
   step: number
   blocks: readonly AssistantBlock[]
   blocks: readonly AssistantBlock[]
   usage?: unknown
   usage?: unknown
-  providerInfo?: AssistantProviderMetadataView
+  providerMetadata?: AssistantProviderMetadataView
   requestConfig?: AssistantRequestConfig
   requestConfig?: AssistantRequestConfig
   /** Timing derived from the recorded step/chunk/message event sequence. */
   /** Timing derived from the recorded step/chunk/message event sequence. */
   timing?: AssistantTiming
   timing?: AssistantTiming

+ 1 - 1
packages/client/ui-conversation/src/client/contract/request-inspection.ts

@@ -133,7 +133,7 @@ interface RequestViewBase {
   error?: string
   error?: string
   /** Stable provider code for localized presentation of known failures. */
   /** Stable provider code for localized presentation of known failures. */
   errorCode?: string
   errorCode?: string
-  providerInfo?: AssistantProviderMetadataView
+  providerMetadata?: AssistantProviderMetadataView
   requestConfig?: AssistantRequestConfig
   requestConfig?: AssistantRequestConfig
   usage?: unknown
   usage?: unknown
   /** Assistant message or compaction summary sequence produced by this request. */
   /** Assistant message or compaction summary sequence produced by this request. */

+ 6 - 6
packages/client/ui-trajectory/src/client/TrajectoryView.tsx

@@ -250,8 +250,8 @@ export function TrajectoryView({
         const turn = request?.turn ?? node?.turn
         const turn = request?.turn ?? node?.turn
         const step = request?.step ?? node?.step
         const step = request?.step ?? node?.step
         if (turn === undefined || step === undefined) continue
         if (turn === undefined || step === undefined) continue
-        const provider = request?.providerInfo?.provider ?? node?.providerInfo?.provider
-        const model = request?.providerInfo?.model ?? node?.providerInfo?.model
+        const provider = request?.providerMetadata?.provider ?? node?.providerMetadata?.provider
+        const model = request?.providerMetadata?.model ?? node?.providerMetadata?.model
         const requestConfig = request?.requestConfig ?? node?.requestConfig
         const requestConfig = request?.requestConfig ?? node?.requestConfig
         numbered.push({
         numbered.push({
           seq: entry.seq,
           seq: entry.seq,
@@ -292,12 +292,12 @@ export function TrajectoryView({
         ...(request.error === undefined ? {} : { error: request.error }),
         ...(request.error === undefined ? {} : { error: request.error }),
         ...(request.errorCode === undefined ? {} : { errorCode: request.errorCode }),
         ...(request.errorCode === undefined ? {} : { errorCode: request.errorCode }),
         resultSeq: request.startSeq,
         resultSeq: request.startSeq,
-        ...(request.providerInfo?.provider === undefined
+        ...(request.providerMetadata?.provider === undefined
           ? {}
           ? {}
-          : { provider: request.providerInfo.provider }),
-        ...(request.providerInfo?.model === undefined
+          : { provider: request.providerMetadata.provider }),
+        ...(request.providerMetadata?.model === undefined
           ? {}
           ? {}
-          : { model: request.providerInfo.model }),
+          : { model: request.providerMetadata.model }),
         ...(request.requestConfig === undefined ? {} : { requestConfig: request.requestConfig }),
         ...(request.requestConfig === undefined ? {} : { requestConfig: request.requestConfig }),
         ...(usage === undefined ? {} : { usage }),
         ...(usage === undefined ? {} : { usage }),
         ...(cumulativeUsage === undefined ? {} : { cumulativeUsage }),
         ...(cumulativeUsage === undefined ? {} : { cumulativeUsage }),

+ 2 - 2
packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts

@@ -252,7 +252,7 @@ function finalNode(
       step: state.step,
       step: state.step,
       blocks: toAssistantBlocks(event.data.message.content),
       blocks: toAssistantBlocks(event.data.message.content),
       usage: event.data.usage,
       usage: event.data.usage,
-      providerInfo: {
+      providerMetadata: {
         provider: event.data.message.source.provider,
         provider: event.data.message.source.provider,
         model: event.data.message.source.model,
         model: event.data.message.source.model,
       },
       },
@@ -309,7 +309,7 @@ function assistantRequest(
       ? {}
       ? {}
       : {
       : {
         resultSeq: node.seq,
         resultSeq: node.seq,
-        ...(node.providerInfo === undefined ? {} : { providerInfo: node.providerInfo }),
+        ...(node.providerMetadata === undefined ? {} : { providerMetadata: node.providerMetadata }),
       }),
       }),
     ...(state.usage === undefined ? {} : { usage: state.usage }),
     ...(state.usage === undefined ? {} : { usage: state.usage }),
   }
   }

+ 1 - 1
packages/client/ui-trajectory/src/client/trajectory-compaction-definition.ts

@@ -64,7 +64,7 @@ function requestFromState(
         resultSeq: summary.seq,
         resultSeq: summary.seq,
         summary: summary.data.summary,
         summary: summary.data.summary,
         ...(summary.data.rawOutput === undefined ? {} : { rawOutput: summary.data.rawOutput }),
         ...(summary.data.rawOutput === undefined ? {} : { rawOutput: summary.data.rawOutput }),
-        providerInfo: { provider: summary.data.provider, model: summary.data.model },
+        providerMetadata: { provider: summary.data.provider, model: summary.data.model },
         requestConfig: {
         requestConfig: {
           provider: summary.data.provider,
           provider: summary.data.provider,
           model: summary.data.model,
           model: summary.data.model,

+ 1 - 1
packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts

@@ -397,7 +397,7 @@ describe('Trajectory conversation Definitions', () => {
       purpose: 'assistant',
       purpose: 'assistant',
       resultSeq: 3,
       resultSeq: 3,
       status: 'error',
       status: 'error',
-      providerInfo: { provider: 'test', model: 'test' },
+      providerMetadata: { provider: 'test', model: 'test' },
     }])
     }])
   })
   })
 
 

+ 25 - 1
scripts/verify-concrete-terms.spec.ts

@@ -1,5 +1,8 @@
+import { mkdtempSync, rmSync, symlinkSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
 import { describe, expect, it } from 'vitest'
 import { describe, expect, it } from 'vitest'
-import { findConcreteTermViolations } from './verify-concrete-terms.ts'
+import { findConcreteTermViolations, readTrackedSource } from './verify-concrete-terms.ts'
 
 
 const blockedTerm = 'prove' + 'nance'
 const blockedTerm = 'prove' + 'nance'
 
 
@@ -21,6 +24,27 @@ describe('concrete terminology policy', () => {
       .toEqual([{ file: 'packages/example/src/source.ts', line: 1 }])
       .toEqual([{ file: 'packages/example/src/source.ts', line: 1 }])
   })
   })
 
 
+  it('normalizes compatibility characters before scanning', () => {
+    const fullwidthTerm = blockedTerm.split('')
+      .map(character => String.fromCodePoint(character.charCodeAt(0) + 0xfee0))
+      .join('')
+    expect(findConcreteTermViolations('packages/example/src/source.ts', fullwidthTerm))
+      .toEqual([{ file: 'packages/example/src/source.ts', line: 1 }])
+  })
+
+  it.skipIf(process.platform === 'win32')('reads the target of a dangling tracked symlink', () => {
+    const repoRoot = mkdtempSync(join(tmpdir(), 'dsh-concrete-terms-'))
+    try {
+      symlinkSync(`../${blockedTerm}-target`, join(repoRoot, 'tracked-link'))
+      expect(findConcreteTermViolations(
+        'tracked-link',
+        readTrackedSource(repoRoot, 'tracked-link') ?? '',
+      )).toEqual([{ file: 'tracked-link', line: 1 }])
+    } finally {
+      rmSync(repoRoot, { recursive: true, force: true })
+    }
+  })
+
   it('accepts exact replacement terms', () => {
   it('accepts exact replacement terms', () => {
     expect(findConcreteTermViolations(
     expect(findConcreteTermViolations(
       'packages/example/src/origin.ts',
       'packages/example/src/origin.ts',

+ 10 - 4
scripts/verify-concrete-terms.ts

@@ -1,7 +1,7 @@
 /** Reject one ambiguous origin label from maintained tracked files. */
 /** Reject one ambiguous origin label from maintained tracked files. */
 
 
 import { execFileSync } from 'node:child_process'
 import { execFileSync } from 'node:child_process'
-import { existsSync, lstatSync, readFileSync, readlinkSync } from 'node:fs'
+import { lstatSync, readFileSync, readlinkSync } from 'node:fs'
 import { resolve } from 'node:path'
 import { resolve } from 'node:path'
 import { pathToFileURL } from 'node:url'
 import { pathToFileURL } from 'node:url'
 
 
@@ -55,10 +55,16 @@ function trackedFiles(repoRoot: string): string[] {
   return files
   return files
 }
 }
 
 
-function readTrackedSource(repoRoot: string, file: string): string | undefined {
+/**
+ * Read one tracked file without following a symlink to its target.
+ * @param repoRoot - Repository root containing the tracked path.
+ * @param file - Repository-relative tracked path.
+ * @returns File text, the symlink target, or undefined when the path is absent or not a file.
+ */
+export function readTrackedSource(repoRoot: string, file: string): string | undefined {
   const path = resolve(repoRoot, file)
   const path = resolve(repoRoot, file)
-  if (!existsSync(path)) return undefined
-  const stat = lstatSync(path)
+  const stat = lstatSync(path, { throwIfNoEntry: false })
+  if (stat === undefined) return undefined
   if (stat.isSymbolicLink()) return readlinkSync(path)
   if (stat.isSymbolicLink()) return readlinkSync(path)
   return stat.isFile() ? readFileSync(path, 'utf8') : undefined
   return stat.isFile() ? readFileSync(path, 'utf8') : undefined
 }
 }

+ 0 - 1
vitest.config.ts

@@ -243,7 +243,6 @@ export default defineConfig({
         'packages/client/ui-chat/src/client/conversation-nodes/*',
         'packages/client/ui-chat/src/client/conversation-nodes/*',
         'packages/client/ui-chat/src/client/details/*',
         'packages/client/ui-chat/src/client/details/*',
         'packages/client/ui-chat/src/client/model/*',
         'packages/client/ui-chat/src/client/model/*',
-        'packages/client/ui-chat/src/client/contract/context-producer.ts',
         'packages/client/ui-chat/src/client/contract/snapshot.ts',
         'packages/client/ui-chat/src/client/contract/snapshot.ts',
         'packages/client/ui-chat/src/client/historical-images.ts',
         'packages/client/ui-chat/src/client/historical-images.ts',
         'packages/client/ui-primitives/src/DisclosureRow.tsx',
         'packages/client/ui-primitives/src/DisclosureRow.tsx',