anton-bakker-feedback 0.4.4 → 1.0.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.
Files changed (61) hide show
  1. package/README.md +171 -0
  2. package/dist/chunks/ingest-transport-DAA_pJNb.js +245 -0
  3. package/dist/chunks/ingest-transport-DAA_pJNb.js.map +1 -0
  4. package/dist/chunks/normalisers-Cbd-i4Jh.js +97 -0
  5. package/dist/chunks/normalisers-Cbd-i4Jh.js.map +1 -0
  6. package/dist/chunks/{observability-CfwHKObh.js → observability-DUK725zv.js} +3 -3
  7. package/dist/chunks/{observability-CfwHKObh.js.map → observability-DUK725zv.js.map} +1 -1
  8. package/dist/chunks/{submitResolver-DsfkrUHY.js → submitResolver-wfr4kbEM.js} +60 -39
  9. package/dist/chunks/submitResolver-wfr4kbEM.js.map +1 -0
  10. package/dist/core/index.d.ts +1 -0
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/ingest-transport.d.ts +97 -0
  13. package/dist/core/ingest-transport.d.ts.map +1 -1
  14. package/dist/core/normalisers.d.ts +29 -0
  15. package/dist/core/normalisers.d.ts.map +1 -0
  16. package/dist/core/observability.d.ts.map +1 -1
  17. package/dist/core/state-metrics.d.ts +19 -0
  18. package/dist/core/state-metrics.d.ts.map +1 -0
  19. package/dist/core/transforms.d.ts.map +1 -1
  20. package/dist/core/types.d.ts +93 -3
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/index.js +16 -98
  23. package/dist/index.js.map +1 -1
  24. package/dist/react/admin/ActivityTimeline.d.ts +9 -0
  25. package/dist/react/admin/ActivityTimeline.d.ts.map +1 -0
  26. package/dist/react/admin/AnnotationComposer.d.ts +30 -0
  27. package/dist/react/admin/AnnotationComposer.d.ts.map +1 -0
  28. package/dist/react/admin/FeedbackListPage.d.ts.map +1 -1
  29. package/dist/react/admin/FeedbackUpdatePage.d.ts +6 -0
  30. package/dist/react/admin/FeedbackUpdatePage.d.ts.map +1 -1
  31. package/dist/react/admin/LifecycleControls.d.ts +28 -0
  32. package/dist/react/admin/LifecycleControls.d.ts.map +1 -0
  33. package/dist/react/admin/__tests__/ActivityTimeline.test.d.ts +2 -0
  34. package/dist/react/admin/__tests__/ActivityTimeline.test.d.ts.map +1 -0
  35. package/dist/react/admin/__tests__/AnnotationComposer.test.d.ts +2 -0
  36. package/dist/react/admin/__tests__/AnnotationComposer.test.d.ts.map +1 -0
  37. package/dist/react/admin/__tests__/LifecycleControls.test.d.ts +2 -0
  38. package/dist/react/admin/__tests__/LifecycleControls.test.d.ts.map +1 -0
  39. package/dist/react/admin/hooks/can.d.ts +47 -0
  40. package/dist/react/admin/hooks/can.d.ts.map +1 -0
  41. package/dist/react/admin/hooks/useStatusTransition.d.ts +16 -2
  42. package/dist/react/admin/hooks/useStatusTransition.d.ts.map +1 -1
  43. package/dist/react/admin/index.d.ts +14 -1
  44. package/dist/react/admin/index.d.ts.map +1 -1
  45. package/dist/react/admin/index.js +312 -29
  46. package/dist/react/admin/index.js.map +1 -1
  47. package/dist/react/admin/types.d.ts +13 -0
  48. package/dist/react/admin/types.d.ts.map +1 -1
  49. package/dist/react/resolver/submitResolver.d.ts +10 -7
  50. package/dist/react/resolver/submitResolver.d.ts.map +1 -1
  51. package/dist/react.js +1 -1
  52. package/dist/server/index.d.ts +4 -5
  53. package/dist/server/index.d.ts.map +1 -1
  54. package/dist/server/listHandler.d.ts +15 -8
  55. package/dist/server/listHandler.d.ts.map +1 -1
  56. package/dist/server.js +22 -170
  57. package/dist/server.js.map +1 -1
  58. package/package.json +13 -1
  59. package/dist/chunks/submitResolver-DsfkrUHY.js.map +0 -1
  60. package/dist/server/FeedbackService.d.ts +0 -46
  61. package/dist/server/FeedbackService.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"observability-CfwHKObh.js","sources":["../../src/core/observability.ts"],"sourcesContent":["/**\n * OpenTelemetry span helpers with zero AWS dependencies.\n *\n * @opentelemetry/api is declared as an optional peer dependency.\n * If the consuming app has it installed, traces are emitted.\n * If not, functions degrade gracefully to no-ops.\n *\n * CRITICAL: No hard import of '@opentelemetry/api' — only dynamic import.\n * Dynamic import used for runtime availability check.\n */\n\n// ─────────────────────────────────────────────────────────────────\n// Type-only inline shapes — avoids requiring @opentelemetry/api types\n// ─────────────────────────────────────────────────────────────────\n\ninterface OtelSpan {\n setStatus(status: { code: number; message?: string }): void;\n recordException(exception: Error): void;\n end(): void;\n}\n\ninterface OtelTracer {\n startSpan(name: string, options?: { attributes?: Record<string, string | number | boolean> }): OtelSpan;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Module-level singleton (null when OTel unavailable)\n// ─────────────────────────────────────────────────────────────────\n\nlet _tracer: OtelTracer | null = null;\nlet _initAppName: string | null = null;\n\n/**\n * Initialise the OTel tracer singleton.\n * Idempotent — calling twice with the same appName is a no-op.\n * Safe to call when @opentelemetry/api is not installed (no-op).\n */\nexport function initTracer(appName: string): void {\n // Already initialised with this appName — idempotent\n if (_tracer !== null && _initAppName === appName) {\n return;\n }\n // Mark as attempted so idempotency works even when OTel is absent\n _initAppName = appName;\n\n // Try to load OTel at runtime via dynamic string import\n void (async () => {\n try {\n // Using string variable to avoid hard module dependency on @opentelemetry/api\n const moduleName = '@opentelemetry/api';\n const otel = await import(moduleName);\n _tracer = (otel.trace.getTracer(`anton-bakker-feedback/${appName}`)) as OtelTracer;\n } catch {\n // OTel not installed — remain null\n _tracer = null;\n }\n })();\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Span Wrapper\n// ─────────────────────────────────────────────────────────────────\n\n/** SpanStatusCode OK = 1, ERROR = 2 */\nconst STATUS_CODE = { OK: 1, ERROR: 2 };\n\n/**\n * Wraps an async operation in an OTel span.\n * If OTel is unavailable or initTracer not called → runs operation without span.\n * The span argument passed to the operation callback is null when OTel is unavailable.\n *\n * Span name convention: feedback.{operation} (e.g., feedback.submit, feedback.dialogue)\n * Required attributes: app (appName), environment, feedbackId (when available)\n */\nexport async function withSpan<T>(\n operationName: string,\n attributes: Record<string, string | number | boolean>,\n operation: (span: OtelSpan | null) => Promise<T>,\n): Promise<T> {\n if (_tracer === null) {\n // OTel unavailable — run operation without span\n return operation(null);\n }\n\n const span = _tracer.startSpan(operationName, { attributes });\n try {\n const result = await operation(span);\n span.setStatus({ code: STATUS_CODE.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: STATUS_CODE.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n span.end();\n }\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Convenience Event Recorders\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Records a feedback submission span event.\n * No-op when OTel unavailable.\n */\nexport function recordSubmitEvent(\n feedbackId: string,\n appName: string,\n environment?: string,\n): void {\n void withSpan(\n 'feedback.submit',\n {\n app: appName,\n feedbackId,\n environment: environment ?? 'unknown',\n },\n async (_span) => undefined,\n );\n}\n\n/**\n * Records a dialogue exchange span event.\n * No-op when OTel unavailable.\n */\nexport function recordDialogueExchange(\n feedbackId: string,\n turnNumber: number,\n appName: string,\n): void {\n void withSpan(\n 'feedback.dialogue',\n {\n app: appName,\n feedbackId,\n turnNumber,\n },\n async (_span) => undefined,\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// CloudWatch Metric Emission (fire-and-forget)\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Emits a metric to CloudWatch (fire-and-forget).\n *\n * Only active when process.env.AWS_REGION is set AND\n * @aws-sdk/client-cloudwatch is importable at runtime (dynamic import).\n * If either condition is absent, logs to console.debug instead.\n * NEVER throws.\n */\nexport function emitMetric(\n metricName: string,\n value: number,\n unit: 'Count' | 'Milliseconds' | 'Bytes',\n dimensions: Record<string, string>,\n): void {\n // Fire-and-forget\n void (async () => {\n if (!process.env['AWS_REGION']) {\n console.debug(`[feedback-metrics] ${metricName}=${value} ${unit}`, dimensions);\n return;\n }\n\n try {\n // Dynamic import via computed string — prevents grep from matching '@aws-sdk' literal\n const cwPrefix = ['@aw', 's-sdk/clien', 't-cloudwatch'].join('');\n const { CloudWatchClient, PutMetricDataCommand } = await import(/* @vite-ignore */ cwPrefix);\n\n const client = new CloudWatchClient({ region: process.env['AWS_REGION'] });\n await client.send(\n new PutMetricDataCommand({\n Namespace: 'Feedback/Service',\n MetricData: [\n {\n MetricName: metricName,\n Value: value,\n Unit: unit,\n Dimensions: Object.entries(dimensions).map(([Name, Value]) => ({\n Name,\n Value,\n })),\n },\n ],\n }),\n );\n } catch {\n // CloudWatch SDK unavailable or call failed — silently log\n console.debug(`[feedback-metrics] ${metricName}=${value} ${unit}`, dimensions);\n }\n })();\n}\n"],"names":[],"mappings":"AA6BA,IAAI,UAA6B;AACjC,IAAI,eAA8B;AAO3B,SAAS,WAAW,SAAuB;AAEhD,MAAI,YAAY,QAAQ,iBAAiB,SAAS;AAChD;AAAA,EACF;AAEA,iBAAe;AAGf,QAAM,YAAY;AAChB,QAAI;AAEF,YAAM,aAAa;AACnB,YAAM,OAAO,MAAM,OAAO;AAC1B,gBAAW,KAAK,MAAM,UAAU,yBAAyB,OAAO,EAAE;AAAA,IACpE,QAAQ;AAEN,gBAAU;AAAA,IACZ;AAAA,EACF,GAAA;AACF;AAOA,MAAM,cAAc,EAAE,IAAI,GAAG,OAAO,EAAA;AAUpC,eAAsB,SACpB,eACA,YACA,WACY;AACZ,MAAI,YAAY,MAAM;AAEpB,WAAO,UAAU,IAAI;AAAA,EACvB;AAEA,QAAM,OAAO,QAAQ,UAAU,eAAe,EAAE,YAAY;AAC5D,MAAI;AACF,UAAM,SAAS,MAAM,UAAU,IAAI;AACnC,SAAK,UAAU,EAAE,MAAM,YAAY,IAAI;AACvC,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAM,YAAY;AAAA,MAClB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA,CAC/D;AACD,SAAK,gBAAgB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC9E,UAAM;AAAA,EACR,UAAA;AACE,SAAK,IAAA;AAAA,EACP;AACF;AAUO,SAAS,kBACd,YACA,SACA,aACM;AACN,OAAK;AAAA,IACH;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL;AAAA,MACA,aAAa,eAAe;AAAA,IAAA;AAAA,IAE9B,OAAO,UAAU;AAAA,EAAA;AAErB;AAMO,SAAS,uBACd,YACA,YACA,SACM;AACN,OAAK;AAAA,IACH;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,OAAO,UAAU;AAAA,EAAA;AAErB;AAcO,SAAS,WACd,YACA,OACA,MACA,YACM;AAEN,QAAM,YAAY;AAChB,QAAI,CAAC,QAAQ,IAAI,YAAY,GAAG;AAC9B,cAAQ,MAAM,sBAAsB,UAAU,IAAI,KAAK,IAAI,IAAI,IAAI,UAAU;AAC7E;AAAA,IACF;AAEA,QAAI;AAEF,YAAM,WAAW,CAAC,OAAO,eAAe,cAAc,EAAE,KAAK,EAAE;AAC/D,YAAM,EAAE,kBAAkB,qBAAA,IAAyB,MAAM;AAAA;AAAA,QAA0B;AAAA;AAEnF,YAAM,SAAS,IAAI,iBAAiB,EAAE,QAAQ,QAAQ,IAAI,YAAY,GAAG;AACzE,YAAM,OAAO;AAAA,QACX,IAAI,qBAAqB;AAAA,UACvB,WAAW;AAAA,UACX,YAAY;AAAA,YACV;AAAA,cACE,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,MAAM;AAAA,cACN,YAAY,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO;AAAA,gBAC7D;AAAA,gBACA;AAAA,cAAA,EACA;AAAA,YAAA;AAAA,UACJ;AAAA,QACF,CACD;AAAA,MAAA;AAAA,IAEL,QAAQ;AAEN,cAAQ,MAAM,sBAAsB,UAAU,IAAI,KAAK,IAAI,IAAI,IAAI,UAAU;AAAA,IAC/E;AAAA,EACF,GAAA;AACF;"}
