weir-harness 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.capability-badge.js +2 -0
- package/lib/client.capability-manager.js +46 -179
- package/lib/client.capability-model.js +428 -3
- package/lib/client.file-lease-tool-model.js +189 -0
- package/lib/client.file-lease-tool-view.js +434 -0
- package/lib/client.js +105 -7
- package/lib/client.mcp-manager.js +563 -0
- package/package.json +1 -1
- package/skills/mcp-server-setup/SKILL.md +124 -0
- package/src/capabilities/apply-engine.js +18 -4
- package/src/capabilities/capability-remote.js +296 -45
- package/src/capabilities/initial-selection.js +3 -2
- package/src/capabilities/lifecycle-preload.js +69 -11
- package/src/capabilities/lifecycle-snapshot.js +127 -39
- package/src/capabilities/mcp-admission.js +2 -2
- package/src/capabilities/mcp-config.js +344 -0
- package/src/capabilities/mcp-credentials.js +180 -0
- package/src/capabilities/mcp-diagnostics.js +169 -0
- package/src/capabilities/mcp-drain.js +18 -4
- package/src/capabilities/mcp-facade.js +40 -9
- package/src/capabilities/mcp-gate.js +51 -52
- package/src/capabilities/mcp-install-tool.js +276 -0
- package/src/capabilities/mcp-manager.js +575 -146
- package/src/capabilities/mcp-mount.js +220 -79
- package/src/capabilities/mcp-operations.js +359 -0
- package/src/capabilities/mcp-process.js +43 -0
- package/src/capabilities/mcp-registry.js +74 -60
- package/src/capabilities/mcp-runtime.js +103 -0
- package/src/capabilities/read-payloads.js +8 -3
- package/src/capabilities/skill-applicability.js +185 -0
- package/src/capabilities/skill-builtin-migration.js +2 -1
- package/src/capabilities/skill-selection-plugin.js +170 -57
- package/src/file-lease/client.js +268 -35
- package/src/file-lease/index.js +4 -0
- package/src/file-lease/plugin.js +239 -15
- package/src/file-lease/tools.js +426 -38
- package/src/file-lease/watch.js +473 -0
- package/src/intent-gate/index.js +29 -12
- package/src/intent-gate/matcher.js +36 -2
- package/src/lease-broker/protocol.js +3 -2
- package/src/lease-broker/server.js +6 -3
- package/src/lease-broker/state.js +68 -11
- package/src/process/index.js +4 -2
- package/src/shared/audit.js +3 -0
- package/src/shared/child-scope.js +2 -0
- package/src/worktree/rules.js +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Synchronous per-identity replacement fence. The runtime phase is authoritative
|
|
2
|
+
// for admission; registry/mount joins are for inventory, never for reopening a
|
|
3
|
+
// closed replacement window early.
|
|
4
|
+
import { randomUUID } from 'node:crypto'
|
|
5
|
+
|
|
6
|
+
const OPEN = new Set(['mounted'])
|
|
7
|
+
const RESTORABLE = new Set(['mounted', 'registered', 'saved-degraded', 'unknown', 'cleanup-failed', 'cleanup-unknown'])
|
|
8
|
+
const clone = value => value == null ? value : structuredClone(value)
|
|
9
|
+
|
|
10
|
+
export function createMcpRuntime() {
|
|
11
|
+
const states = new Map()
|
|
12
|
+
|
|
13
|
+
function snapshot(identity) {
|
|
14
|
+
const state = states.get(identity)
|
|
15
|
+
return state ? clone(state) : { phase: 'unknown', generation: null, incarnation: null, operationId: null }
|
|
16
|
+
}
|
|
17
|
+
function begin(identity, { phase = 'replacing', generation = null, incarnation = null, operationId = randomUUID() } = {}) {
|
|
18
|
+
const previous = states.get(identity) ?? null
|
|
19
|
+
const token = { identity, operationId, previous }
|
|
20
|
+
// This transition is deliberately synchronous and happens before the
|
|
21
|
+
// caller's first registry/credential/probe await.
|
|
22
|
+
states.set(identity, { phase, generation, incarnation, operationId })
|
|
23
|
+
return token
|
|
24
|
+
}
|
|
25
|
+
function current(token) {
|
|
26
|
+
return states.get(token.identity)?.operationId === token.operationId
|
|
27
|
+
}
|
|
28
|
+
function finish(token, phase, details = {}) {
|
|
29
|
+
if (!current(token)) return false
|
|
30
|
+
states.set(token.identity, {
|
|
31
|
+
phase,
|
|
32
|
+
generation: details.generation ?? states.get(token.identity).generation,
|
|
33
|
+
incarnation: details.incarnation === undefined ? states.get(token.identity).incarnation : details.incarnation,
|
|
34
|
+
operationId: token.operationId,
|
|
35
|
+
})
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
function restore(token) {
|
|
39
|
+
if (!current(token)) return false
|
|
40
|
+
const previous = token.previous
|
|
41
|
+
if (previous && RESTORABLE.has(previous.phase)) states.set(token.identity, previous)
|
|
42
|
+
else states.set(token.identity, { phase: 'unknown', generation: null, incarnation: null, operationId: token.operationId })
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
function enabledFor(identity) {
|
|
46
|
+
return OPEN.has(snapshot(identity).phase)
|
|
47
|
+
}
|
|
48
|
+
function registrationFor(identity) {
|
|
49
|
+
const state = snapshot(identity)
|
|
50
|
+
return enabledFor(identity)
|
|
51
|
+
? { identity, generation: state.generation, incarnation: state.incarnation, operationId: state.operationId }
|
|
52
|
+
: null
|
|
53
|
+
}
|
|
54
|
+
function permit(identity, { generation, incarnation }) {
|
|
55
|
+
const state = snapshot(identity)
|
|
56
|
+
if (!enabledFor(identity) || state.generation !== generation || (state.incarnation ?? null) !== (incarnation ?? null)) return null
|
|
57
|
+
return { identity, generation, incarnation: incarnation ?? null, operationId: state.operationId }
|
|
58
|
+
}
|
|
59
|
+
function assertPermit(value) {
|
|
60
|
+
if (!value || typeof value !== 'object') return false
|
|
61
|
+
const state = snapshot(value.identity)
|
|
62
|
+
return enabledFor(value.identity)
|
|
63
|
+
&& state.operationId === value.operationId
|
|
64
|
+
&& state.generation === value.generation
|
|
65
|
+
&& (state.incarnation ?? null) === (value.incarnation ?? null)
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
begin, finish, restore, snapshot, enabledFor, registrationFor, permit, assertPermit,
|
|
69
|
+
beforeRead: enabledFor,
|
|
70
|
+
afterRead: enabledFor,
|
|
71
|
+
states,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Per-identity FIFO for every operation that opens a runtime fence
|
|
77
|
+
* (runtime.begin). Same-identity operations run strictly one after another
|
|
78
|
+
* (design D6), so a Restart can never begin while an edit-mode Test holds the
|
|
79
|
+
* fence and leave the server mounted but refusing calls. A task's failure never
|
|
80
|
+
* poisons the queue; different identities never wait on each other. Not
|
|
81
|
+
* reentrant: callers already inside serialize() pass their token instead.
|
|
82
|
+
*/
|
|
83
|
+
export function createMcpIdentityLock() {
|
|
84
|
+
const tails = new Map()
|
|
85
|
+
return function serialize(identity, task) {
|
|
86
|
+
const previous = tails.get(identity) ?? Promise.resolve()
|
|
87
|
+
const run = previous.then(() => task())
|
|
88
|
+
const tail = run.then(() => {}, () => {})
|
|
89
|
+
tails.set(identity, tail)
|
|
90
|
+
void tail.then(() => { if (tails.get(identity) === tail) tails.delete(identity) })
|
|
91
|
+
return run
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const fallbackLocks = new WeakMap()
|
|
96
|
+
/** The manager's own lock, or one stable lock per manager object for fakes/older faces. */
|
|
97
|
+
export function mcpIdentityLockFor(manager) {
|
|
98
|
+
if (typeof manager?.serialize === 'function') return (identity, task) => manager.serialize(identity, task)
|
|
99
|
+
if (!manager || (typeof manager !== 'object' && typeof manager !== 'function')) return createMcpIdentityLock()
|
|
100
|
+
let lock = fallbackLocks.get(manager)
|
|
101
|
+
if (!lock) { lock = createMcpIdentityLock(); fallbackLocks.set(manager, lock) }
|
|
102
|
+
return lock
|
|
103
|
+
}
|
|
@@ -50,6 +50,7 @@ export async function buildReceiptPayload(deps, options) {
|
|
|
50
50
|
skills: selected.map(candidate => candidate.name),
|
|
51
51
|
skillIdentities,
|
|
52
52
|
mcpServers,
|
|
53
|
+
...(snapshot?.mcpIncarnations && typeof snapshot.mcpIncarnations === 'object' ? { mcpIncarnations: structuredClone(snapshot.mcpIncarnations) } : {}),
|
|
53
54
|
digest: effectiveSelectionDigest(skillIdentities, mcpServers),
|
|
54
55
|
},
|
|
55
56
|
warnings: result?.complete === false || status?.error
|
|
@@ -78,7 +79,7 @@ export async function buildListPayload(deps, options) {
|
|
|
78
79
|
if (!candidate.selected || !candidate.identity) continue
|
|
79
80
|
try { selectedKeys.add(skillIdentityKey(candidate.identity)) } catch { /* an unstamped shape carries no mark */ }
|
|
80
81
|
}
|
|
81
|
-
const listing = deps.mcpManager?.list?.() ?? { managed: [], unmanaged: [] }
|
|
82
|
+
const listing = await (deps.mcpManager?.list?.() ?? Promise.resolve({ managed: [], unmanaged: [] }))
|
|
82
83
|
return {
|
|
83
84
|
skills: candidates.map(candidate => {
|
|
84
85
|
/** @type {string | null} */
|
|
@@ -95,9 +96,13 @@ export async function buildListPayload(deps, options) {
|
|
|
95
96
|
}
|
|
96
97
|
}),
|
|
97
98
|
mcpServers: [
|
|
98
|
-
...(listing.managed ?? []).map(server => ({
|
|
99
|
-
...(listing.unmanaged ?? []).map(server => ({
|
|
99
|
+
...(listing.managed ?? []).map(server => ({ ...server })),
|
|
100
|
+
...(listing.unmanaged ?? []).map(server => ({ ...server, state: 'unmanaged', readOnly: true })),
|
|
100
101
|
],
|
|
102
|
+
mcpWarnings: Array.isArray(listing.warnings) ? [...listing.warnings] : [],
|
|
103
|
+
mcpUnavailable: listing.unavailable ?? null,
|
|
104
|
+
mcpCapabilities: listing.capabilities ?? null,
|
|
105
|
+
mcpRegistryRevision: Number.isSafeInteger(listing.registryRevision) ? listing.registryRevision : 0,
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Cross-workspace preset applicability (fix-capability-fork-and-preset-workspaces
|
|
2
|
+
// D4): the server-side partition of Skill entries an Apply draft names but the
|
|
3
|
+
// CURRENT inventory did not resolve exactly.
|
|
4
|
+
//
|
|
5
|
+
// A project-scoped Skill identity is machine-local: its canonical root names
|
|
6
|
+
// the workspace it was discovered in. A global preset carrying such an identity
|
|
7
|
+
// therefore names an entry that does not exist in another workspace at all —
|
|
8
|
+
// the preset contract is "skip it, warn, keep the rest", never an unactionable
|
|
9
|
+
// same-name conflict with a local Skill that happens to share the invocation
|
|
10
|
+
// name, and never a silent substitution.
|
|
11
|
+
//
|
|
12
|
+
// Evidence discipline (the whole point of this module): ABSENCE IS NEVER PROOF.
|
|
13
|
+
// The current workspace's project Skill roots are derived from the inventory's
|
|
14
|
+
// own root observations (canonical `projectRoot` + the two project Skill root
|
|
15
|
+
// suffixes, plus each observed project root path). A skip is authorized only
|
|
16
|
+
// when the inventory is COMPLETE (`inventoryResult.complete === true`) and at
|
|
17
|
+
// least one current project root resolved. An incomplete inventory, a root set
|
|
18
|
+
// that cannot be resolved, and a legacy name string with no identity all keep
|
|
19
|
+
// the pre-existing behavior: profile-supplemental knowledge may still skip with
|
|
20
|
+
// its own reason, everything else stays a hard failure with the draft kept.
|
|
21
|
+
//
|
|
22
|
+
// Path comparison is by canonical exact key — never a string prefix — so a
|
|
23
|
+
// lexical alias of this very workspace (`/var` vs `/private/var`, a symlinked
|
|
24
|
+
// checkout, win32 case folding) is NOT misread as another workspace. A local
|
|
25
|
+
// identity whose root IS one of this workspace's project roots but which the
|
|
26
|
+
// inventory did not resolve stays a hard failure: it is missing, not external.
|
|
27
|
+
import { realpathSync } from 'node:fs'
|
|
28
|
+
import { basename, dirname, isAbsolute, join, sep } from 'node:path'
|
|
29
|
+
|
|
30
|
+
/** Reason reported for a project identity proven to belong to another workspace. */
|
|
31
|
+
export const NOT_APPLICABLE_IN_WORKSPACE = 'not applicable in this workspace'
|
|
32
|
+
/** Reason reported for a name known only to a profile-supplemental preset root. */
|
|
33
|
+
export const NOT_APPLICABLE_IN_PRESET = 'not applicable in this agent preset'
|
|
34
|
+
|
|
35
|
+
/** The two project Skill discovery roots of one workspace, relative to its project root. */
|
|
36
|
+
const PROJECT_SKILL_SUFFIXES = Object.freeze(['.dsh/skills', '.agents/skills'])
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Comparable form of a path: realpath when it exists, '/'-separated, and
|
|
40
|
+
* case-folded on win32. A path that does not exist yet keeps its lexical form
|
|
41
|
+
* with the deepest EXISTING ancestor canonicalized, so the macOS `/var` →
|
|
42
|
+
* `/private/var` alias is resolved for `<root>/.dsh/skills` even before that
|
|
43
|
+
* directory is created.
|
|
44
|
+
* (Same contract as the worktree lane's `pathKey`; kept local so the capability
|
|
45
|
+
* modules never depend on the worktree feature's internals.)
|
|
46
|
+
* @param {unknown} path
|
|
47
|
+
* @param {string} [platform]
|
|
48
|
+
* @returns {string | null} canonical key, or null when the path cannot be resolved safely
|
|
49
|
+
*/
|
|
50
|
+
export function canonicalPathKey(path, platform = process.platform) {
|
|
51
|
+
const lexical = String(path)
|
|
52
|
+
let real = lexical
|
|
53
|
+
const missing = error => error?.code === 'ENOENT' || error?.code === 'ENOTDIR'
|
|
54
|
+
try {
|
|
55
|
+
real = realpathSync.native(lexical)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Only a genuinely absent path may be reconstructed through its deepest
|
|
58
|
+
// existing ancestor. EINVAL/EACCES/ELOOP/etc. prove nothing and must not
|
|
59
|
+
// become a lexical key that could authorize a cross-workspace skip.
|
|
60
|
+
if (!missing(error)) return null
|
|
61
|
+
let parent = dirname(lexical)
|
|
62
|
+
const tail = [basename(lexical)]
|
|
63
|
+
while (parent !== dirname(parent)) {
|
|
64
|
+
try {
|
|
65
|
+
real = join(realpathSync.native(parent), ...tail.reverse())
|
|
66
|
+
break
|
|
67
|
+
} catch (ancestorError) {
|
|
68
|
+
if (!missing(ancestorError)) return null
|
|
69
|
+
tail.push(basename(parent))
|
|
70
|
+
parent = dirname(parent)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const slashed = real.split(sep).join('/').replace(/\/+$/, '')
|
|
75
|
+
return platform === 'win32' ? slashed.toLowerCase() : slashed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The canonical project Skill roots of the workspace the inventory observed.
|
|
80
|
+
* Empty means "this inventory proves nothing about the current workspace";
|
|
81
|
+
* callers must treat that as NO skip authorization, never as a foreign verdict.
|
|
82
|
+
* @param {unknown} roots inventory root observations (`inventoryResult.roots`)
|
|
83
|
+
* @param {{ platform?: string }} [options]
|
|
84
|
+
* @returns {Set<string>}
|
|
85
|
+
*/
|
|
86
|
+
export function workspaceProjectSkillRoots(roots, options = {}) {
|
|
87
|
+
const platform = options.platform ?? process.platform
|
|
88
|
+
const keys = new Set()
|
|
89
|
+
for (const observation of Array.isArray(roots) ? roots : []) {
|
|
90
|
+
const root = /** @type {Record<string, unknown> | undefined} */ (
|
|
91
|
+
observation && typeof observation === 'object' ? (/** @type {any} */ (observation).root ?? observation) : undefined)
|
|
92
|
+
if (!root || root.scope !== 'project') continue
|
|
93
|
+
if (typeof root.projectRoot === 'string' && root.projectRoot.length > 0) {
|
|
94
|
+
const projectRoot = canonicalPathKey(root.projectRoot, platform)
|
|
95
|
+
if (projectRoot !== null) {
|
|
96
|
+
for (const suffix of PROJECT_SKILL_SUFFIXES) {
|
|
97
|
+
const key = canonicalPathKey(join(projectRoot, suffix), platform)
|
|
98
|
+
if (key !== null) keys.add(key)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// The observed root itself is canonicalized by discovery (a symlinked
|
|
103
|
+
// project Skill root keeps ITS OWN identity), so it names this workspace
|
|
104
|
+
// too: a broken local Skill must fail as missing, never read as external.
|
|
105
|
+
if (typeof root.path === 'string' && root.path.length > 0) {
|
|
106
|
+
const key = canonicalPathKey(root.path, platform)
|
|
107
|
+
if (key !== null) keys.add(key)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return keys
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Is this identity a project identity rooted OUTSIDE every current project root?
|
|
115
|
+
* The caller must have authorized the classification (complete inventory and a
|
|
116
|
+
* non-empty project root set) — this predicate only answers the path question.
|
|
117
|
+
* @param {unknown} identity
|
|
118
|
+
* @param {Set<string>} projectRoots
|
|
119
|
+
* @param {{ platform?: string }} [options]
|
|
120
|
+
* @returns {boolean}
|
|
121
|
+
*/
|
|
122
|
+
export function isForeignProjectIdentity(identity, projectRoots, options = {}) {
|
|
123
|
+
const value = /** @type {Record<string, unknown> | null} */ (
|
|
124
|
+
identity && typeof identity === 'object' ? identity : null)
|
|
125
|
+
if (!value || value.scope !== 'project') return false
|
|
126
|
+
if (typeof value.root !== 'string' || value.root.length === 0 || !isAbsolute(value.root)) return false
|
|
127
|
+
const key = canonicalPathKey(value.root, options.platform ?? process.platform)
|
|
128
|
+
return key !== null && !projectRoots.has(key)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Partition the draft Skill entries the CURRENT inventory did not resolve
|
|
133
|
+
* exactly. Order follows the draft; duplicates are preserved (the request is
|
|
134
|
+
* reported as received, never silently deduplicated).
|
|
135
|
+
*
|
|
136
|
+
* - an identity proven to be a project identity of another workspace →
|
|
137
|
+
* `skipped` with `not applicable in this workspace`
|
|
138
|
+
* - a legacy name or identity name known to a profile-supplemental preset root →
|
|
139
|
+
* `not applicable in this agent preset` (the pre-existing contract)
|
|
140
|
+
* - everything else → `unknown` (the caller keeps the hard-failure contract and
|
|
141
|
+
* preserves the draft)
|
|
142
|
+
* @param {unknown[]} entries unresolved draft entries (identity objects or name strings)
|
|
143
|
+
* @param {{
|
|
144
|
+
* roots?: unknown,
|
|
145
|
+
* complete?: unknown,
|
|
146
|
+
* knownSupplemental?: (name: string) => boolean,
|
|
147
|
+
* platform?: string,
|
|
148
|
+
* }} [options]
|
|
149
|
+
* @returns {{ skipped: Array<{ name: string, reason: string }>, unknown: string[] }}
|
|
150
|
+
*/
|
|
151
|
+
export function partitionUnresolvedSkills(entries, options = {}) {
|
|
152
|
+
const knownSupplemental = typeof options.knownSupplemental === 'function' ? options.knownSupplemental : () => false
|
|
153
|
+
// Evidence gate: an incomplete inventory, or a current workspace whose project
|
|
154
|
+
// roots did not resolve, authorizes no cross-workspace skip at all.
|
|
155
|
+
const projectRoots = options.complete === true
|
|
156
|
+
? workspaceProjectSkillRoots(options.roots, options)
|
|
157
|
+
: new Set()
|
|
158
|
+
const canProveForeign = projectRoots.size > 0
|
|
159
|
+
const skipped = []
|
|
160
|
+
const unknown = []
|
|
161
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
162
|
+
if (typeof entry === 'string') {
|
|
163
|
+
if (knownSupplemental(entry)) skipped.push({ name: entry, reason: NOT_APPLICABLE_IN_PRESET })
|
|
164
|
+
else unknown.push(entry)
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
const identity = /** @type {Record<string, unknown> | null} */ (
|
|
168
|
+
entry && typeof entry === 'object' ? entry : null)
|
|
169
|
+
if (!identity) {
|
|
170
|
+
unknown.push(String(entry))
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
const name = typeof identity.name === 'string' && identity.name.length > 0 ? identity.name : String(entry)
|
|
174
|
+
if (knownSupplemental(name)) {
|
|
175
|
+
skipped.push({ name, reason: NOT_APPLICABLE_IN_PRESET })
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
if (canProveForeign && isForeignProjectIdentity(identity, projectRoots, options)) {
|
|
179
|
+
skipped.push({ name, reason: NOT_APPLICABLE_IN_WORKSPACE })
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
unknown.push(name)
|
|
183
|
+
}
|
|
184
|
+
return { skipped, unknown }
|
|
185
|
+
}
|
|
@@ -14,6 +14,7 @@ export const WEIR_BUILTIN_SKILLS = Object.freeze([
|
|
|
14
14
|
'debugging',
|
|
15
15
|
'deep-work',
|
|
16
16
|
'git-master',
|
|
17
|
+
'mcp-server-setup',
|
|
17
18
|
'programming',
|
|
18
19
|
'refactor',
|
|
19
20
|
'remove-ai-slops',
|
|
@@ -33,7 +34,7 @@ export const MIGRATION_FAILURE = 'Weir builtin skill migration check failed'
|
|
|
33
34
|
/**
|
|
34
35
|
* Verify a discovery snapshot against the pre-migration contract: exactly one
|
|
35
36
|
* readable weir-builtin root whose parsed candidates are exactly the
|
|
36
|
-
* contracted
|
|
37
|
+
* contracted catalog, each carrying the custom source label and rank 300.
|
|
37
38
|
* @param {{ roots?: Array<{ root: Record<string, unknown>, complete: boolean, error?: string }>,
|
|
38
39
|
* candidates?: Array<Record<string, any>> }} snapshot
|
|
39
40
|
* @returns {{ ok: true } | { ok: false, reason: string }}
|