@h-rig/core 0.0.6-alpha.136 → 0.0.6-alpha.137

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.
@@ -60,6 +60,37 @@ function definePlugin(meta, runtime) {
60
60
  }
61
61
  }
62
62
  }
63
+ const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
64
+ for (const capability of runtime.featureCapabilities ?? []) {
65
+ if (!declaredCapabilities.has(capability.id)) {
66
+ throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
67
+ }
68
+ }
69
+ const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
70
+ for (const panel of runtime.panels ?? []) {
71
+ const metadata = declaredPanels.get(panel.id);
72
+ if (!metadata) {
73
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
74
+ }
75
+ if (metadata.slot !== panel.slot) {
76
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
77
+ }
78
+ if (metadata.capabilityId !== panel.capabilityId) {
79
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
80
+ }
81
+ }
82
+ const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
83
+ for (const classifier of runtime.blockerClassifiers ?? []) {
84
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
85
+ throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
86
+ }
87
+ }
88
+ const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
89
+ for (const command of runtime.cliCommands ?? []) {
90
+ if (!declaredCliCommands.has(command.id)) {
91
+ throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
92
+ }
93
+ }
63
94
  return { ...validated, __runtime: runtime };
64
95
  }
65
96
  export {
@@ -4,13 +4,13 @@ export { defineConfig } from "./define-config";
4
4
  export { createPluginHost } from "./plugin-host";
5
5
  export type { PluginHost } from "./plugin-host";
6
6
  export { buildRigInitConfigSource, type RigInitConfigInput, } from "./rig-init-builder";
7
- export type { RegisteredValidator, RigPluginRuntime, RigPluginWithRuntime, TaskSourceFactoryContext, TaskSourceFactoryEntry, ValidatorContext, ValidatorResult, } from "./plugin-runtime";
7
+ export type { RegisteredValidator, RigPluginRuntime, RigPluginWithRuntime, TaskSourceFactoryContext, RuntimeCliCommand, RuntimeCliContext, TaskSourceFactoryEntry, ValidatorContext, ValidatorResult, } from "./plugin-runtime";
8
8
  export { applyEngineEvent, applyEngineEvents, applyEngineEvent as applyRigEvent, pruneQueueEntries, type ApplyStatus, type EngineEventApplyResult, type EngineEventApplyResult as RigEventApplyResult, } from "./engineReadModelReducer";
9
9
  export { pickDefaultWorkspaceId, projectRunStatusForTaskGrouping, projectTaskStatusForGrouping, projectTaskStatusWithSessions, normalizeTaskAssigneeFilter, readTaskAssigneeLogins, selectAdhocRuns, selectAdhocRunsForWorkspace, selectApprovalsForRun, selectApprovalsForWorkspace, selectGraphsForWorkspace, selectPendingApprovals, selectPrimaryWorkspace, selectQueueForWorkspace, selectRun, selectRunsByTask, selectRunsForTask, selectRunsForWorkspace, selectTask, selectTasksByStatus, selectTasksByWorkspace, selectTasksForWorkspace, selectTasksGroupedByStatus, selectTasksAssignedTo, selectTasksAssignedToMe, selectUserInputsForRun, selectUserInputsForWorkspace, selectWorkspace, selectWorkspaces, type RigTaskSessionProjection, type RigTaskStatusGroup, type RigTaskStatusGroupingInput, } from "./rigSelectors";
10
10
  export { buildTaskReferenceIndex, computeTaskBlockingDepths, computeTaskDependencyBadges, disjointScope, isTaskTerminalStatus, rankReadyTasks, readTaskBlockingDependencyRefs, readTaskDependencyRefs, readTaskMetadataStringList, readTaskScope, readTaskSourceIssueId, resolveTaskReference, selectNextReadyTaskByPriority, selectRankedReadyTasks, type RankedReadyTask, type RankedReadyTaskOptions, type ReadyTaskSelectionMode, type TaskDependencyBadge, type TaskDependencyBadgeKind, type TaskDependencyBadgeSummary, type TaskDependencyProjection, type TaskScopeInput, } from "./taskGraph";
11
11
  export { extractTaskCode, extractTaskGroupKey, stripTaskCode, } from "./taskGraphCodes";
12
12
  export { buildTaskGraphLayout, type TaskGraphEdge, type TaskGraphLane, type TaskGraphLayout, type TaskGraphNode, type TaskGraphStage, } from "./taskGraphLayout";
13
- export { PipelineUnresolvableError, resolveStagePipeline, type DroppedStageAnchor, type ProtectedStageGrant, type ResolvableStage, type ResolvedStagePipeline, type ResolveStagePipelineInput, type StageKind, type StageMutation, type StageMutationOp, type StageResolutionRecordEntry, type StageWrapper, } from "./stageResolve";
13
+ export { PipelineUnresolvableError, resolveStagePipeline, type DroppedStageAnchor, type ResolvableStage, type ResolvedStagePipeline, type ResolveStagePipelineInput, type StageKind, type StageMutation, type StageMutationOp, type StageResolutionRecordEntry, type StageWrapper, } from "./stageResolve";
14
14
  export { rankTasks, scoreTask, type RankedTask, type TaskCriticality, type TaskScoreInput, } from "./taskScore";
15
15
  export { buildDependencyGraphModel, type BuildDependencyGraphModelOptions, type DependencyEdge, type DependencyEdgeType, type DependencyGraphModel, type DependencyNode, } from "./dependencyGraph";
16
16
  export { rollupByAssignee, rollupByEpic, type AssigneeRollup, type BlockerClass, type EpicRollup, } from "./rollups";
package/dist/src/index.js CHANGED
@@ -60,6 +60,37 @@ function definePlugin(meta, runtime) {
60
60
  }
61
61
  }
62
62
  }