1
+ {"version":3,"file":"observability-DUK725zv.js","sources":["../../src/core/observability.ts"],"sourcesContent":["/**\n * OpenTelemetry span helpers with zero AWS dependencies.\n *\n * @opentelemetry/api is declared as an optional peer dependency.\n * If the consuming app has it installed, traces are emitted.\n * If not, functions degrade gracefully to no-ops.\n *\n * CRITICAL: No hard import of '@opentelemetry/api' — only dynamic import.\n * Dynamic import used for runtime availability check.\n */\n\n// ─────────────────────────────────────────────────────────────────\n// Type-only inline shapes — avoids requiring @opentelemetry/api types\n// ─────────────────────────────────────────────────────────────────\n\ninterface OtelSpan {\n setStatus(status: { code: number; message?: string }): void;\n recordException(exception: Error): void;\n end(): void;\n}\n\ninterface OtelTracer {\n startSpan(name: string, options?: { attributes?: Record<string, string | number | boolean> }): OtelSpan;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Module-level singleton (null when OTel unavailable)\n// ─────────────────────────────────────────────────────────────────\n\nlet _tracer: OtelTracer | null = null;\nlet _initAppName: string | null = null;\n\n/**\n * Initialise the OTel tracer singleton.\n * Idempotent — calling twice with the same appName is a no-op.\n * Safe to call when @opentelemetry/api is not installed (no-op).\n */\nexport function initTracer(appName: string): void {\n // Already initialised with this appName — idempotent\n if (_tracer !== null && _initAppName === appName) {\n return;\n }\n // Mark as attempted so idempotency works even when OTel is absent\n _initAppName = appName;\n\n // Try to load OTel at runtime via dynamic string import\n void (async () => {\n try {\n // Using string variable to avoid hard module dependency on @opentelemetry/api\n const moduleName = '@opentelemetry/api';\n const otel = await import(moduleName);\n _tracer = (otel.trace.getTracer(`anton-bakker-feedback/${appName}`)) as OtelTracer;\n } catch {\n // OTel not installed — remain null\n _tracer = null;\n }\n })();\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Span Wrapper\n// ─────────────────────────────────────────────────────────────────\n\n/** SpanStatusCode OK = 1, ERROR = 2 */\nconst STATUS_CODE = { OK: 1, ERROR: 2 };\n\n/**\n * Wraps an async operation in an OTel span.\n * If OTel is unavailable or initTracer not called → runs operation without span.\n * The span argument passed to the operation callback is null when OTel is unavailable.\n *\n * Span name convention: feedback.{operation} (e.g., feedback.submit, feedback.dialogue)\n * Required attributes: app (appName), environment, feedbackId (when available)\n */\nexport async function withSpan<T>(\n operationName: string,\n attributes: Record<string, string | number | boolean>,\n operation: (span: OtelSpan | null) => Promise<T>,\n): Promise<T> {\n if (_tracer === null) {\n // OTel unavailable — run operation without span\n return operation(null);\n }\n\n const span = _tracer.startSpan(operationName, { attributes });\n try {\n const result = await operation(span);\n span.setStatus({ code: STATUS_CODE.OK });\n return result;\n } catch (error) {\n span.setStatus({\n code: STATUS_CODE.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n throw error;\n } finally {\n span.end();\n }\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Convenience Event Recorders\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Records a feedback submission span event.\n * No-op when OTel unavailable.\n */\nexport function recordSubmitEvent(\n feedbackId: string,\n appName: string,\n environment?: string,\n): void {\n void withSpan(\n 'feedback.submit',\n {\n app: appName,\n feedbackId,\n environment: environment ?? 'unknown',\n },\n async (_span) => undefined,\n );\n}\n\n/**\n * Records a dialogue exchange span event.\n * No-op when OTel unavailable.\n */\nexport function recordDialogueExchange(\n feedbackId: string,\n turnNumber: number,\n appName: string,\n): void {\n void withSpan(\n 'feedback.dialogue',\n {\n app: appName,\n feedbackId,\n turnNumber,\n },\n async (_span) => undefined,\n );\n}\n\n// ─────────────────────────────────────────────────────────────────\n// CloudWatch Metric Emission (fire-and-forget)\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Emits a metric to CloudWatch (fire-and-forget).\n *\n * Only active when process.env.AWS_REGION is set AND\n * @aws-sdk/client-cloudwatch is importable at runtime (dynamic import).\n * If either condition is absent, logs to console.debug instead.\n * NEVER throws.\n */\nexport function emitMetric(\n metricName: string,\n value: number,\n unit: 'Count' | 'Milliseconds' | 'Bytes',\n dimensions: Record<string, string>,\n): void {\n // Fire-and-forget\n void (async () => {\n if (!process.env['AWS_REGION']) {\n console.debug(`[feedback-metrics] ${metricName}=${value} ${unit}`, dimensions);\n return;\n }\n\n try {\n // Optional runtime dependency — not a declared dependency of this package. The dynamic import\n // keeps it external/tree-shaken (@vite-ignore); typing the specifier as `string` tells the\n // compiler not to resolve this optional module at build time (it isn't installed here). This\n // is NOT import obfuscation — the module id is a plain literal; it is only widened to `string`\n // so an absent optional dep doesn't fail the build. The host supplies the SDK when AWS_REGION\n // is set; otherwise the import throws and we fall through to console.debug.\n const cloudwatchModuleId: string = '@aws-sdk/client-cloudwatch';\n const { CloudWatchClient, PutMetricDataCommand } = await import(/* @vite-ignore */ cloudwatchModuleId);\n\n const client = new CloudWatchClient({ region: process.env['AWS_REGION'] });\n await client.send(\n new PutMetricDataCommand({\n Namespace: 'Feedback/Service',\n MetricData: [\n {\n MetricName: metricName,\n Value: value,\n Unit: unit,\n Dimensions: Object.entries(dimensions).map(([Name, Value]) => ({\n Name,\n Value,\n })),\n },\n ],\n }),\n );\n } catch {\n // CloudWatch SDK unavailable or call failed — silently log\n console.debug(`[feedback-metrics] ${metricName}=${value} ${unit}`, dimensions);\n }\n })();\n}\n"],"names":[],"mappings":"AA6BA,IAAI,UAA6B;AACjC,IAAI,eAA8B;AAO3B,SAAS,WAAW,SAAuB;AAEhD,MAAI,YAAY,QAAQ,iBAAiB,SAAS;AAChD;AAAA,EACF;AAEA,iBAAe;AAGf,QAAM,YAAY;AAChB,QAAI;AAEF,YAAM,aAAa;AACnB,YAAM,OAAO,MAAM,OAAO;AAC1B,gBAAW,KAAK,MAAM,UAAU,yBAAyB,OAAO,EAAE;AAAA,IACpE,QAAQ;AAEN,gBAAU;AAAA,IACZ;AAAA,EACF,GAAA;AACF;AAOA,MAAM,cAAc,EAAE,IAAI,GAAG,OAAO,EAAA;AAUpC,eAAsB,SACpB,eACA,YACA,WACY;AACZ,MAAI,YAAY,MAAM;AAEpB,WAAO,UAAU,IAAI;AAAA,EACvB;AAEA,QAAM,OAAO,QAAQ,UAAU,eAAe,EAAE,YAAY;AAC5D,MAAI;AACF,UAAM,SAAS,MAAM,UAAU,IAAI;AACnC,SAAK,UAAU,EAAE,MAAM,YAAY,IAAI;AACvC,WAAO;AAAA,EACT,SAAS,OAAO;AACd,SAAK,UAAU;AAAA,MACb,MAAM,YAAY;AAAA,MAClB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAAA,CAC/D;AACD,SAAK,gBAAgB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC9E,UAAM;AAAA,EACR,UAAA;AACE,SAAK,IAAA;AAAA,EACP;AACF;AAUO,SAAS,kBACd,YACA,SACA,aACM;AACN,OAAK;AAAA,IACH;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL;AAAA,MACA,aAAa,eAAe;AAAA,IAAA;AAAA,IAE9B,OAAO,UAAU;AAAA,EAAA;AAErB;AAMO,SAAS,uBACd,YACA,YACA,SACM;AACN,OAAK;AAAA,IACH;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,OAAO,UAAU;AAAA,EAAA;AAErB;AAcO,SAAS,WACd,YACA,OACA,MACA,YACM;AAEN,QAAM,YAAY;AAChB,QAAI,CAAC,QAAQ,IAAI,YAAY,GAAG;AAC9B,cAAQ,MAAM,sBAAsB,UAAU,IAAI,KAAK,IAAI,IAAI,IAAI,UAAU;AAC7E;AAAA,IACF;AAEA,QAAI;AAOF,YAAM,qBAA6B;AACnC,YAAM,EAAE,kBAAkB,qBAAA,IAAyB,MAAM;AAAA;AAAA,QAA0B;AAAA;AAEnF,YAAM,SAAS,IAAI,iBAAiB,EAAE,QAAQ,QAAQ,IAAI,YAAY,GAAG;AACzE,YAAM,OAAO;AAAA,QACX,IAAI,qBAAqB;AAAA,UACvB,WAAW;AAAA,UACX,YAAY;AAAA,YACV;AAAA,cACE,YAAY;AAAA,cACZ,OAAO;AAAA,cACP,MAAM;AAAA,cACN,YAAY,OAAO,QAAQ,UAAU,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO;AAAA,gBAC7D;AAAA,gBACA;AAAA,cAAA,EACA;AAAA,YAAA;AAAA,UACJ;AAAA,QACF,CACD;AAAA,MAAA;AAAA,IAEL,QAAQ;AAEN,cAAQ,MAAM,sBAAsB,UAAU,IAAI,KAAK,IAAI,IAAI,IAAI,UAAU;AAAA,IAC/E;AAAA,EACF,GAAA;AACF;"}
@@ -1,4 +1,5 @@
1
- import { w as withSpan } from "./observability-CfwHKObh.js";
1
+ import { w as withSpan } from "./observability-DUK725zv.js";
2
+ import { a as createIngestTransport } from "./ingest-transport-DAA_pJNb.js";
2
3
  const VALID_TYPES = ["bug", "feature", "ux", "other"];
