xoonya 1.2.0 → 1.3.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/README.md CHANGED
@@ -98,6 +98,8 @@ Primary proof-model routes:
98
98
  - `GET /v1/integrations`
99
99
  - `GET /v1/integrations/summary`
100
100
  - `GET /v1/integrations/snapshot`
101
+ - `GET /v1/integrations/promotion-readiness`
102
+ - `GET /v1/integrations/promotion-workflow`
101
103
 
102
104
  Preserved compatibility routes:
103
105
 
@@ -160,6 +162,8 @@ The package now includes:
160
162
  - `exportIntegrationRegistrySnapshot(...)`
161
163
  - `validateIntegrationRegistrySnapshot(...)`
162
164
  - `summarizeIntegrationPromotionReadiness(...)`
165
+ - `summarizeIntegrationPromotionWorkflow(...)`
166
+ - `summarizeIntegrationPromotionWorkflowList(...)`
163
167
 
164
168
  Those helpers are intended to normalize proof-adjacent integrations without letting pricing, workflow, or settlement logic leak into the proof kernel.
165
169
 
@@ -172,6 +176,9 @@ For lightweight local review flows, `xoonya` also includes a small repo-local pr
172
176
  ```bash
173
177
  node scripts/registry_cli.js validate-registration --example verifier
174
178
  node scripts/registry_cli.js export-snapshot --registration_state staging_registered
179
+ node scripts/registry_cli.js promotion-readiness --integration partner.verifier.lattice_notary
180
+ node scripts/registry_cli.js promotion-workflow --integration partner.verifier.lattice_notary
181
+ node scripts/registry_cli.js promotion-workflow-summary --source third_party_example --registration_state staging_registered --bundled false
175
182
  ```
176
183
 
177
184
  That tooling is intentionally narrow: validate proof-integration registration posture and export filtered proof registry snapshots.
@@ -182,3 +189,4 @@ The proof-side readiness layer now also makes promotion posture explicit without
182
189
  - default-eligible means it can participate in the proof-side default posture
183
190
  - production-default-ready means certification, maturity, bundled posture, and selection posture all line up
184
191
  - blockers are returned as data instead of being left implicit
192
+ - promotion workflow summaries now show the next proof-side transition and the current recommendation without introducing approval state
@@ -10,6 +10,13 @@ function sortStrings(values) {
10
10
  return Array.from(new Set((Array.isArray(values) ? values : []).filter(Boolean))).sort();
11
11
  }
12
12
 
