role-os 2.0.0 → 2.2.0

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/src/evidence.mjs CHANGED
@@ -146,7 +146,7 @@ const DEFAULT_REQUIREMENTS = {
146
146
  * @property {EvidenceItem[]} evidence - Structured evidence items
147
147
  * @property {string[]} gaps - What's missing or weak
148
148
  * @property {string[]} risks - Identified risks
149
- * @property {string} [requiredNextArtifact] - What the next role must produce (for non-approve)
149
+ * @property {string} [requiredNextArtifact] - What the next role must produce (for non-accept)
150
150
  * @property {string} confidence - One of CONFIDENCE_LEVELS
151
151
  */
152
152
 
@@ -182,25 +182,25 @@ export function checkSufficiency(verdict) {
182
182
  .map(e => `${e.kind}: ${e.claim} (${e.reference})`);
183
183
 
184
184
  if (contradictions.length > 0 && verdict.verdict === "accept") {
185
- warnings.push("Verdict is 'approve' but evidence contains contradictions — review carefully");
185
+ warnings.push("Verdict is 'accept' but evidence contains contradictions — review carefully");
186
186
  }
187
187
 
188
- // Check for missing evidence items on non-approve verdicts
188
+ // Check for missing evidence items on accept verdicts
189
189
  const missingItems = verdict.evidence.filter(e => e.status === "missing");
190
190
  if (missingItems.length > 0 && verdict.verdict === "accept") {
191
- warnings.push("Verdict is 'approve' but some evidence items are marked 'missing'");
191
+ warnings.push("Verdict is 'accept' but some evidence items are marked 'missing'");
192
192
  }
193
193
 
194
- // Non-approve verdicts should have gaps or requiredNextArtifact
195
- if (verdict.verdict !== "approve" && verdict.verdict !== "accept-with-notes") {
194
+ // Non-accept verdicts should have gaps or requiredNextArtifact
195
+ if (verdict.verdict !== "accept" && verdict.verdict !== "accept-with-notes") {
196
196
  if (verdict.gaps.length === 0 && !verdict.requiredNextArtifact) {
197
- warnings.push("Non-approve verdict should specify gaps or requiredNextArtifact for recovery");
197
+ warnings.push("Non-accept verdict should specify gaps or requiredNextArtifact for recovery");
198
198
  }
199
199
  }
200
200
 
201
- // Low confidence + approve is suspicious
201
+ // Low confidence + accept is suspicious
202
202
  if (verdict.confidence === "low" && verdict.verdict === "accept") {
203
- warnings.push("Low confidence approve — consider whether evidence is actually sufficient");
203
+ warnings.push("Low confidence accept — consider whether evidence is actually sufficient");
204
204
  }
205
205
 
206
206
  const sufficient = missingRequired.length === 0 && contradictions.length === 0;
@@ -10,8 +10,7 @@
10
10
  */
11
11
 
12
12
  import { MISSIONS, getMission, validateMission } from "./mission.mjs";
13
- import { TEAM_PACKS } from "./packs.mjs";
14
- import { validateArtifact, ROLE_ARTIFACT_CONTRACTS } from "./artifacts.mjs";
13
+ import { validateArtifact } from "./artifacts.mjs";
15
14
 
16
15
  let _runCounter = 0;
17
16
 
@@ -59,7 +58,7 @@ let _runCounter = 0;
59
58
  * @param {string} taskDescription
60
59
  * @returns {MissionRun}
61
60
  */
62
- export function createRun(missionKey, taskDescription) {
61
+ export function createRun(missionKey, taskDescription, options = {}) {
63
62
  const mission = getMission(missionKey);
64
63
  if (!mission) {
65
64
  throw new Error(`Mission "${missionKey}" not found. Available: ${Object.keys(MISSIONS).join(", ")}`);
@@ -72,16 +71,26 @@ export function createRun(missionKey, taskDescription) {
72
71
 
73
72
  const id = `${missionKey}-${Date.now()}-${++_runCounter}`;
74
73
 
75
- const steps = mission.artifactFlow.map((step) => ({
76
- role: step.role,
77
- produces: step.produces,
78
- consumedBy: step.consumedBy,
79
- status: "pending",
80
- artifact: null,
81
- note: null,
82
- startedAt: null,
83
- completedAt: null,
84
- }));
74
+ let steps;
75
+ const dd = mission.dynamicDispatch;
76
+
77
+ if (dd && options.manifest) {
78
+ // Dynamic dispatch — build steps from manifest
79
+ steps = buildDynamicSteps(mission, options.manifest);
80
+ } else {
81
+ // Static dispatch — use artifactFlow as-is
82
+ steps = mission.artifactFlow.map((step) => ({
83
+ role: step.role,
84
+ produces: step.produces,
85
+ consumedBy: step.consumedBy,
86
+ status: "pending",
87
+ artifact: null,
88
+ artifactValidation: null,
89
+ note: null,
90
+ startedAt: null,
91
+ completedAt: null,
92
+ }));
93
+ }
85
94
 
86
95
  return {
87
96
  id,
@@ -93,9 +102,94 @@ export function createRun(missionKey, taskDescription) {
93
102
  startedAt: new Date().toISOString(),
94
103
  completedAt: null,
95
104
  completionReport: null,
105
+ dynamicDispatch: dd && options.manifest ? true : false,
106
+ manifest: options.manifest || null,
96
107
  };
97
108
  }
98
109
 
110
+ /**
111
+ * Build steps from manifest for dynamic dispatch missions.
112
+ * @param {Object} mission
113
+ * @param {Object} manifest - The audit-manifest.json content
114
+ * @returns {MissionStep[]}
115
+ */
116
+ function buildDynamicSteps(mission, manifest) {
117
+ const dd = mission.dynamicDispatch;
118
+ const steps = [];
119
+
120
+ // Scaling roles: one step per manifest entry
121
+ const components = manifest[dd.componentAuditorPer] || [];
122
+ const boundaries = manifest[dd.seamAuditorPer] || manifest.boundaries || [];
123
+
124
+ // Component Auditor × N
125
+ for (const comp of components) {
126
+ steps.push({
127
+ role: "Component Auditor",
128
+ produces: "component-audit-report",
129
+ consumedBy: "Audit Synthesizer",
130
+ parcel: comp.id || comp.name,
131
+ status: "pending",
132
+ artifact: null,
133
+ artifactValidation: null,
134
+ note: null,
135
+ startedAt: null,
136
+ completedAt: null,
137
+ });
138
+ }
139
+
140
+ // Test Truth Auditor × M
141
+ for (const comp of components) {
142
+ steps.push({
143
+ role: "Test Truth Auditor",
144
+ produces: "test-truth-report",
145
+ consumedBy: "Audit Synthesizer",
146
+ parcel: comp.id || comp.name,
147
+ status: "pending",
148
+ artifact: null,
149
+ artifactValidation: null,
150
+ note: null,
151
+ startedAt: null,
152
+ completedAt: null,
153
+ });
154
+ }
155
+
156
+ // Seam Auditor × K
157
+ for (const boundary of boundaries) {
158
+ const label = boundary.id || `${boundary.from}-${boundary.to}`;
159
+ steps.push({
160
+ role: "Seam Auditor",
161
+ produces: "seam-audit-report",
162
+ consumedBy: "Audit Synthesizer",
163
+ parcel: label,
164
+ status: "pending",
165
+ artifact: null,
166
+ artifactValidation: null,
167
+ note: null,
168
+ startedAt: null,
169
+ completedAt: null,
170
+ });
171
+ }
172
+
173
+ // Non-scaling roles from artifactFlow (Audit Synthesizer, Critic Reviewer)
174
+ for (const step of mission.artifactFlow) {
175
+ if (!dd.scalingRoles.includes(step.role)) {
176
+ steps.push({
177
+ role: step.role,
178
+ produces: step.produces,
179
+ consumedBy: step.consumedBy,
180
+ status: "pending",
181
+ artifact: null,
182
+ artifactValidation: null,
183
+ note: null,
184
+ startedAt: null,
185
+ completedAt: null,
186
+ });
187
+ }
188
+ }
189
+
190
+ return steps;
191
+ }
192
+
99
193
  // ── Step through a run ──────────────────────────────────────────────────────
100
194
 
101
195
  /**
@@ -127,6 +221,10 @@ export function completeStep(run, artifact, note) {
127
221
  throw new Error("No active step to complete");
128
222
  }
129
223
 
224
+ // Validate artifact against role contract (warn, don't block)
225
+ const validation = validateArtifact(active.role, artifact);
226
+ active.artifactValidation = validation;
227
+
130
228
  active.status = "completed";
131
229
  active.artifact = artifact;
132
230
  active.note = note || null;