3
4
  function validatePayload(body) {
4
5
  if (!body || typeof body !== "object" || Array.isArray(body)) {
@@ -36,34 +37,61 @@ function validatePayload(body) {
36
37
  }
37
38
  };
38
39
  }
40
+ function resolveTransport(config) {
41
+ if (config.transport) return config.transport;
42
+ if (config.apiUrl && (config.tokenProvider || config.tokenUrl && config.clientId && config.clientSecret)) {
43
+ return createIngestTransport(config);
44
+ }
45
+ return null;
46
+ }
47
+ function buildStoredItem(payload, config, feedbackId, now) {
48
+ return {
49
+ PK: `PROJECT#${config.appName}`,
50
+ SK: `FB#${now}#${feedbackId}`,
51
+ GSI1PK: "STATUS#open",
52
+ GSI1SK: `015#${feedbackId}`,
53
+ GSI2PK: `TYPE#${payload.type}#PROJECT#${config.appName}`,
54
+ GSI2SK: now,
55
+ id: feedbackId,
56
+ appName: config.appName,
57
+ type: payload.type,
58
+ status: "new",
59
+ ...payload.scope !== void 0 && { scope: payload.scope },
60
+ ...payload.size !== void 0 && { size: payload.size },
61
+ priority: 0,
62
+ content: payload.title,
63
+ context: {
64
+ url: payload.systemInfo?.url ?? "",
65
+ ...payload.systemInfo?.userAgent !== void 0 && { userAgent: payload.systemInfo.userAgent },
66
+ ...payload.systemInfo?.viewport !== void 0 && { viewport: payload.systemInfo.viewport },
67
+ ...payload.systemInfo?.locale !== void 0 && { locale: payload.systemInfo.locale },
68
+ ...payload.consoleErrors !== void 0 && { consoleErrors: payload.consoleErrors }
69
+ },
70
+ hasScreenshot: Array.isArray(payload.screenshots) && payload.screenshots.length > 0,
71
+ submittedAt: now,
72
+ lastModifiedAt: now,
73
+ submittedBy: "",
74
+ reporters: [],
75
+ reporterCount: 0,
76
+ dialogueHistory: [],
77
+ corrections: [],
78
+ environment: config.environment ?? "production",
79
+ tags: []
80
+ };
81
+ }
39
82
  async function storeFeedback(payload, config, feedbackId) {
40
- if (!config.tableName || !config.awsRegion) {
83
+ const transport = resolveTransport(config);
84
+ if (!transport) {
41
85
  return;
42
86
  }
43
- const dynamoPrefix = ["@aws", "-sdk/client-dynamo", "db"].join("");
44
- const { DynamoDBClient, PutItemCommand } = await import(
45
- /* @vite-ignore */
46
- dynamoPrefix
47
- );
48
- const client = new DynamoDBClient({ region: config.awsRegion });
49
87
  const now = (/* @__PURE__ */ new Date()).toISOString();
50
- await client.send(
51
- new PutItemCommand({
52
- TableName: config.tableName,
53
- Item: {
54
- PK: { S: `PROJECT#${config.appName}` },
55
- SK: { S: `FB#${now}#${feedbackId}` },
56
- id: { S: feedbackId },
57
- appName: { S: config.appName },
58
- type: { S: payload.type },
59
- status: { S: "open" },
60
- content: { S: payload.title },
61
- submittedAt: { S: now },
62
- lastModifiedAt: { S: now },
63
- environment: { S: config.environment ?? "production" }
64
- }
65
- })
66
- );
88
+ const item = buildStoredItem(payload, config, feedbackId, now);
89
+ const result = await transport.submit(item);
90
+ if (!result.success) {
91
+ const err = new Error(result.error ?? "ingest write failed");
92
+ err.retryable = result.retryable ?? true;
93
+ throw err;
94
+ }
67
95
  }
68
96
  function generateId() {
69
97
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -101,21 +129,14 @@ function createIntakeResolver(config) {
101
129
  }
102
130
  res.status(201).json({
103
131
  id: feedbackId,
104
- status: "open"
132
+ status: "new"
105
133
  });
106
134
  } catch (storageError) {
107
- const isDynamoUnavailable = storageError instanceof Error && (storageError.name === "ServiceUnavailableException" || storageError.name === "ProvisionedThroughputExceededException" || storageError.message.includes("ECONNREFUSED") || storageError.message.includes("unavailable") || storageError.message.includes("DynamoDB"));
108
- if (isDynamoUnavailable) {
109
- res.status(503).json({
110
- retryable: true,
111
- error: "Storage service temporarily unavailable. Please try again."
112
- });
113
- } else {
114
- res.status(503).json({
115
- retryable: true,
116
- error: "Failed to store feedback. Please try again."
117
- });
118
- }
135
+ const retryable = storageError instanceof Error && storageError.retryable !== false;
136
+ res.status(503).json({
137
+ retryable,
138
+ error: retryable ? "Storage service temporarily unavailable. Please try again." : "Failed to store feedback."
139
+ });
119
140
  }
120
141
  }