13
+ function summarizeCounts(values, fieldName) {
14
+ return sortStrings(values).map((value) => ({
15
+ [fieldName]: value,
16
+ count: values.filter((entry) => entry === value).length
17
+ }));
18
+ }
19
+
13
20
  function createIntegrationDescriptor(descriptor, extras = {}) {
14
21
  return {
15
22
  integration_id: descriptor.integration_id,
@@ -190,6 +197,129 @@ function buildProofPromotionReadinessSummary(input = {}) {
190
197
  };
191
198
  }
192
199
 
200
+ function summarizeProofPromotionBlockerCategories(blockers = []) {
201
+ const values = Array.isArray(blockers) ? blockers : [];
202
+ const categories = [];
203
+ if (values.some((entry) => entry === "registration_state_missing")) categories.push("registration");
204
+ if (values.some((entry) => entry === "certification_state_missing" || entry === "not_certified")) categories.push("certification");
205
+ if (values.some((entry) => entry === "integration_maturity_missing" || entry === "not_marked_production_default")) categories.push("maturity");
206
+ if (values.some((entry) => entry === "default_selection_disabled" || entry === "still_requires_explicit_selection")) categories.push("selection");
207
+ if (values.some((entry) => entry === "not_bundled_for_default_rollout")) categories.push("rollout");
208
+ return summarizeCounts(categories, "category");
209
+ }
210
+
211
+ function buildProofCertificationGapSummary(readiness = {}, integration = {}) {
212
+ const blockers = Array.isArray(readiness.blockers) ? readiness.blockers : [];
213
+ const certificationScope = Array.isArray(integration.certification_scope) ? integration.certification_scope.filter(Boolean) : [];
214
+ const verifiedWith = integration.registration && Array.isArray(integration.registration.verified_with)
215
+ ? integration.registration.verified_with.filter(Boolean)
216
+ : [];
217
+ const missingRequirements = [];
218
+ const productionReady = readiness.production_default_ready === true;
219
+
220
+ if (blockers.includes("certification_state_missing") || blockers.includes("not_certified")) {
221
+ missingRequirements.push("certification_state");
222
+ }
223
+ if (!productionReady && certificationScope.length < 1) {
224
+ missingRequirements.push("certification_scope");
225
+ }
226
+ if (!productionReady && verifiedWith.length < 1) {
227
+ missingRequirements.push("verified_with");
228
+ }
229
+
230
+ return {
231
+ certification_state: readiness.certification_state || null,
232
+ certification_scope_count: certificationScope.length,
233
+ verified_with_count: verifiedWith.length,
234
+ missing_requirements: missingRequirements,
235
+ certifiable: missingRequirements.length === 0
236
+ };
237
+ }
238
+
239
+ function buildProofNextTransitionSummary(readiness = {}) {
240
+ const blockers = Array.isArray(readiness.blockers) ? readiness.blockers : [];
241
+ if (readiness.production_default_ready === true) {
242
+ return {
243
+ action: "maintain_production_default",
244
+ reason: "integration already satisfies proof-side production-default posture",
245
+ requirements: [],
246
+ later_actions: []
247
+ };
248
+ }
249
+ if (blockers.includes("registration_state_missing")) {
250
+ return {
251
+ action: "register_integration",
252
+ reason: "proof integration is missing a registration state",
253
+ requirements: ["registration_state"],
254
+ later_actions: ["complete_certification", "promote_maturity", "enable_default_selection", "bundle_for_default_rollout"]
255
+ };
256
+ }
257
+ if (blockers.includes("not_certified") || blockers.includes("certification_state_missing")) {
258
+ return {
259
+ action: "complete_certification",
260
+ reason: "proof integration is not yet certified",
261
+ requirements: ["certification_state"],
262
+ later_actions: ["promote_maturity", "enable_default_selection", "bundle_for_default_rollout"]
263
+ };
264
+ }
265
+ if (blockers.includes("not_marked_production_default") || blockers.includes("integration_maturity_missing")) {
266
+ return {
267
+ action: "promote_maturity",
268
+ reason: "proof integration maturity is not yet aligned with production-default posture",
269
+ requirements: ["integration_maturity"],
270
+ later_actions: ["enable_default_selection", "bundle_for_default_rollout"]
271
+ };
272
+ }
273
+ if (blockers.includes("default_selection_disabled") || blockers.includes("still_requires_explicit_selection")) {
274
+ return {
275
+ action: "enable_default_selection",
276
+ reason: "proof integration remains explicit-selection only",
277
+ requirements: ["default_selection_enabled", "requires_explicit_selection=false"],
278
+ later_actions: blockers.includes("not_bundled_for_default_rollout") ? ["bundle_for_default_rollout"] : []
279
+ };
280
+ }
281
+ if (blockers.includes("not_bundled_for_default_rollout")) {
282
+ return {
283
+ action: "bundle_for_default_rollout",
284
+ reason: "proof integration is not yet bundled for production-default rollout",
285
+ requirements: ["bundled=true"],
286
+ later_actions: []
287
+ };
288
+ }
289
+ return {
290
+ action: "review_manually",
291
+ reason: "proof integration posture needs review",
292
+ requirements: [],
293
+ later_actions: []
294
+ };
295
+ }
296
+
297
+ function buildProofPromotionRecommendationSummary(readiness = {}) {
298
+ const nextTransition = buildProofNextTransitionSummary(readiness);
299
+ if (readiness.production_default_ready === true) {
300
+ return {
301
+ recommendation: "eligible_for_production_default",
302
+ rationale: "proof integration is ready for production-default posture"
303
+ };
304
+ }
305
+ if (readiness.review_ready === true && readiness.default_eligible === false) {
306
+ return {
307
+ recommendation: "keep_staged_until_requirements_clear",
308
+ rationale: nextTransition.reason
309
+ };
310
+ }
311
+ if (readiness.review_ready === true) {
312
+ return {
313
+ recommendation: "review_for_promotion",
314
+ rationale: nextTransition.reason
315
+ };
316
+ }
317
+ return {
318
+ recommendation: "complete_prerequisites_first",
319
+ rationale: nextTransition.reason
320
+ };
321
+ }
322
+
193
323
  function summarizeIntegrationPromotionReadiness(integrationOrId) {
194
324
  const integration = typeof integrationOrId === "string"
195
325
  ? getIntegration(integrationOrId)
@@ -217,6 +347,45 @@ function summarizeIntegrationPromotionReadiness(integrationOrId) {
217
347
  };
218
348
  }
219
349
 
350
+ function summarizeIntegrationPromotionWorkflow(integrationOrId) {
351
+ const summary = summarizeIntegrationPromotionReadiness(integrationOrId);
352
+ if (!summary.ok) {
353
+ return summary;
354
+ }
355
+ const readiness = clone(summary.promotion_readiness || {});
356
+ return {
357
+ ...summary,
358
+ certification_gaps: buildProofCertificationGapSummary(readiness, summary),
359
+ blocker_categories: summarizeProofPromotionBlockerCategories(readiness.blockers),
360
+ next_transition: buildProofNextTransitionSummary(readiness),
361
+ promotion_recommendation: buildProofPromotionRecommendationSummary(readiness)
362
+ };
363
+ }
364
+
365
+ function summarizeIntegrationPromotionWorkflowList(filters = {}) {
366
+ const integrations = listIntegrations(filters).map((integration) => summarizeIntegrationPromotionWorkflow(integration));
367
+ return {
368
+ ok: true,
369
+ integration_count: integrations.length,
370
+ review_ready_count: integrations.filter((entry) => entry.promotion_readiness.review_ready).length,
371
+ production_default_ready_count: integrations.filter((entry) => entry.promotion_readiness.production_default_ready).length,
372
+ certifiable_count: integrations.filter((entry) => entry.certification_gaps.certifiable).length,
373
+ next_actions: summarizeCounts(
374
+ integrations.map((entry) => entry.next_transition.action).filter(Boolean),
375
+ "action"
376
+ ),
377
+ recommendations: summarizeCounts(
378
+ integrations.map((entry) => entry.promotion_recommendation.recommendation).filter(Boolean),
379
+ "recommendation"
380
+ ),
381
+ blocker_categories: summarizeCounts(
382
+ integrations.flatMap((entry) => (entry.blocker_categories || []).map((item) => item.category)).filter(Boolean),
383
+ "category"
384
+ ),
385
+ integrations
386
+ };
387
+ }
388
+
220
389
  const BUILTIN_INTEGRATIONS = [
221
390
  createIntegrationDescriptor({
222
391
  integration_id: "builtin.identity.legacy_material",
@@ -454,6 +623,8 @@ module.exports = {
454
623
  listIntegrations,
455
624
  summarizeIntegrations,
456
625
  summarizeIntegrationPromotionReadiness,
626
+ summarizeIntegrationPromotionWorkflow,
627
+ summarizeIntegrationPromotionWorkflowList,
457
628
  validateIntegrationRegistration,
458
629
  importRegisteredIntegration,
459
630
  createIntegrationRegistrySnapshot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xoonya",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Deterministic proof SDK for canonical events, receipts, proof bundles, and verification.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/server.js CHANGED
@@ -4,6 +4,8 @@ const proofSdk = require('./index.js');
4
4
  const {
5
5
  getIntegration,
6
6
  exportIntegrationRegistrySnapshot,
7
+ summarizeIntegrationPromotionWorkflow,
8
+ summarizeIntegrationPromotionWorkflowList,
7
9
  summarizeIntegrationPromotionReadiness,
8
10
  summarizeIntegrations
9
11
  } = require('./integrations/registry');
@@ -157,6 +159,22 @@ async function routeRequest(req, res) {
157
159
  return;
158
160
  }
159
161
 
162
+ if (req.method === 'GET' && url.pathname === '/v1/integrations/promotion-workflow') {
163
+ const response = buildIntegrationListResponse(summarizeIntegrationPromotionWorkflowList({
164
+ integration_class: url.searchParams.get('class'),
165
+ capability: url.searchParams.get('capability'),
166
+ surface: url.searchParams.get('surface'),
167
+ source: url.searchParams.get('source'),
168
+ certification_state: url.searchParams.get('certification_state'),
169
+ integration_maturity: url.searchParams.get('integration_maturity'),
170
+ registration_state: url.searchParams.get('registration_state'),
171
+ proof_scope: url.searchParams.get('proof_scope'),
172
+ bundled: url.searchParams.get('bundled')
173
+ }));
174
+ sendJson(res, response.status, response.payload);
175
+ return;
176
+ }
177
+
160
178
  if (req.method === 'GET' && url.pathname.startsWith('/v1/integrations/') && url.pathname.endsWith('/promotion-readiness')) {
161
179
  const integrationId = decodeURIComponent(
162
180
  url.pathname
@@ -168,6 +186,17 @@ async function routeRequest(req, res) {
168
186
  return;
169
187
  }
170
188
 
189
+ if (req.method === 'GET' && url.pathname.startsWith('/v1/integrations/') && url.pathname.endsWith('/promotion-workflow')) {
190
+ const integrationId = decodeURIComponent(
191
+ url.pathname
192
+ .slice('/v1/integrations/'.length, -'/promotion-workflow'.length)
193
+ .replace(/\/$/, '')
194
+ );
195
+ const response = buildIntegrationListResponse(summarizeIntegrationPromotionWorkflow(integrationId));
196
+ sendJson(res, response.status || 200, response.payload || response);
197
+ return;
198
+ }
199
+
171
200
  if (req.method === 'GET' && url.pathname.startsWith('/v1/integrations/')) {
172
201
  const integrationId = decodeURIComponent(url.pathname.slice('/v1/integrations/'.length));
173
202
  const response = buildIntegrationDetailResponse(getIntegration(integrationId));