63
+ const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
64
+ for (const capability of runtime.featureCapabilities ?? []) {
65
+ if (!declaredCapabilities.has(capability.id)) {
66
+ throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
67
+ }
68
+ }
69
+ const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
70
+ for (const panel of runtime.panels ?? []) {
71
+ const metadata = declaredPanels.get(panel.id);
72
+ if (!metadata) {
73
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
74
+ }
75
+ if (metadata.slot !== panel.slot) {
76
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
77
+ }
78
+ if (metadata.capabilityId !== panel.capabilityId) {
79
+ throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
80
+ }
81
+ }
82
+ const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
83
+ for (const classifier of runtime.blockerClassifiers ?? []) {
84
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
85
+ throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
86
+ }
87
+ }
88
+ const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
89
+ for (const command of runtime.cliCommands ?? []) {
90
+ if (!declaredCliCommands.has(command.id)) {
91
+ throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
92
+ }
93
+ }
63
94
  return { ...validated, __runtime: runtime };
64
95
  }
65
96
  // packages/core/src/define-config.ts
@@ -176,6 +207,41 @@ function assertRuntimeMatchesMetadata(plugin) {
176
207
  }
177
208
  }
178
209
  }
210
+ const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
211
+ const runtimeCapabilities = new Map((plugin.__runtime?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
212
+ for (const capability of runtimeCapabilities.values()) {
213
+ if (!declaredCapabilities.has(capability.id)) {
214
+ throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
215
+ }
216
+ }
217
+ const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
218
+ const runtimePanels = new Map((plugin.__runtime?.panels ?? []).map((panel) => [panel.id, panel]));
219
+ for (const panel of runtimePanels.values()) {
220
+ const metadata = declaredPanels.get(panel.id);
221
+ if (!metadata) {
222
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
223
+ }
224
+ if (metadata.slot !== panel.slot) {
225
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
226
+ }
227
+ if (metadata.capabilityId !== panel.capabilityId) {
228
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
229
+ }
230
+ }
231
+ const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
232
+ const runtimeBlockerClassifiers = new Map((plugin.__runtime?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
233
+ for (const classifier of runtimeBlockerClassifiers.values()) {
234
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
235
+ throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
236
+ }
237
+ }
238
+ const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
239
+ const runtimeCliCommands = new Map((plugin.__runtime?.cliCommands ?? []).map((command) => [command.id, command]));
240
+ for (const command of runtimeCliCommands.values()) {
241
+ if (!declaredCliCommands.has(command.id)) {
242
+ throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
243
+ }
244
+ }
179
245
  }
180
246
  function createPluginHost(plugins) {
181
247
  assertUniquePluginNames(plugins);
@@ -190,10 +256,18 @@ function createPluginHost(plugins) {
190
256
  const taskFieldExtensions = [];
191
257
  const taskSources = [];
192
258
  const cliCommands = [];
259
+ const capabilities = [];
260
+ const panels = [];
261
+ const blockerClassifiers = [];
262
+ const stages = [];
193
263
  const stageMutations = [];
194
264
  const stageExecutors = {};
195
265
  const executableValidators = [];
196
266
  const executableTaskSources = [];
267
+ const executableCapabilities = [];
268
+ const executablePanels = [];
269
+ const executableBlockerClassifiers = [];
270
+ const executableCliCommands = [];
197
271
  for (const plugin of plugins) {
198
272
  const c = plugin.contributes;
199
273
  if (!c)
@@ -205,6 +279,18 @@ function createPluginHost(plugins) {
205
279
  if (plugin.__runtime?.taskSources) {
206
280
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
207
281
  }
282
+ if (plugin.__runtime?.featureCapabilities) {
283
+ executableCapabilities.push(...plugin.__runtime.featureCapabilities.map((item) => ({ item, pluginName })));
284
+ }
285
+ if (plugin.__runtime?.panels) {
286
+ executablePanels.push(...plugin.__runtime.panels.map((item) => ({ item, pluginName })));
287
+ }
288
+ if (plugin.__runtime?.blockerClassifiers) {
289
+ executableBlockerClassifiers.push(...plugin.__runtime.blockerClassifiers.map((item) => ({ item, pluginName })));
290
+ }
291
+ if (plugin.__runtime?.cliCommands) {
292
+ executableCliCommands.push(...plugin.__runtime.cliCommands.map((item) => ({ item, pluginName })));
293
+ }
208
294
  if (c.validators)
209
295
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
210
296
  if (c.hooks)
@@ -221,6 +307,14 @@ function createPluginHost(plugins) {
221
307
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
222
308
  if (c.cliCommands)
223
309
  cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
310
+ if (c.capabilities)
311
+ capabilities.push(...c.capabilities.map((item) => ({ item, pluginName })));
312
+ if (c.panels)
313
+ panels.push(...c.panels.map((item) => ({ item, pluginName })));
314
+ if (c.blockerClassifiers)
315
+ blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
316
+ if (c.stages)
317
+ stages.push(...c.stages.map((item) => ({ item, pluginName })));
224
318
  if (c.stageMutations)
225
319
  stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
226
320
  if (plugin.__runtime?.stages)
@@ -228,6 +322,10 @@ function createPluginHost(plugins) {
228
322
  }
229
323
  indexById(executableValidators, "executableValidator");
230
324
  indexById(executableTaskSources, "executableTaskSource");
325
+ indexById(executableCapabilities, "executableCapability");
326
+ indexById(executablePanels, "executablePanel");
327
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
328
+ indexById(executableCliCommands, "executableCliCommand");
231
329
  const taskSourceFactoryByKind = new Map;
232
330
  const taskSourceKindRegistrant = new Map;
233
331
  for (const { item, pluginName } of executableTaskSources) {
@@ -245,6 +343,9 @@ function createPluginHost(plugins) {
245
343
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
246
344
  const taskSourceMap = indexById(taskSources, "taskSource");
247
345
  const cliCommandMap = indexById(cliCommands, "cliCommand");
346
+ const capabilityMap = indexById(capabilities, "capability");
347
+ const panelMap = indexById(panels, "panel");
348
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
248
349
  const allValidators = validators.map((c) => c.item);
249
350
  const allHooks = hooks.map((c) => c.item);
250
351
  const allSkills = skills.map((c) => c.item);
@@ -254,8 +355,33 @@ function createPluginHost(plugins) {
254
355
  const allTaskSources = taskSources.map((c) => c.item);
255
356
  const allCliCommands = cliCommands.map((c) => c.item);
256
357
  const allStageMutations = stageMutations.map((c) => c.item);
358
+ const allStages = stages.map((c) => c.item);
359
+ const allCapabilities = capabilities.map((c) => c.item);
360
+ const allPanels = panels.map((c) => c.item);
361
+ const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
257
362
  const allExecutableValidators = executableValidators.map((c) => c.item);
258
363
  const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
364
+ const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
365
+ const allExecutablePanels = executablePanels.map((c) => c.item);
366
+ const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
367
+ const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
368
+ const executableCliCommandByName = new Map;
369
+ const registerExecutableCliCommandSelector = (selector, contribution) => {
370
+ const existing = executableCliCommandByName.get(selector);
371
+ if (existing && existing.item.id !== contribution.item.id) {
372
+ throw new Error(`duplicate executable CLI selector "${selector}" registered by command "${existing.item.id}" from plugin "${existing.pluginName}" and command "${contribution.item.id}" from plugin "${contribution.pluginName}"`);
373
+ }
374
+ executableCliCommandByName.set(selector, contribution);
375
+ };
376
+ for (const contribution of executableCliCommands) {
377
+ const command = contribution.item;
378
+ const family = command.family ?? command.id;
379
+ registerExecutableCliCommandSelector(command.id, contribution);
380
+ registerExecutableCliCommandSelector(family, contribution);
381
+ for (const alias of command.aliases ?? []) {
382
+ registerExecutableCliCommandSelector(alias, contribution);
383
+ }
384
+ }
259
385
  return {
260
386
  getValidator: (id) => validatorMap.get(id),
261
387
  getHook: (id) => hookMap.get(id),
@@ -265,6 +391,9 @@ function createPluginHost(plugins) {
265
391
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
266
392
  getTaskSource: (id) => taskSourceMap.get(id),
267
393
  getCliCommand: (id) => cliCommandMap.get(id),
394
+ getCapability: (id) => capabilityMap.get(id),
395
+ getPanel: (id) => panelMap.get(id),
396
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
268
397
  listValidators: () => allValidators,
269
398
  listHooks: () => allHooks,
270
399
  listSkills: () => allSkills,
@@ -273,32 +402,44 @@ function createPluginHost(plugins) {
273
402
  listTaskFieldExtensions: () => allTaskFieldExtensions,
274
403
  listTaskSources: () => allTaskSources,
275
404
  listCliCommands: () => allCliCommands,
405
+ listCapabilities: () => allCapabilities,
406
+ listPanels: () => allPanels,
407
+ listBlockerClassifiers: () => allBlockerClassifiers,
408
+ listStages: () => allStages,
276
409
  listStageMutations: () => allStageMutations,
277
410
  listStageExecutors: () => stageExecutors,
278
411
  listExecutableValidators: () => allExecutableValidators,
279
412
  listExecutableTaskSources: () => allExecutableTaskSources,
413
+ listExecutableCapabilities: () => allExecutableCapabilities,
414
+ listExecutablePanels: () => allExecutablePanels,
415
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
416
+ listExecutableCliCommands: () => allExecutableCliCommands,
417
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
280
418
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
281
419
  };
282
420
  }
283
421
  // packages/core/src/rig-init-builder.ts
284
422
  function buildRigInitConfigSource(input) {
285
423
  const lines = [`import { defineConfig } from "@rig/core";`];
286
- if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
287
- lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
288
- } else if (input.useStandardPlugin) {
289
- lines.push(`import standard from "@rig/standard-plugin";`);
424
+ if (input.useStandardPlugin) {
425
+ lines.push(`import { standardProjectPlugins } from "@rig/standard-plugin/bundle";`);
426
+ if (input.taskSource.kind === "github-issues") {
427
+ lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
428
+ }
290
429
  }
291
430
  lines.push(``, `export default defineConfig({`);
292
431
  const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
293
432
  lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
294
433
  if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
295
- lines.push(` plugins: [standard({`);
296
- lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
297
- lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
298
- lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
434
+ lines.push(` plugins: [...standardProjectPlugins({`);
435
+ lines.push(` standard: {`);
436
+ lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
437
+ lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
438
+ lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
439
+ lines.push(` },`);
299
440
  lines.push(` })],`);
300
441
  } else {
301
- lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
442
+ lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
302
443
  }
303
444
  if (input.taskSource.kind === "github-issues") {
304
445
  lines.push(` taskSource: {`);
@@ -3067,19 +3208,6 @@ class PipelineUnresolvableError extends Error {
3067
3208
  this.contributors = contributors;
3068
3209
  }
3069
3210
  }
3070
-
3071
- class ProtectedStageViolationError extends PipelineUnresolvableError {
3072
- stageId;
3073
- pluginId;
3074
- op;
3075
- constructor(stageId, pluginId, op) {
3076
- super(`Protected stage ${stageId} cannot be ${op === "remove" ? "removed" : "replaced"} by ${pluginId} without an explicit grant`, [], [pluginId]);
3077
- this.name = "ProtectedStageViolationError";
3078
- this.stageId = stageId;
3079
- this.pluginId = pluginId;
3080
- this.op = op;
3081
- }
3082
- }
3083
3211
  function uniqueSorted(values) {
3084
3212
  return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
3085
3213
  }
@@ -3124,9 +3252,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
3124
3252
  seen.set(id, contributorOf(mutation));
3125
3253
  }
3126
3254
  }
3127
- function hasProtectedGrant(grants, stageId, pluginId, op) {
3128
- return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
3129
- }
3130
3255
  function mergeAnchors(current, incoming) {
3131
3256
  return uniqueSorted([...current, ...incoming ?? []]);
3132
3257
  }
@@ -3257,19 +3382,18 @@ function resolveStagePipeline(input) {
3257
3382
  const mutations = [...input.mutations ?? []];
3258
3383
  assertNoDuplicateMutationTargets(mutations, "insert");
3259
3384
  assertNoDuplicateMutationTargets(mutations, "replace");
3260
- const grants = input.protectedStageGrants ?? [];
3261
3385
  const states = new Map;
3262
3386
  const removedStates = new Map;
3263
3387
  for (const [index, stage] of input.defaultStages.entries()) {
3264
3388
  states.set(stage.id, {
3265
- stage,
3389
+ stage: { ...stage, protected: false },
3266
3390
  before: [...stage.before ?? []],
3267
3391
  after: [...stage.after ?? []],
3268
3392
  baseIndex: index,
3269
3393
  contributedBy: "default",
3270
3394
  wrappers: [],
3271
3395
  droppedAnchors: [],
3272
- isProtected: stage.protected === true
3396
+ isProtected: false
3273
3397
  });
3274
3398
  }
3275
3399
  for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
@@ -3277,13 +3401,9 @@ function resolveStagePipeline(input) {
3277
3401
  const contributor = contributorOf(mutation);
3278
3402
  if (!state)
3279
3403
  continue;
3280
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
3281
- throw new ProtectedStageViolationError(mutation.id, contributor, "remove");
3282
- }
3283
3404
  const removedState = {
3284
3405
  ...state,
3285
- removedBy: contributor,
3286
- ...state.isProtected ? { grantUsedBy: contributor } : {}
3406
+ removedBy: contributor
3287
3407
  };
3288
3408
  removedStates.set(mutation.id, removedState);
3289
3409
  states.delete(mutation.id);
@@ -3293,10 +3413,7 @@ function resolveStagePipeline(input) {
3293
3413
  const contributor = contributorOf(mutation);
3294
3414
  if (!state)
3295
3415
  continue;
3296
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "replace")) {
3297
- throw new ProtectedStageViolationError(mutation.id, contributor, "replace");
3298
- }
3299
- const replacement = { ...mutation.stage, id: mutation.id };
3416
+ const replacement = { ...mutation.stage, id: mutation.id, protected: false };
3300
3417
  states.set(mutation.id, {
3301
3418
  ...state,
3302
3419
  stage: replacement,
@@ -3304,8 +3421,7 @@ function resolveStagePipeline(input) {
3304
3421
  after: mutation.stage.after ? [...mutation.stage.after] : state.after,
3305
3422
  replacedBy: contributor,
3306
3423
  contributedBy: state.contributedBy,
3307
- isProtected: mutation.stage.protected ?? state.isProtected,
3308
- ...state.isProtected ? { grantUsedBy: contributor } : {}
3424
+ isProtected: false
3309
3425
  });
3310
3426
  }
3311
3427
  for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
@@ -3314,14 +3430,14 @@ function resolveStagePipeline(input) {
3314
3430
  throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
3315
3431
  }
3316
3432
  states.set(mutation.stage.id, {
3317
- stage: mutation.stage,
3433
+ stage: { ...mutation.stage, protected: false },
3318
3434
  before: [...mutation.stage.before ?? []],
3319
3435
  after: [...mutation.stage.after ?? []],
3320
3436
  baseIndex: null,
3321
3437
  contributedBy: contributor,
3322
3438
  wrappers: [],
3323
3439
  droppedAnchors: [],
3324
- isProtected: mutation.stage.protected === true
3440
+ isProtected: false
3325
3441
  });
3326
3442
  }
3327
3443
  for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
@@ -3420,8 +3536,7 @@ function resolveStagePipeline(input) {
3420
3536
  ...state.replacedBy ? { replacedBy: state.replacedBy } : {},
3421
3537
  ...wrappedBy.length > 0 ? { wrappedBy } : {},
3422
3538
  ...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
3423
- isProtected: state.isProtected,
3424
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
3539
+ isProtected: state.isProtected
3425
3540
  });
3426
3541
  }
3427
3542
  record.push(...[...removedStates.entries()].toSorted((left, right) => {
@@ -3434,8 +3549,7 @@ function resolveStagePipeline(input) {
3434
3549
  stageId,
3435
3550
  contributedBy: state.contributedBy,
3436
3551
  ...state.removedBy ? { removedBy: state.removedBy } : {},
3437
- isProtected: state.isProtected,
3438
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
3552
+ isProtected: state.isProtected
3439
3553
  })));
3440
3554
  return { stages, order, record, cycles: [] };
3441
3555
  }
@@ -1,5 +1,5 @@
1
- import type { ValidatorRegistration, HookRegistration, SkillRegistration, RepoSourceRegistration, AgentRoleRegistration, TaskFieldExtension, TaskSourceRegistration, CliCommandRegistration, StageMutation, StageRun } from "@rig/contracts";
2
- import type { RegisteredValidator, RigPluginWithRuntime, TaskSourceFactoryEntry } from "./plugin-runtime";
1
+ import type { BlockerClassifierRegistration, CliCommandRegistration, HookRegistration, PanelRegistration, ProductCapabilityRegistration, RepoSourceRegistration, AgentRoleRegistration, SkillRegistration, Stage, StageMutation, StageRun, TaskFieldExtension, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
2
+ import type { RegisteredValidator, RigPluginWithRuntime, RuntimeBlockerClassifier, RuntimeCliCommand, RuntimeFeatureCapability, RuntimePanelProducer, TaskSourceFactoryEntry } from "./plugin-runtime";
3
3
  export interface PluginHost {
4
4
  getValidator(id: string): ValidatorRegistration | undefined;
5
5
  getHook(id: string): HookRegistration | undefined;
@@ -9,6 +9,9 @@ export interface PluginHost {
9
9
  getTaskFieldExtension(id: string): TaskFieldExtension | undefined;
10
10
  getTaskSource(id: string): TaskSourceRegistration | undefined;
11
11
  getCliCommand(id: string): CliCommandRegistration | undefined;
12
+ getCapability(id: string): ProductCapabilityRegistration | undefined;
13
+ getPanel(id: string): PanelRegistration | undefined;
14
+ getBlockerClassifier(id: string): BlockerClassifierRegistration | undefined;
12
15
  listValidators(): readonly ValidatorRegistration[];
13
16
  listHooks(): readonly HookRegistration[];
14
17
  listSkills(): readonly SkillRegistration[];
@@ -17,6 +20,10 @@ export interface PluginHost {
17
20
  listTaskFieldExtensions(): readonly TaskFieldExtension[];
18
21
  listTaskSources(): readonly TaskSourceRegistration[];
19
22
  listCliCommands(): readonly CliCommandRegistration[];
23
+ listCapabilities(): readonly ProductCapabilityRegistration[];
24
+ listPanels(): readonly PanelRegistration[];
25
+ listBlockerClassifiers(): readonly BlockerClassifierRegistration[];
26
+ listStages(): readonly Stage[];
20
27
  /**
21
28
  * Stage mutations contributed by all loaded plugins, flattened in load order.
22
29
  * The kernel resolves these (+ the default-lifecycle stages) into the run
@@ -43,6 +50,11 @@ export interface PluginHost {
43
50
  * source kinds (Linear, Jira, custom) declared by plugins.
44
51
  */
45
52
  listExecutableTaskSources(): readonly TaskSourceFactoryEntry[];
53
+ listExecutableCapabilities(): readonly RuntimeFeatureCapability[];
54
+ listExecutablePanels(): readonly RuntimePanelProducer[];
55
+ listExecutableBlockerClassifiers(): readonly RuntimeBlockerClassifier[];
56
+ listExecutableCliCommands(): readonly RuntimeCliCommand[];
57
+ resolveExecutableCliCommand(requested: string): RuntimeCliCommand | undefined;
46
58
  /**
47
59
  * Look up an executable task source factory by its `kind` (the value in
48
60
  * `config.taskSource.kind`). Returns undefined when no plugin provides
@@ -76,6 +76,41 @@ function assertRuntimeMatchesMetadata(plugin) {
76
76
  }
77
77
  }
78
78
  }
79
+ const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
80
+ const runtimeCapabilities = new Map((plugin.__runtime?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
81
+ for (const capability of runtimeCapabilities.values()) {
82
+ if (!declaredCapabilities.has(capability.id)) {
83
+ throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
84
+ }
85
+ }
86
+ const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
87
+ const runtimePanels = new Map((plugin.__runtime?.panels ?? []).map((panel) => [panel.id, panel]));
88
+ for (const panel of runtimePanels.values()) {
89
+ const metadata = declaredPanels.get(panel.id);
90
+ if (!metadata) {
91
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
92
+ }
93
+ if (metadata.slot !== panel.slot) {
94
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
95
+ }
96
+ if (metadata.capabilityId !== panel.capabilityId) {
97
+ throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
98
+ }
99
+ }
100
+ const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
101
+ const runtimeBlockerClassifiers = new Map((plugin.__runtime?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
102
+ for (const classifier of runtimeBlockerClassifiers.values()) {
103
+ if (!declaredBlockerClassifiers.has(classifier.id)) {
104
+ throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
105
+ }
106
+ }
107
+ const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
108
+ const runtimeCliCommands = new Map((plugin.__runtime?.cliCommands ?? []).map((command) => [command.id, command]));
109
+ for (const command of runtimeCliCommands.values()) {
110
+ if (!declaredCliCommands.has(command.id)) {
111
+ throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
112
+ }
113
+ }
79
114
  }
80
115
  function createPluginHost(plugins) {
81
116
  assertUniquePluginNames(plugins);
@@ -90,10 +125,18 @@ function createPluginHost(plugins) {
90
125
  const taskFieldExtensions = [];
91
126
  const taskSources = [];
92
127
  const cliCommands = [];
128
+ const capabilities = [];
129
+ const panels = [];
130
+ const blockerClassifiers = [];
131
+ const stages = [];
93
132
  const stageMutations = [];
94
133
  const stageExecutors = {};
95
134
  const executableValidators = [];
96
135
  const executableTaskSources = [];
136
+ const executableCapabilities = [];
137
+ const executablePanels = [];
138
+ const executableBlockerClassifiers = [];
139
+ const executableCliCommands = [];
97
140
  for (const plugin of plugins) {
98
141
  const c = plugin.contributes;
99
142
  if (!c)
@@ -105,6 +148,18 @@ function createPluginHost(plugins) {
105
148
  if (plugin.__runtime?.taskSources) {
106
149
  executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
107
150
  }
151
+ if (plugin.__runtime?.featureCapabilities) {
152
+ executableCapabilities.push(...plugin.__runtime.featureCapabilities.map((item) => ({ item, pluginName })));
153
+ }
154
+ if (plugin.__runtime?.panels) {
155
+ executablePanels.push(...plugin.__runtime.panels.map((item) => ({ item, pluginName })));
156
+ }
157
+ if (plugin.__runtime?.blockerClassifiers) {
158
+ executableBlockerClassifiers.push(...plugin.__runtime.blockerClassifiers.map((item) => ({ item, pluginName })));
159
+ }
160
+ if (plugin.__runtime?.cliCommands) {
161
+ executableCliCommands.push(...plugin.__runtime.cliCommands.map((item) => ({ item, pluginName })));
162
+ }
108
163
  if (c.validators)
109
164
  validators.push(...c.validators.map((item) => ({ item, pluginName })));
110
165
  if (c.hooks)
@@ -121,6 +176,14 @@ function createPluginHost(plugins) {
121
176
  taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
122
177
  if (c.cliCommands)
123
178
  cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
179
+ if (c.capabilities)
180
+ capabilities.push(...c.capabilities.map((item) => ({ item, pluginName })));
181
+ if (c.panels)
182
+ panels.push(...c.panels.map((item) => ({ item, pluginName })));
183
+ if (c.blockerClassifiers)
184
+ blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
185
+ if (c.stages)
186
+ stages.push(...c.stages.map((item) => ({ item, pluginName })));
124
187
  if (c.stageMutations)
125
188
  stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
126
189
  if (plugin.__runtime?.stages)
@@ -128,6 +191,10 @@ function createPluginHost(plugins) {
128
191
  }
129
192
  indexById(executableValidators, "executableValidator");
130
193
  indexById(executableTaskSources, "executableTaskSource");
194
+ indexById(executableCapabilities, "executableCapability");
195
+ indexById(executablePanels, "executablePanel");
196
+ indexById(executableBlockerClassifiers, "executableBlockerClassifier");
197
+ indexById(executableCliCommands, "executableCliCommand");
131
198
  const taskSourceFactoryByKind = new Map;
132
199
  const taskSourceKindRegistrant = new Map;
133
200
  for (const { item, pluginName } of executableTaskSources) {
@@ -145,6 +212,9 @@ function createPluginHost(plugins) {
145
212
  const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
146
213
  const taskSourceMap = indexById(taskSources, "taskSource");
147
214
  const cliCommandMap = indexById(cliCommands, "cliCommand");
215
+ const capabilityMap = indexById(capabilities, "capability");
216
+ const panelMap = indexById(panels, "panel");
217
+ const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
148
218
  const allValidators = validators.map((c) => c.item);
149
219
  const allHooks = hooks.map((c) => c.item);
150
220
  const allSkills = skills.map((c) => c.item);
@@ -154,8 +224,33 @@ function createPluginHost(plugins) {
154
224
  const allTaskSources = taskSources.map((c) => c.item);
155
225
  const allCliCommands = cliCommands.map((c) => c.item);
156
226
  const allStageMutations = stageMutations.map((c) => c.item);
227
+ const allStages = stages.map((c) => c.item);
228
+ const allCapabilities = capabilities.map((c) => c.item);
229
+ const allPanels = panels.map((c) => c.item);
230
+ const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
157
231
  const allExecutableValidators = executableValidators.map((c) => c.item);
158
232
  const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
233
+ const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
234
+ const allExecutablePanels = executablePanels.map((c) => c.item);
235
+ const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
236
+ const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
237
+ const executableCliCommandByName = new Map;
238
+ const registerExecutableCliCommandSelector = (selector, contribution) => {
239
+ const existing = executableCliCommandByName.get(selector);
240
+ if (existing && existing.item.id !== contribution.item.id) {
241
+ throw new Error(`duplicate executable CLI selector "${selector}" registered by command "${existing.item.id}" from plugin "${existing.pluginName}" and command "${contribution.item.id}" from plugin "${contribution.pluginName}"`);
242
+ }
243
+ executableCliCommandByName.set(selector, contribution);
244
+ };
245
+ for (const contribution of executableCliCommands) {
246
+ const command = contribution.item;
247
+ const family = command.family ?? command.id;
248
+ registerExecutableCliCommandSelector(command.id, contribution);
249
+ registerExecutableCliCommandSelector(family, contribution);
250
+ for (const alias of command.aliases ?? []) {
251
+ registerExecutableCliCommandSelector(alias, contribution);
252
+ }
253
+ }
159
254
  return {
160
255
  getValidator: (id) => validatorMap.get(id),
161
256
  getHook: (id) => hookMap.get(id),
@@ -165,6 +260,9 @@ function createPluginHost(plugins) {
165
260
  getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
166
261
  getTaskSource: (id) => taskSourceMap.get(id),
167
262
  getCliCommand: (id) => cliCommandMap.get(id),
263
+ getCapability: (id) => capabilityMap.get(id),
264
+ getPanel: (id) => panelMap.get(id),
265
+ getBlockerClassifier: (id) => blockerClassifierMap.get(id),
168
266
  listValidators: () => allValidators,
169
267
  listHooks: () => allHooks,
170
268
  listSkills: () => allSkills,
@@ -173,10 +271,19 @@ function createPluginHost(plugins) {
173
271
  listTaskFieldExtensions: () => allTaskFieldExtensions,
174
272
  listTaskSources: () => allTaskSources,
175
273
  listCliCommands: () => allCliCommands,
274
+ listCapabilities: () => allCapabilities,
275
+ listPanels: () => allPanels,
276
+ listBlockerClassifiers: () => allBlockerClassifiers,
277
+ listStages: () => allStages,
176
278
  listStageMutations: () => allStageMutations,
177
279
  listStageExecutors: () => stageExecutors,
178
280
  listExecutableValidators: () => allExecutableValidators,
179
281
  listExecutableTaskSources: () => allExecutableTaskSources,
282
+ listExecutableCapabilities: () => allExecutableCapabilities,
283
+ listExecutablePanels: () => allExecutablePanels,
284
+ listExecutableBlockerClassifiers: () => allExecutableBlockerClassifiers,
285
+ listExecutableCliCommands: () => allExecutableCliCommands,
286
+ resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
180
287
  resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
181
288
  };
182
289
  }
@@ -1,4 +1,4 @@
1
- import type { HookImplementation, RegisteredTaskSource, RigConfig, RigPlugin, StageRun, TaskSourceConfig, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
1
+ import type { BlockerClassifierRegistration, CliCommandRegistration, HookImplementation, PanelRegistration, ProductCapabilityRegistration, RegisteredTaskSource, RigConfig, RigPlugin, StageRun, TaskSourceConfig, TaskSourceRegistration, ValidatorRegistration } from "@rig/contracts";
2
2
  export interface ValidatorResult {
3
3
  id: string;
4
4
  passed: boolean;
@@ -44,6 +44,23 @@ export interface TaskSourceFactoryContext {
44
44
  export interface TaskSourceFactoryEntry extends TaskSourceRegistration {
45
45
  factory(config: TaskSourceConfig, context?: TaskSourceFactoryContext): RegisteredTaskSource;
46
46
  }
47
+ export interface RuntimeFeatureCapability extends ProductCapabilityRegistration {
48
+ run?: (input: unknown) => Promise<unknown> | unknown;
49
+ }
50
+ export interface RuntimePanelProducer extends PanelRegistration {
51
+ produce?: (context?: unknown) => Promise<unknown> | unknown;
52
+ }
53
+ export interface RuntimeBlockerClassifier extends BlockerClassifierRegistration {
54
+ classify(input: unknown): Promise<unknown> | unknown;
55
+ }
56
+ export interface RuntimeCliContext {
57
+ projectRoot: string;
58
+ outputMode?: string;
59
+ dryRun?: boolean;
60
+ }
61
+ export interface RuntimeCliCommand extends CliCommandRegistration {
62
+ run(context: RuntimeCliContext, args: readonly string[]): Promise<unknown> | unknown;
63
+ }
47
64
  export interface RigPluginRuntime {
48
65
  validators?: readonly RegisteredValidator[];
49
66
  taskSources?: readonly TaskSourceFactoryEntry[];
@@ -66,6 +83,22 @@ export interface RigPluginRuntime {
66
83
  * execute plugin-contributed stages (e.g. the docs-drift pre-merge gate).
67
84
  */
68
85
  stages?: Record<string, StageRun>;
86
+ /**
87
+ * Executable product-capability registrations. This is deliberately separate
88
+ * from `capabilities`: that map is reserved for the five kernel provider
89
+ * capability tags resolved by `@rig/kernel-seed`.
90
+ */
91
+ featureCapabilities?: readonly RuntimeFeatureCapability[];
92
+ panels?: readonly RuntimePanelProducer[];
93
+ blockerClassifiers?: readonly RuntimeBlockerClassifier[];
94
+ cliCommands?: readonly RuntimeCliCommand[];
95
+ /**
96
+ * Executable kernel-capability providers keyed by **capability tag**
97
+ * (`transport`, `journal`, …) — the same shape the kernel-seed resolver reads
98
+ * via `plugin.runtime.capabilities[tag]`. Which tags a plugin provides is
99
+ * declared by the top-level `provides: CapabilityTag[]`; this holds the impls.
100
+ */
101
+ capabilities?: Record<string, unknown>;
69
102
  }
70
103
  export type RigPluginWithRuntime = RigPlugin & {
71
104
  __runtime?: RigPluginRuntime;
@@ -2,22 +2,25 @@
2
2
  // packages/core/src/rig-init-builder.ts
3
3
  function buildRigInitConfigSource(input) {
4
4
  const lines = [`import { defineConfig } from "@rig/core";`];
5
- if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
6
- lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
7
- } else if (input.useStandardPlugin) {
8
- lines.push(`import standard from "@rig/standard-plugin";`);
5
+ if (input.useStandardPlugin) {
6
+ lines.push(`import { standardProjectPlugins } from "@rig/standard-plugin/bundle";`);
7
+ if (input.taskSource.kind === "github-issues") {
8
+ lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
9
+ }
9
10
  }
10
11
  lines.push(``, `export default defineConfig({`);
11
12
  const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
12
13
  lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
13
14
  if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
14
- lines.push(` plugins: [standard({`);
15
- lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
16
- lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
17
- lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
15
+ lines.push(` plugins: [...standardProjectPlugins({`);
16
+ lines.push(` standard: {`);
17
+ lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
18
+ lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
19
+ lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
20
+ lines.push(` },`);
18
21
  lines.push(` })],`);
19
22
  } else {
20
- lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
23
+ lines.push(` plugins: [${input.useStandardPlugin ? "...standardProjectPlugins()" : ""}],`);
21
24
  }
22
25
  if (input.taskSource.kind === "github-issues") {
23
26
  lines.push(` taskSource: {`);
@@ -189,7 +189,7 @@ export declare function selectPolicyDecisionsForTask(snapshot: EngineReadModel |
189
189
  readonly id: string;
190
190
  readonly createdAt: string;
191
191
  readonly decision: "allow" | "block" | "warn";
192
- readonly mode: "off" | "observe" | "enforce";
192
+ readonly mode: "observe" | "off" | "enforce";
193
193
  readonly runId: string & import("effect/Brand").Brand<"RunId">;
194
194
  readonly actionId: (string & import("effect/Brand").Brand<"ActionId">) | null;
195
195
  readonly reason: string;
@@ -43,15 +43,9 @@ export type StageMutation<TStage extends ResolvableStage = ResolvableStage> = Re
43
43
  after?: readonly string[];
44
44
  contributedBy?: string;
45
45
  }>;
46
- export interface ProtectedStageGrant {
47
- readonly stageId: string;
48
- readonly pluginId: string;
49
- readonly op?: "remove" | "replace";
50
- }
51
46
  export interface ResolveStagePipelineInput<TStage extends ResolvableStage = ResolvableStage> {
52
47
  readonly defaultStages: readonly TStage[];
53
48
  readonly mutations?: readonly StageMutation<TStage>[];
54
- readonly protectedStageGrants?: readonly ProtectedStageGrant[];
55
49
  }
56
50
  export interface DroppedStageAnchor {
57
51
  readonly stageId: string;
@@ -68,7 +62,6 @@ export interface StageResolutionRecordEntry {
68
62
  readonly wrappedBy?: readonly string[];
69
63
  readonly droppedAnchors?: readonly DroppedStageAnchor[];
70
64
  readonly isProtected: boolean;
71
- readonly grantUsedBy?: string;
72
65
  }
73
66
  export interface ResolvedStagePipeline<TStage extends ResolvableStage = ResolvableStage> {
74
67
  readonly stages: readonly TStage[];
@@ -81,17 +74,4 @@ export declare class PipelineUnresolvableError extends Error {
81
74
  readonly contributors: readonly string[];
82
75
  constructor(message: string, cycles: readonly (readonly string[])[], contributors: readonly string[]);
83
76
  }
84
- /**
85
- * Raised when a plugin attempts to `remove`/`replace` a protected stage without
86
- * an explicit grant. Subclasses {@link PipelineUnresolvableError} so existing
87
- * `instanceof PipelineUnresolvableError` checks keep working, but carries the
88
- * stage/plugin/op so the kernel layer can surface the typed contracts
89
- * `ProtectedStageViolation` (with its `KernelBootErrorCode`).
90
- */
91
- export declare class ProtectedStageViolationError extends PipelineUnresolvableError {
92
- readonly stageId: string;
93
- readonly pluginId: string;
94
- readonly op: "remove" | "replace";
95
- constructor(stageId: string, pluginId: string, op: "remove" | "replace");
96
- }
97
77
  export declare function resolveStagePipeline<TStage extends ResolvableStage>(input: ResolveStagePipelineInput<TStage>): ResolvedStagePipeline<TStage>;
@@ -10,19 +10,6 @@ class PipelineUnresolvableError extends Error {
10
10
  this.contributors = contributors;
11
11
  }
12
12
  }
13
-
14
- class ProtectedStageViolationError extends PipelineUnresolvableError {
15
- stageId;
16
- pluginId;
17
- op;
18
- constructor(stageId, pluginId, op) {
19
- super(`Protected stage ${stageId} cannot be ${op === "remove" ? "removed" : "replaced"} by ${pluginId} without an explicit grant`, [], [pluginId]);
20
- this.name = "ProtectedStageViolationError";
21
- this.stageId = stageId;
22
- this.pluginId = pluginId;
23
- this.op = op;
24
- }
25
- }
26
13
  function uniqueSorted(values) {
27
14
  return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
28
15
  }
@@ -67,9 +54,6 @@ function assertNoDuplicateMutationTargets(mutations, op) {
67
54
  seen.set(id, contributorOf(mutation));
68
55
  }
69
56
  }
70
- function hasProtectedGrant(grants, stageId, pluginId, op) {
71
- return grants.some((grant) => grant.stageId === stageId && grant.pluginId === pluginId && (grant.op === undefined || grant.op === op));
72
- }
73
57
  function mergeAnchors(current, incoming) {
74
58
  return uniqueSorted([...current, ...incoming ?? []]);
75
59
  }
@@ -200,19 +184,18 @@ function resolveStagePipeline(input) {
200
184
  const mutations = [...input.mutations ?? []];
201
185
  assertNoDuplicateMutationTargets(mutations, "insert");
202
186
  assertNoDuplicateMutationTargets(mutations, "replace");
203
- const grants = input.protectedStageGrants ?? [];
204
187
  const states = new Map;
205
188
  const removedStates = new Map;
206
189
  for (const [index, stage] of input.defaultStages.entries()) {
207
190
  states.set(stage.id, {
208
- stage,
191
+ stage: { ...stage, protected: false },
209
192
  before: [...stage.before ?? []],
210
193
  after: [...stage.after ?? []],
211
194
  baseIndex: index,
212
195
  contributedBy: "default",
213
196
  wrappers: [],
214
197
  droppedAnchors: [],
215
- isProtected: stage.protected === true
198
+ isProtected: false
216
199
  });
217
200
  }
218
201
  for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
@@ -220,13 +203,9 @@ function resolveStagePipeline(input) {
220
203
  const contributor = contributorOf(mutation);
221
204
  if (!state)
222
205
  continue;
223
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "remove")) {
224
- throw new ProtectedStageViolationError(mutation.id, contributor, "remove");
225
- }
226
206
  const removedState = {
227
207
  ...state,
228
- removedBy: contributor,
229
- ...state.isProtected ? { grantUsedBy: contributor } : {}
208
+ removedBy: contributor
230
209
  };
231
210
  removedStates.set(mutation.id, removedState);
232
211
  states.delete(mutation.id);
@@ -236,10 +215,7 @@ function resolveStagePipeline(input) {
236
215
  const contributor = contributorOf(mutation);
237
216
  if (!state)
238
217
  continue;
239
- if (state.isProtected && !hasProtectedGrant(grants, mutation.id, contributor, "replace")) {
240
- throw new ProtectedStageViolationError(mutation.id, contributor, "replace");
241
- }
242
- const replacement = { ...mutation.stage, id: mutation.id };
218
+ const replacement = { ...mutation.stage, id: mutation.id, protected: false };
243
219
  states.set(mutation.id, {
244
220
  ...state,
245
221
  stage: replacement,
@@ -247,8 +223,7 @@ function resolveStagePipeline(input) {
247
223
  after: mutation.stage.after ? [...mutation.stage.after] : state.after,
248
224
  replacedBy: contributor,
249
225
  contributedBy: state.contributedBy,
250
- isProtected: mutation.stage.protected ?? state.isProtected,
251
- ...state.isProtected ? { grantUsedBy: contributor } : {}
226
+ isProtected: false
252
227
  });
253
228
  }
254
229
  for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
@@ -257,14 +232,14 @@ function resolveStagePipeline(input) {
257
232
  throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
258
233
  }
259
234
  states.set(mutation.stage.id, {
260
- stage: mutation.stage,
235
+ stage: { ...mutation.stage, protected: false },
261
236
  before: [...mutation.stage.before ?? []],
262
237
  after: [...mutation.stage.after ?? []],
263
238
  baseIndex: null,
264
239
  contributedBy: contributor,
265
240
  wrappers: [],
266
241
  droppedAnchors: [],
267
- isProtected: mutation.stage.protected === true
242
+ isProtected: false
268
243
  });
269
244
  }
270
245
  for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
@@ -363,8 +338,7 @@ function resolveStagePipeline(input) {
363
338
  ...state.replacedBy ? { replacedBy: state.replacedBy } : {},
364
339
  ...wrappedBy.length > 0 ? { wrappedBy } : {},
365
340
  ...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
366
- isProtected: state.isProtected,
367
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
341
+ isProtected: state.isProtected
368
342
  });
369
343
  }
370
344
  record.push(...[...removedStates.entries()].toSorted((left, right) => {
@@ -377,13 +351,11 @@ function resolveStagePipeline(input) {
377
351
  stageId,
378
352
  contributedBy: state.contributedBy,
379
353
  ...state.removedBy ? { removedBy: state.removedBy } : {},
380
- isProtected: state.isProtected,
381
- ...state.grantUsedBy ? { grantUsedBy: state.grantUsedBy } : {}
354
+ isProtected: state.isProtected
382
355
  })));
383
356
  return { stages, order, record, cycles: [] };
384
357
  }
385
358
  export {
386
359
  resolveStagePipeline,
387
- ProtectedStageViolationError,
388
360
  PipelineUnresolvableError
389
361
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/core",
3
- "version": "0.0.6-alpha.136",
3
+ "version": "0.0.6-alpha.137",
4
4
  "type": "module",
5
5
  "description": "Config and plugin composition library for Rig's OMP extension ecosystem; not a product host/runtime.",
6
6
  "license": "UNLICENSED",
@@ -45,7 +45,7 @@
45
45
  "module": "./dist/src/index.js",
46
46
  "types": "./dist/src/index.d.ts",
47
47
  "dependencies": {
48
- "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.136",
48
+ "@rig/contracts": "npm:@h-rig/contracts@0.0.6-alpha.137",
49
49
  "effect": "https://pkg.pr.new/Effect-TS/effect-smol/effect@8881a9b"
50
50
  }
51
51
  }