refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
130
packages/fs/fs-observation-policy/src/index.ts
Normal file
130
packages/fs/fs-observation-policy/src/index.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Event-only filesystem observation policy; it registers no service. A weak owner/target map
|
||||
* records every authoritative presence/absence observation, single-slot intent listeners derive
|
||||
* guards from that state, and the provider performs the atomic freshness/no-clobber check. Without
|
||||
* this plugin, tools retain the bare provider's unconditional mutation behavior. See the package
|
||||
* README for composition rules.
|
||||
* @module @deepseek-ai/dsh-fs-observation-policy
|
||||
*/
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import { FsError } from '@deepseek-ai/dsh-fs'
|
||||
import type { FsObservation, FsTarget, FsVersion, FsWriteIntent } from '@deepseek-ai/dsh-fs'
|
||||
import type { FsObservationActor } from './types.ts'
|
||||
|
||||
export type { FsObservationActor } from './types.ts'
|
||||
|
||||
/**
|
||||
* Per-context observed-file state and the three `fs/*` decisions over it. One
|
||||
* instance is created per `apply()` so disposal can drop all state for HMR.
|
||||
*/
|
||||
class ObservedStateGate {
|
||||
/**
|
||||
* Observed-file state, keyed first by the owner object (weakly held, so a
|
||||
* collected session frees its state), then by {@link FsTarget.targetKey}. An
|
||||
* entry's presence is the prior-observation record; its discriminant keeps
|
||||
* confirmed absence distinct from an unseen target.
|
||||
*/
|
||||
private observed = new WeakMap<object, Map<string, FsObservation>>()
|
||||
|
||||
/**
|
||||
* Derive the observed-state owner from the opaque event actor — normally the
|
||||
* active agent session. `undefined` when no owner can be derived (e.g. a
|
||||
* direct tool call with no agent); such calls read freely but cannot satisfy
|
||||
* the write/edit prior-observation policy.
|
||||
*/
|
||||
private owner(actor: object | undefined): object | undefined {
|
||||
// tsgolint treats object as assignable to weak FsObservationActor, while tsc still requires the structural cast for property access.
|
||||
// See the analyzer-divergence consequence in .agents/notes/implemented/process/2026-07-29-oxlint-linter.md.
|
||||
// oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- The analyzers disagree on this weak type.
|
||||
return (actor as FsObservationActor | undefined)?.agent?.session
|
||||
}
|
||||
|
||||
private get(owner: object, targetKey: string): FsObservation | undefined {
|
||||
return this.observed.get(owner)?.get(targetKey)
|
||||
}
|
||||
|
||||
private set(owner: object, targetKey: string, observation: FsObservation): void {
|
||||
let byTarget = this.observed.get(owner)
|
||||
if (!byTarget) {
|
||||
byTarget = new Map()
|
||||
this.observed.set(owner, byTarget)
|
||||
}
|
||||
byTarget.set(targetKey, observation)
|
||||
}
|
||||
|
||||
/** Drop all recorded state (HMR safety / disposal). */
|
||||
clear(): void {
|
||||
this.observed = new WeakMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide the write intent: unseen or confirmed absent ⇒ `createIfAbsent`;
|
||||
* confirmed present ⇒ `replaceIfVersion` at the observed version.
|
||||
*/
|
||||
writeIntent(target: FsTarget, actor: object | undefined): FsWriteIntent {
|
||||
const owner = this.owner(actor)
|
||||
const prior = owner ? this.get(owner, target.targetKey) : undefined
|
||||
return prior?.kind === 'present'
|
||||
? { kind: 'replaceIfVersion', version: prior.version }
|
||||
: { kind: 'createIfAbsent' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide the edit version guard: unseen rejects with `FS_NOT_OBSERVED`,
|
||||
* confirmed absence rejects with `FS_NOT_FOUND`, and presence supplies the
|
||||
* observed version as the CAS basis.
|
||||
*/
|
||||
editIntent(target: FsTarget, actor: object | undefined): { version: FsVersion } {
|
||||
const owner = this.owner(actor)
|
||||
const prior = owner ? this.get(owner, target.targetKey) : undefined
|
||||
if (!owner || prior === undefined) {
|
||||
throw new FsError(`edit requires reading "${target.displayPath}" first`, 'FS_NOT_OBSERVED')
|
||||
}
|
||||
if (prior.kind === 'absent') {
|
||||
throw new FsError(`cannot edit "${target.displayPath}": not found`, 'FS_NOT_FOUND')
|
||||
}
|
||||
return { version: prior.version }
|
||||
}
|
||||
|
||||
/** Record an authoritative present or absent observation for this owner and target. */
|
||||
observe(target: FsTarget, observation: FsObservation, actor: object | undefined): void {
|
||||
const owner = this.owner(actor)
|
||||
if (owner) this.set(owner, target.targetKey, observation)
|
||||
}
|
||||
}
|
||||
|
||||
/** Cordis plugin name used by loader diagnostics. */
|
||||
export const name = 'fs-observation-policy'
|
||||
|
||||
/**
|
||||
* Register the three `fs/*` listeners. No `inject` — this plugin reads no
|
||||
* services; it operates only on its own `WeakMap`. The waterfalls are unbound
|
||||
* (the tool dispatches them with no `this`), so the listeners take the raw
|
||||
* `(target, actor, next)` arguments.
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
const gate = new ObservedStateGate()
|
||||
|
||||
ctx.effect(() => () => {
|
||||
// Drop all recorded state on disposal so a reloaded plugin starts clean
|
||||
// (HMR safety). The WeakMap itself would be GC'd, but replacing it makes the
|
||||
// release observable and immediate for tests.
|
||||
gate.clear()
|
||||
}, 'fs-observation-policy observed-state teardown')
|
||||
|
||||
// fs/write-intent: occupy the single decision slot — do NOT call next().
|
||||
// Deferred through Promise.resolve().then so the declared Promise return type
|
||||
// holds (a throw rejects, never escapes synchronously through the waterfall).
|
||||
ctx.on('fs/write-intent', (target, actor) => Promise.resolve().then(() => gate.writeIntent(target, actor)))
|
||||
|
||||
// fs/edit-intent: occupy the single decision slot — do not call next().
|
||||
ctx.on('fs/edit-intent', (target, actor) => Promise.resolve().then(() => gate.editIntent(target, actor)))
|
||||
|
||||
// fs/observed must remain synchronous and non-throwing: emit does not await
|
||||
// promises, and successful mutations have already committed. WeakMap.set
|
||||
// satisfies that contract for both presence and absence.
|
||||
ctx.on('fs/observed', (target, observation, actor) => {
|
||||
gate.observe(target, observation, actor)
|
||||
})
|
||||
}
|
||||
30
packages/fs/fs-observation-policy/src/invariant.ts
Normal file
30
packages/fs/fs-observation-policy/src/invariant.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-fs-observation-policy`.
|
||||
* @module @deepseek-ai/dsh-fs-observation-policy/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-fs-observation-policy'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'fs-observation-policy-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
||||
* beyond contracts enforced at its owning seam.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns the installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
29
packages/fs/fs-observation-policy/src/types.ts
Normal file
29
packages/fs/fs-observation-policy/src/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Vocabulary for the fs-observation-policy plugin: the minimal execution-context
|
||||
* fields used to derive an observed-state owner by narrowing the opaque `object`
|
||||
* actor the `fs/*` events carry.
|
||||
*
|
||||
* The provider vocabulary (`FsTarget`, `FsVersion`, write/edit request types) is
|
||||
* re-used from `@deepseek-ai/dsh-fs`; this package owns only the observed-state
|
||||
* owner structure on top of it.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-fs-observation-policy/types
|
||||
*/
|
||||
|
||||
/**
|
||||
* Minimal structural view of a tool execution the policy plugin needs to derive
|
||||
* an observed-state owner. `@deepseek-ai/dsh-tools`' `ToolExecution` contains
|
||||
* these fields, so the tool passes its `exec` straight through as the opaque
|
||||
* `object` actor on the `fs/*` events; this plugin narrows that actor to
|
||||
* `FsObservationActor` without importing `dsh-tools`, `dsh-agent`, or `dsh-session`.
|
||||
*
|
||||
* The owner is `agent.session` when present. It is treated as an opaque object
|
||||
* identity (a `WeakMap` key); this package never reads any of its fields.
|
||||
*/
|
||||
export interface FsObservationActor {
|
||||
/** The agent on whose behalf the call runs, when there is one. */
|
||||
agent?: {
|
||||
/** The session that owns observed-file state, used as an opaque key. */
|
||||
session?: object
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user