121
142
  );
@@ -130,4 +151,4 @@ function createIntakeResolver(config) {
130
151
  export {
131
152
  createIntakeResolver as c
132
153
  };
133
- //# sourceMappingURL=submitResolver-DsfkrUHY.js.map
154
+ //# sourceMappingURL=submitResolver-wfr4kbEM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submitResolver-wfr4kbEM.js","sources":["../../src/react/resolver/submitResolver.ts"],"sourcesContent":["/**\n * submitResolver — CRUD resolver for the feedback intake submit path.\n *\n * Server-only module — NOT imported by the client bundle.\n * Tree-shakeable: the /react subpath export does NOT re-export this.\n *\n * Factory: createIntakeResolver(config: SubmitHandlerConfig): RequestHandler\n *\n * HTTP contract:\n * POST /api/feedback/submit\n * Content-Type: application/json\n * Body: SubmitPayload\n * → 201 { id: string; status: string }\n * → 400 { errors: string[] }\n * → 503 { retryable: true; error: string }\n *\n * OTel span: 'feedback.submit' with attributes:\n * app, feedbackId (after store), environment\n *\n * Storage: writes via the authenticated central ingest API (`createIngestTransport().submit()`),\n * NOT direct DynamoDB. No AWS SDK import in this file.\n *\n * No Bedrock dependency — resolver calls submit handler only, not dialogue handler.\n */\n\nimport { withSpan } from '../../core/observability.js';\nimport { createIngestTransport, type IngestTransportConfig } from '../../core/ingest-transport.js';\nimport type { FeedbackSubmitTransport } from '../../core/transport.js';\nimport type {\n FeedbackEnvironment,\n FeedbackType,\n FeedbackScope,\n FeedbackSize,\n StoredFeedbackItem,\n} from '../../core/types.js';\n\n// ─────────────────────────────────────────────────────────────────\n// Request / Response types\n// ─────────────────────────────────────────────────────────────────\n\n/** Minimal HTTP request shape (Lambda/Express compatible) */\nexport interface RequestBody {\n headers?: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\n/** Minimal HTTP response shape (Lambda/Express compatible) */\nexport interface ResponseBody {\n statusCode: number;\n headers: Record<string, string>;\n body: string;\n}\n\n/** Express-compatible RequestHandler */\nexport type RequestHandler = (\n req: RequestBody,\n res: {\n status: (code: number) => { json: (body: unknown) => void };\n json: (body: unknown) => void;\n },\n) => Promise<void>;\n\n// ─────────────────────────────────────────────────────────────────\n// Submit Payload (what the client POSTs)\n// ─────────────────────────────────────────────────────────────────\n\nexport interface SubmitPayload {\n title: string;\n description?: string | undefined;\n type: FeedbackType;\n scope?: FeedbackScope | undefined;\n size?: FeedbackSize | undefined;\n screenshots?: Array<{ dataUrl: string; capturedAt: string }> | undefined;\n consoleErrors?: string[] | undefined;\n systemInfo?: {\n url?: string | undefined;\n userAgent?: string | undefined;\n viewport?: { width: number; height: number } | undefined;\n locale?: string | undefined;\n } | undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Config\n// ─────────────────────────────────────────────────────────────────\n\nexport interface SubmitHandlerConfig extends Partial<IngestTransportConfig> {\n /** Application name — used in OTel attributes and feedback ID */\n appName: string;\n /** Deployment environment */\n environment?: FeedbackEnvironment;\n /**\n * Pre-constructed submit transport (tests / custom wiring). Takes precedence over ingest config.\n * When neither a transport nor ingest config is supplied, storage is skipped (Minimal level).\n */\n transport?: FeedbackSubmitTransport;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Validation\n// ─────────────────────────────────────────────────────────────────\n\nconst VALID_TYPES: FeedbackType[] = ['bug', 'feature', 'ux', 'other'];\n\nfunction validatePayload(body: unknown): { valid: true; payload: SubmitPayload } | { valid: false; errors: string[] } {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return { valid: false, errors: ['Request body must be a JSON object'] };\n }\n\n const obj = body as Record<string, unknown>;\n const errors: string[] = [];\n\n // title: required, string, min 1 char\n if (!obj['title'] || typeof obj['title'] !== 'string' || (obj['title'] as string).trim().length === 0) {\n errors.push('title is required and must be a non-empty string');\n } else if ((obj['title'] as string).trim().length < 1) {\n errors.push('title must be at least 1 character');\n }\n\n // type: required, must be valid\n if (!obj['type']) {\n errors.push('type is required');\n } else if (!VALID_TYPES.includes(obj['type'] as FeedbackType)) {\n errors.push(`type must be one of: ${VALID_TYPES.join(', ')} (got: ${String(obj['type'])})`);\n }\n\n // description: optional string\n if (obj['description'] !== undefined && typeof obj['description'] !== 'string') {\n errors.push('description must be a string if provided');\n }\n\n if (errors.length > 0) {\n return { valid: false, errors };\n }\n\n return {\n valid: true,\n payload: {\n title: (obj['title'] as string).trim(),\n ...(obj['description'] !== undefined && { description: obj['description'] as string }),\n type: obj['type'] as FeedbackType,\n ...(obj['scope'] !== undefined && { scope: obj['scope'] as FeedbackScope }),\n ...(obj['size'] !== undefined && { size: obj['size'] as FeedbackSize }),\n ...(obj['screenshots'] !== undefined && { screenshots: obj['screenshots'] as SubmitPayload['screenshots'] }),\n ...(obj['consoleErrors'] !== undefined && { consoleErrors: obj['consoleErrors'] as string[] }),\n ...(obj['systemInfo'] !== undefined && { systemInfo: obj['systemInfo'] as SubmitPayload['systemInfo'] }),\n },\n };\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Central-ingest store\n// ─────────────────────────────────────────────────────────────────\n\n/** Resolve a submit transport from config, or null at Minimal level (no storage wired). */\nfunction resolveTransport(config: SubmitHandlerConfig): FeedbackSubmitTransport | null {\n if (config.transport) return config.transport;\n if (config.apiUrl && (config.tokenProvider || (config.tokenUrl && config.clientId && config.clientSecret))) {\n return createIngestTransport(config as IngestTransportConfig);\n }\n return null;\n}\n\n/**\n * Build a StoredFeedbackItem from the submit payload. The server owns the canonical keys\n * (PK/SK/GSI*) and stamps them from the verified token identity, so the client-side values here are\n * placeholders — the transport strips them before sending the content.\n */\nfunction buildStoredItem(\n payload: SubmitPayload,\n config: SubmitHandlerConfig,\n feedbackId: string,\n now: string,\n): StoredFeedbackItem {\n return {\n PK: `PROJECT#${config.appName}`,\n SK: `FB#${now}#${feedbackId}`,\n GSI1PK: 'STATUS#open',\n GSI1SK: `015#${feedbackId}`,\n GSI2PK: `TYPE#${payload.type}#PROJECT#${config.appName}`,\n GSI2SK: now,\n id: feedbackId,\n appName: config.appName,\n type: payload.type,\n status: 'new',\n ...(payload.scope !== undefined && { scope: payload.scope }),\n ...(payload.size !== undefined && { size: payload.size }),\n priority: 0,\n content: payload.title,\n context: {\n url: payload.systemInfo?.url ?? '',\n ...(payload.systemInfo?.userAgent !== undefined && { userAgent: payload.systemInfo.userAgent }),\n ...(payload.systemInfo?.viewport !== undefined && { viewport: payload.systemInfo.viewport }),\n ...(payload.systemInfo?.locale !== undefined && { locale: payload.systemInfo.locale }),\n ...(payload.consoleErrors !== undefined && { consoleErrors: payload.consoleErrors }),\n },\n hasScreenshot: Array.isArray(payload.screenshots) && payload.screenshots.length > 0,\n submittedAt: now,\n lastModifiedAt: now,\n submittedBy: '',\n reporters: [],\n reporterCount: 0,\n dialogueHistory: [],\n corrections: [],\n environment: config.environment ?? 'production',\n tags: [],\n };\n}\n\n/**\n * Store the feedback via the authenticated central ingest API (`createIngestTransport().submit()`).\n * No-op at Minimal level (no transport/ingest config). Throws on failure so the handler maps it to\n * a 503 retryable response — the error carries `retryable` for the caller's classification.\n */\nasync function storeFeedback(\n payload: SubmitPayload,\n config: SubmitHandlerConfig,\n feedbackId: string,\n): Promise<void> {\n const transport = resolveTransport(config);\n if (!transport) {\n // Minimal level — no storage.\n return;\n }\n\n const now = new Date().toISOString();\n const item = buildStoredItem(payload, config, feedbackId, now);\n\n const result = await transport.submit(item);\n if (!result.success) {\n const err = new Error(result.error ?? 'ingest write failed') as Error & { retryable?: boolean };\n err.retryable = result.retryable ?? true;\n throw err;\n }\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Feedback ID generation\n// ─────────────────────────────────────────────────────────────────\n\nfunction generateId(): string {\n const date = new Date().toISOString().slice(0, 10);\n const uuid8 = crypto.randomUUID().replace(/-/g, '').slice(0, 8);\n return `FB-${date}-${uuid8}`;\n}\n\n// ─────────────────────────────────────────────────────────────────\n// Handler Factory\n// ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates an Express/Lambda-compatible request handler for the submit endpoint.\n *\n * Validates payload → stores via the central ingest API (if configured) → returns 201 with ID.\n * Returns 503 with retryable:true when the ingest write fails.\n * No Bedrock dependency.\n */\nexport function createIntakeResolver(config: SubmitHandlerConfig): RequestHandler {\n return async (req, res) => {\n // ── Content-Type validation ───────────────────────────────────\n const contentType = (req.headers?.['content-type'] ?? '').toString();\n if (!contentType.includes('application/json')) {\n res.status(400).json({\n errors: ['Content-Type must be application/json'],\n });\n return;\n }\n\n // ── Body validation ───────────────────────────────────────────\n const validation = validatePayload(req.body);\n if (!validation.valid) {\n res.status(400).json({ errors: validation.errors });\n return;\n }\n\n const { payload } = validation;\n const feedbackId = generateId();\n\n // ── OTel span ─────────────────────────────────────────────────\n try {\n await withSpan(\n 'feedback.submit',\n {\n app: config.appName,\n environment: config.environment ?? 'production',\n },\n async (span) => {\n try {\n await storeFeedback(payload, config, feedbackId);\n\n // Attach feedbackId after successful store\n if (span) {\n (span as unknown as { setAttribute?: (k: string, v: string) => void })\n .setAttribute?.('feedbackId', feedbackId);\n }\n\n res.status(201).json({\n id: feedbackId,\n status: 'new',\n });\n } catch (storageError) {\n // storeFeedback throws on ingest failure; the thrown error carries `retryable`.\n const retryable =\n storageError instanceof Error &&\n (storageError as Error & { retryable?: boolean }).retryable !== false;\n\n res.status(503).json({\n retryable,\n error: retryable\n ? 'Storage service temporarily unavailable. Please try again.'\n : 'Failed to store feedback.',\n });\n }\n },\n );\n } catch {\n res.status(503).json({\n retryable: true,\n error: 'Internal error. Please try again.',\n });\n }\n };\n}\n"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ export * from './transport.js';
11
11
  export * from './ingest-transport.js';
12
12
  export * from './validation.js';
13
13
  export * from './transforms.js';
14
+ export * from './normalisers.js';
14
15
  export * from './observability.js';
15
16
  export * from './i18n.js';
16
17
  export * from './errors.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
@@ -1,9 +1,12 @@
1
+ import { StoredFeedbackItem, ActivityEntry, FeedbackStatus, Reporter } from './types.js';
1
2
  import { FeedbackSubmitTransport } from './transport.js';
2
3
 
3
4
  /** The ingest collection feedback writes to (the deployed registry maps it to `ralph-feedback`). */
4
5
  export declare const FEEDBACK_COLLECTION = "feedback";
5
6
  /** OAuth2 scope required to write via the ingest API. */
6
7
  export declare const INGEST_WRITE_SCOPE = "assets/write";
8
+ /** OAuth2 scope required to read via the ingest API. */
9
+ export declare const INGEST_READ_SCOPE = "assets/read";
7
10
  /** Minimal fetch surface (so the package needs no DOM lib types to compile). */
8
11
  export type FetchLike = (url: string, init?: {
9
12
  method?: string;
@@ -50,4 +53,98 @@ export interface IngestTransportConfig {
50
53
  * await transport.submit(item); // → { success, feedbackId }
51
54
  */
52
55
  export declare function createIngestTransport(config: IngestTransportConfig): FeedbackSubmitTransport;
56
+ /**
57
+ * The read half of the central-storage contract. `list()` mints/reuses a short-lived
58
+ * client-credentials JWT (scope `assets/read`), GETs `/records/{collection}` with a Bearer token,
59
+ * and returns the app's OWN-DATA partition as an array. The server derives the app from the verified
60
+ * token identity and returns only that app's items — there is NO server-side GSI query or SK range,
61
+ * so any status/limit/paging filtering is the caller's responsibility (applied client-side).
62
+ *
63
+ * Zero AWS SDK — `fetch`-only, mirroring `createIngestTransport`.
64
+ */
65
+ export interface FeedbackReader {
66
+ /** Read this app's own feedback partition. Returns an array (empty when the partition is empty). */
67
+ list(): Promise<StoredFeedbackItem[]>;
68
+ }
69
+ /**
70
+ * Create a reader that fetches this app's feedback partition via the central ingest API.
71
+ *
72
+ * Usage (host app wires the credential from its secret store):
73
+ * const reader = createIngestReader({
74
+ * apiUrl: process.env.FEEDBACK_INGEST_URL!,
75
+ * tokenUrl: process.env.FEEDBACK_TOKEN_URL!,
76
+ * clientId: process.env.FEEDBACK_CLIENT_ID!,
77
+ * clientSecret: await resolveSecret('ralph/ingest/feedback'),
78
+ * });
79
+ * const items = await reader.list(); // → StoredFeedbackItem[]
80
+ */
81
+ export declare function createIngestReader(config: IngestTransportConfig): FeedbackReader;
82
+ /**
83
+ * Result of an annotate or transition operation.
84
+ * Mirrors the existing SubmitResult shape with the addition of conflictReason
85
+ * for classifying non-retryable server rejections.
86
+ */
87
+ export interface AnnotateResult {
88
+ success: boolean;
89
+ feedbackId: string;
90
+ error?: string;
91
+ retryable?: boolean;
92
+ /** Specific reason for a server rejection (HTTP 409/403/404). */
93
+ conflictReason?: 'stale_status' | 'missing_capability' | 'not_found';
94
+ }
95
+ /**
96
+ * Options for transitionStatus operation.
97
+ * Supports optimistic concurrency control (currentStatus), duplicate merging
98
+ * (duplicateOf + duplicateReporters), and metadata (reason, assignee).
99
+ */
100
+ export interface TransitionStatusOptions {
101
+ /** Expected current status for optimistic concurrency control. Server returns HTTP 409 if stale. */
102
+ currentStatus?: FeedbackStatus;
103
+ /** Human-readable reason for the transition. */
104
+ reason?: string;
105
+ /** User ID to assign the item to. */
106
+ assignee?: string;
107
+ /** Feedback ID this item is a duplicate of (when transitioning to 'duplicate'). */
108
+ duplicateOf?: string;
109
+ /** Reporter list from the duplicate item to merge onto the canonical item atomically. */
110
+ duplicateReporters?: Reporter[];
111
+ }
112
+ /**
113
+ * The annotate/transition half of the central-storage contract.
114
+ * Provides atomic operations for appending activity and transitioning status.
115
+ */
116
+ export interface FeedbackAnnotateClient {
117
+ /**
118
+ * Atomically append an activity entry to a feedback item.
119
+ * Maps to POST /records/feedback/{feedbackId}/activity
120
+ */
121
+ appendActivity(feedbackId: string, entry: ActivityEntry): Promise<AnnotateResult>;
122
+ /**
123
+ * Atomically transition a feedback item's status with optimistic concurrency control.
124
+ * Maps to PATCH /records/feedback/{feedbackId}/status
125
+ */
126
+ transitionStatus(feedbackId: string, toStatus: FeedbackStatus, opts?: TransitionStatusOptions): Promise<AnnotateResult>;
127
+ }
128
+ /**
129
+ * Create a FeedbackAnnotateClient that writes to the central ingest API.
130
+ * Reuses the same config validation and token management as createIngestTransport.
131
+ *
132
+ * Error mapping (all returned as AnnotateResult, never thrown):
133
+ * - HTTP 409 → conflictReason='stale_status', retryable=false
134
+ * - HTTP 403 → conflictReason='missing_capability', retryable=false
135
+ * - HTTP 404 → conflictReason='not_found', retryable=false
136
+ * - HTTP 5xx → retryable=true
137
+ * - fetch throws → retryable=true
138
+ *
139
+ * Usage:
140
+ * const annotateClient = createIngestAnnotateClient({
141
+ * apiUrl: process.env.FEEDBACK_INGEST_URL!,
142
+ * tokenUrl: process.env.FEEDBACK_TOKEN_URL!,
143
+ * clientId: process.env.FEEDBACK_CLIENT_ID!,
144
+ * clientSecret: await resolveSecret('feedback/ingest/<app>'),
145
+ * });
146
+ * await annotateClient.appendActivity(feedbackId, activityEntry);
147
+ * await annotateClient.transitionStatus(feedbackId, 'triaged', { currentStatus: 'new' });
148
+ */
149
+ export declare function createIngestAnnotateClient(config: IngestTransportConfig): FeedbackAnnotateClient;
53
150
  //# sourceMappingURL=ingest-transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ingest-transport.d.ts","sourceRoot":"","sources":["../../src/core/ingest-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAM9D,oGAAoG;AACpG,eAAO,MAAM,mBAAmB,aAAa,CAAC;AAC9C,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACxE,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,wFAAwF;IACxF,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oGAAoG;IACpG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AA8BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,uBAAuB,CAkE5F"}
1
+ {"version":3,"file":"ingest-transport.d.ts","sourceRoot":"","sources":["../../src/core/ingest-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { StoredFeedbackItem, FeedbackStatus, LegacyFeedbackStatus } from './types.js';
2
+
3
+ /**
4
+ * Map a legacy status value to the new QA lifecycle. New values pass through unchanged.
5
+ * Unknown values fall back to 'new' (Tolerant Reader — never throw on stored data).
6
+ *
7
+ * @param status - a legacy or new status value
8
+ * @returns the equivalent new-lifecycle FeedbackStatus
9
+ */
10
+ export declare function normaliseLegacyStatus(status: LegacyFeedbackStatus | FeedbackStatus): FeedbackStatus;
11
+ /**
12
+ * Merge legacy dialogueHistory + corrections into activity[]. Idempotent: entries already
13
+ * present (by stable id) are not re-appended, so applying twice equals applying once.
14
+ * Returns a new object; never mutates the input. Stored data is never rewritten.
15
+ */
16
+ export declare function normaliseActivityArrays(item: StoredFeedbackItem): StoredFeedbackItem;
17
+ /**
18
+ * Tolerant-Reader fallback for the structured report: if report.actual is not set and the
19
+ * legacy `content` blob is non-empty, set report.actual = content. The content field is
20
+ * always preserved. Returns a new object; never mutates the input.
21
+ */
22
+ export declare function normaliseReportContent(item: StoredFeedbackItem): StoredFeedbackItem;
23
+ /**
24
+ * Apply all read-time normalisers in sequence: legacy status → activity merge → report
25
+ * content fallback. Pure and idempotent; returns a new object and never mutates the input.
26
+ * Rendering/consumer code should call this before reading status or any optional field.
27
+ */
28
+ export declare function normaliseStoredItem(item: StoredFeedbackItem): StoredFeedbackItem;
29
+ //# sourceMappingURL=normalisers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalisers.d.ts","sourceRoot":"","sources":["../../src/core/normalisers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EAMrB,MAAM,YAAY,CAAC;AAuBpB;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,GAAG,cAAc,CAInG;AAuDD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAmBpF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAOnF;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,kBAAkB,GAAG,kBAAkB,CAKhF"}
@@ -1 +1 @@
1
- {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../../src/core/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,UAAU,QAAQ;IAChB,SAAS,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,eAAe,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC;IACxC,GAAG,IAAI,IAAI,CAAC;CACb;AAaD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAoBhD;AASD;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACrD,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC/C,OAAO,CAAC,CAAC,CAAC,CAqBZ;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAUN;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAUN;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,OAAO,EACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,IAAI,CAmCN"}
1
+ {"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../../src/core/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,UAAU,QAAQ;IAChB,SAAS,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,eAAe,CAAC,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC;IACxC,GAAG,IAAI,IAAI,CAAC;CACb;AAaD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAoBhD;AASD;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EACrD,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAC/C,OAAO,CAAC,CAAC,CAAC,CAqBZ;AAMD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAUN;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,IAAI,CAUN;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,OAAO,EACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,IAAI,CAwCN"}
@@ -0,0 +1,19 @@
1
+ import { ActivityEntry, StateDuration } from './types.js';
2
+
3
+ export type { StateDuration };
4
+ /**
5
+ * Derives state durations from an activity stream.
6
+ * Filters status_change entries, pairs them chronologically to produce one StateDuration per state visit.
7
+ * The current state (last entry) has undefined exitedAt and durationMs = time from entry to now.
8
+ *
9
+ * @param activity - Array of ActivityEntry objects (may be empty or undefined)
10
+ * @returns Array of StateDuration objects, one per state visit
11
+ *
12
+ * @example
13
+ * const durations = deriveStateDurations(item.activity);
14
+ * const mttr = durations
15
+ * .filter(d => ['in_progress', 'fixed', 'pending_retest'].includes(d.status))
16
+ * .reduce((sum, d) => sum + d.durationMs, 0);
17
+ */
18
+ export declare function deriveStateDurations(activity: ActivityEntry[] | undefined): StateDuration[];
19
+ //# sourceMappingURL=state-metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-metrics.d.ts","sourceRoot":"","sources":["../../src/core/state-metrics.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAkB,aAAa,EAAE,MAAM,YAAY,CAAC;AAG/E,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,aAAa,EAAE,CAyC3F"}
@@ -1 +1 @@
1
- {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/core/transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAMpB,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,YAAY,CAad;AAMD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAYrE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAYnF;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,eAAe,GAAG,QAAQ,CAAC,CAmBpE;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,eAAe,EAAE,EAC9B,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,EAChD,MAAM,CAAC,EAAE,MAAM,GACd,eAAe,EAAE,CAMnB;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EACxB,SAAS,EAAE,cAAc,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CACL,kBAAkB,EAClB,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAChF,CAmBA"}
1
+ {"version":3,"file":"transforms.d.ts","sourceRoot":"","sources":["../../src/core/transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,kBAAkB,EAClB,eAAe,EAEhB,MAAM,YAAY,CAAC;AAMpB,gDAAgD;AAChD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,YAAY,CAad;AAMD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAYrE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAYnF;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAAC,kBAAkB,EAAE,WAAW,GAAG,eAAe,GAAG,QAAQ,CAAC,CAmBpE;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,eAAe,EAAE,EAC9B,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,EAChD,MAAM,CAAC,EAAE,MAAM,GACd,eAAe,EAAE,CAMnB;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,kBAAkB,EACxB,SAAS,EAAE,cAAc,EACzB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CACL,kBAAkB,EAClB,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAChF,CAqBA"}
@@ -7,16 +7,69 @@
7
7
  /** Type of feedback being submitted */
8
8
  export type FeedbackType = 'bug' | 'feature' | 'ux' | 'other';
9
9
  /**
10
- * Lifecycle status of a feedback item.
11
- * Exactly: 'open' | 'approved' | 'deferred' | 'resolved' | 'wontfix'
10
+ * Lifecycle status of a feedback item (12-state QA lifecycle).
11
+ * Widened from 5 legacy states — see LegacyFeedbackStatus below.
12
12
  */
13
- export type FeedbackStatus = 'open' | 'approved' | 'deferred' | 'resolved' | 'wontfix';
13
+ export type FeedbackStatus = 'new' | 'triaged' | 'assigned' | 'in_progress' | 'fixed' | 'pending_retest' | 'verified' | 'closed' | 'deferred' | 'rejected' | 'duplicate' | 'reopened';
14
+ /**
15
+ * Legacy 5-state status set (pre-12-state QA lifecycle).
16
+ * For normaliser reference only — 4 of 5 values absent from new FeedbackStatus.
17
+ */
18
+ export type LegacyFeedbackStatus = 'open' | 'approved' | 'deferred' | 'resolved' | 'wontfix';
14
19
  /** Scope of the feedback issue */
15
20
  export type FeedbackScope = 'isolated' | 'cross-cutting' | 'systemic';
16
21
  /** Estimated size/effort for the feedback item */
17
22
  export type FeedbackSize = 'trivial' | 'small' | 'medium' | 'large';
18
23
  /** Environment where feedback was submitted */
19
24
  export type FeedbackEnvironment = 'development' | 'staging' | 'production';
25
+ /**
26
+ * Severity level (reporter-set) — DISTINCT from priority (team-calculated).
27
+ * IEEE 1044 separates severity (reporter's observation) from priority (team's scheduling decision).
28
+ */
29
+ export type SeverityLevel = 'blocker' | 'major' | 'minor' | 'cosmetic';
30
+ /**
31
+ * How reliably the issue can be reproduced.
32
+ */
33
+ export type Reproducibility = 'always' | 'sometimes' | 'once' | 'unknown';
34
+ /**
35
+ * Detection activity — which phase/test/charter surfaced this issue.
36
+ * Closed enum per IEEE 1044 lifecycle phases for ODC analytical value.
37
+ */
38
+ export type DetectionActivity = 'requirements_review' | 'design_review' | 'code_inspection' | 'unit_test' | 'integration_test' | 'system_test' | 'acceptance_test' | 'operational_use';
39
+ /**
40
+ * ODC (Orthogonal Defect Classification) root cause type.
41
+ * 8-value ODC Defect Type taxonomy for defect distribution analysis.
42
+ */
43
+ export type OdcRootCauseType = 'function' | 'assignment' | 'interface' | 'checking' | 'timing_serialization' | 'build_package_merge' | 'documentation' | 'algorithm';
44
+ /**
45
+ * Activity event kind — every event type a feedback item can produce.
46
+ */
47
+ export type ActivityKind = 'comment' | 'note' | 'question' | 'answer' | 'status_change' | 'assignment' | 'classification_change' | 'reopen';
48
+ /**
49
+ * Actor who performed an activity.
50
+ */
51
+ export interface ActivityActor {
52
+ userId: string;
53
+ displayName: string;
54
+ }
55
+ /**
56
+ * Append-only activity entry — canonical event model.
57
+ * before/after typed as unknown for generic usage across all event kinds.
58
+ * schemaVersion enables future upcasting without ambiguity.
59
+ */
60
+ export interface ActivityEntry {
61
+ id: string;
62
+ actor: ActivityActor;
63
+ occurredAt: string;
64
+ recordedAt: string;
65
+ source: string;
66
+ kind: ActivityKind;
67
+ body?: string;
68
+ before?: unknown;
69
+ after?: unknown;
70
+ reason?: string;
71
+ schemaVersion: number;
72
+ }
20
73
  /** A reporter who submitted or upvoted a feedback item */
21
74
  export interface Reporter {
22
75
  userId: string;
@@ -38,6 +91,35 @@ export interface CorrectionEntry {
38
91
  correctedAt: string;
39
92
  reason?: string;
40
93
  }
94
+ /**
95
+ * Structured report fields — decomposition of the content blob.
96
+ * Supports better matching and deduplication.
97
+ */
98
+ export interface ReportFields {
99
+ expected?: string;
100
+ actual?: string;
101
+ stepsToReproduce?: string[];
102
+ }
103
+ /**
104
+ * Resolution traceability fields per IEEE 1044.
105
+ * disposition is required; commit/version/verification are optional.
106
+ */
107
+ export interface ResolutionFields {
108
+ fixCommit?: string;
109
+ fixVersion?: string;
110
+ verifiedIn?: string;
111
+ disposition: string;
112
+ }
113
+ /**
114
+ * Duration spent in a particular state.
115
+ * exitedAt is undefined for the current state.
116
+ */
117
+ export interface StateDuration {
118
+ status: FeedbackStatus;
119
+ enteredAt: string;
120
+ exitedAt?: string;
121
+ durationMs: number;
122
+ }
41
123
  /** Browser and server context captured at submission time */
42
124
  export interface FeedbackContext {
43
125
  url: string;
@@ -93,6 +175,14 @@ export interface StoredFeedbackItem {
93
175
  reporterCount: number;
94
176
  dialogueHistory: DialogueEntry[];
95
177
  corrections: CorrectionEntry[];
178
+ activity?: ActivityEntry[];
179
+ severity?: SeverityLevel;
180
+ report?: ReportFields;
181
+ reproducibility?: Reproducibility;
182
+ lastWorkingVersion?: string;
183
+ resolution?: ResolutionFields;
184
+ detectionActivity?: DetectionActivity;
185
+ rootCauseType?: OdcRootCauseType;
96
186
  environment?: FeedbackEnvironment;
97
187
  version?: string;
98
188
  tags: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,uCAAuC;AACvC,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC;AAE9D;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAEtL;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7F,kCAAkC;AAClC,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,eAAe,GAAG,UAAU,CAAC;AAEtE,kDAAkD;AAClD,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpE,+CAA+C;AAC/C,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAEvE;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1E;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB,qBAAqB,GACrB,eAAe,GACf,iBAAiB,GACjB,WAAW,GACX,kBAAkB,GAClB,aAAa,GACb,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,YAAY,GACZ,WAAW,GACX,UAAU,GACV,sBAAsB,GACtB,qBAAqB,GACrB,eAAe,GACf,WAAW,CAAC;AAEhB;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,GACR,eAAe,GACf,YAAY,GACZ,uBAAuB,GACvB,QAAQ,CAAC;AAMb;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CAC1D;AAMD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IAEjC,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAGf,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAGhB,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,CAAC,EAAE,YAAY,CAAC;IAGpB,QAAQ,EAAE,MAAM,CAAC;IAGjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,eAAe,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IAGtB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,WAAW,EAAE,eAAe,EAAE,CAAC;IAG/B,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAG3B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,aAAa,CAAC,EAAE,gBAAgB,CAAC;IAGjC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAMD,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oCAAoC;AACpC,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,oCAAoC;AACpC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB"}