@graphorin/observability 0.6.0 → 0.7.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 (86) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +29 -5
  3. package/dist/cost/cost-tracker.d.ts.map +1 -1
  4. package/dist/cost/cost-tracker.js +34 -0
  5. package/dist/cost/cost-tracker.js.map +1 -1
  6. package/dist/cost/delegate.d.ts +40 -0
  7. package/dist/cost/delegate.d.ts.map +1 -0
  8. package/dist/cost/delegate.js +31 -0
  9. package/dist/cost/delegate.js.map +1 -0
  10. package/dist/cost/index.d.ts +2 -1
  11. package/dist/cost/index.js +2 -1
  12. package/dist/cost/types.d.ts +39 -0
  13. package/dist/cost/types.d.ts.map +1 -1
  14. package/dist/exporters/otlp-http.d.ts.map +1 -1
  15. package/dist/exporters/otlp-http.js +4 -2
  16. package/dist/exporters/otlp-http.js.map +1 -1
  17. package/dist/exporters/types.d.ts +7 -0
  18. package/dist/exporters/types.d.ts.map +1 -1
  19. package/dist/exporters/types.js.map +1 -1
  20. package/dist/exporters/with-validation.d.ts.map +1 -1
  21. package/dist/exporters/with-validation.js +5 -4
  22. package/dist/exporters/with-validation.js.map +1 -1
  23. package/dist/gen-ai/emit.js +9 -1
  24. package/dist/gen-ai/emit.js.map +1 -1
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +5 -10
  28. package/dist/index.js.map +1 -1
  29. package/dist/package.js +6 -0
  30. package/dist/package.js.map +1 -0
  31. package/dist/redaction/imperative-patterns.d.ts +3 -3
  32. package/dist/redaction/imperative-patterns.d.ts.map +1 -1
  33. package/dist/redaction/imperative-patterns.js +7 -0
  34. package/dist/redaction/imperative-patterns.js.map +1 -1
  35. package/dist/redaction/patterns.d.ts.map +1 -1
  36. package/dist/redaction/patterns.js +2 -2
  37. package/dist/redaction/patterns.js.map +1 -1
  38. package/dist/tracer/sampling.d.ts +6 -1
  39. package/dist/tracer/sampling.d.ts.map +1 -1
  40. package/dist/tracer/sampling.js +7 -1
  41. package/dist/tracer/sampling.js.map +1 -1
  42. package/dist/tracer/span.d.ts.map +1 -1
  43. package/dist/tracer/span.js +12 -3
  44. package/dist/tracer/span.js.map +1 -1
  45. package/package.json +18 -35
  46. package/src/cost/cost-tracker.ts +315 -0
  47. package/src/cost/delegate.ts +79 -0
  48. package/src/cost/index.ts +23 -0
  49. package/src/cost/types.ts +151 -0
  50. package/src/eval/index.ts +16 -0
  51. package/src/eval/runner.ts +146 -0
  52. package/src/eval/types.ts +122 -0
  53. package/src/exporters/console.ts +75 -0
  54. package/src/exporters/index.ts +36 -0
  55. package/src/exporters/jsonl.ts +167 -0
  56. package/src/exporters/otlp-http.ts +178 -0
  57. package/src/exporters/types.ts +85 -0
  58. package/src/exporters/with-validation.ts +176 -0
  59. package/src/gen-ai/emit.ts +157 -0
  60. package/src/gen-ai/index.ts +26 -0
  61. package/src/gen-ai/operation-mapping.ts +89 -0
  62. package/src/gen-ai/system-derivation.ts +84 -0
  63. package/src/gen-ai/types.ts +143 -0
  64. package/src/index.ts +36 -0
  65. package/src/logger/index.ts +13 -0
  66. package/src/logger/logger.ts +178 -0
  67. package/src/openinference/index.ts +133 -0
  68. package/src/redaction/config.ts +50 -0
  69. package/src/redaction/errors.ts +53 -0
  70. package/src/redaction/imperative-patterns.ts +268 -0
  71. package/src/redaction/index.ts +42 -0
  72. package/src/redaction/patterns.ts +263 -0
  73. package/src/redaction/types.ts +116 -0
  74. package/src/redaction/validator.ts +302 -0
  75. package/src/replay/config.ts +58 -0
  76. package/src/replay/index.ts +17 -0
  77. package/src/replay/log.ts +89 -0
  78. package/src/replay/replay.ts +196 -0
  79. package/src/replay/types.ts +112 -0
  80. package/src/telemetry/index.ts +94 -0
  81. package/src/tracer/ids.ts +27 -0
  82. package/src/tracer/index.ts +22 -0
  83. package/src/tracer/sampling.ts +170 -0
  84. package/src/tracer/span-names.ts +52 -0
  85. package/src/tracer/span.ts +175 -0
  86. package/src/tracer/tracer.ts +309 -0
@@ -1 +1 @@
1
- {"version":3,"file":"with-validation.js","names":["DEFAULT_ATTR_SENSITIVITY: Sensitivity","out: Record<string, SpanAttributeValue>","out: SpanRecord['events'][number][]"],"sources":["../../src/exporters/with-validation.ts"],"sourcesContent":["/**\n * `withValidation(exporter, opts)` - the **mandatory wrapper** applied\n * to every exporter. Forwards each span record through a\n * `RedactionValidator` first; drops + counts when a value exceeds the\n * configured tier floor or matches a secret / PII pattern.\n *\n * @packageDocumentation\n */\n\nimport type { Sensitivity, SpanAttributes, SpanAttributeValue } from '@graphorin/core';\nimport type {\n RedactionCounters,\n RedactionValidatorInstance,\n RedactionValidatorOptions,\n} from '../redaction/types.js';\nimport { createRedactionValidator } from '../redaction/validator.js';\n\nimport { type SpanRecord, type TraceExporter, VALIDATED_EXPORTER_BRAND } from './types.js';\n\nconst DEFAULT_ATTR_SENSITIVITY: Sensitivity = 'internal';\n\n/**\n * Options for {@link withValidation}.\n *\n * @stable\n */\nexport interface WithValidationOptions extends RedactionValidatorOptions {\n /**\n * Optional pre-built validator. When supplied, the rest of the\n * options on this object are ignored and the supplied validator is\n * reused - useful for sharing one validator across multiple exporters.\n */\n readonly validator?: RedactionValidatorInstance;\n}\n\n/**\n * Wrap an exporter so every span flows through a {@link RedactionValidator}\n * before reaching the sink. Exporters that are not wrapped are rejected\n * by the tracer at startup.\n *\n * @stable\n */\nexport function withValidation<E extends TraceExporter>(\n exporter: E,\n opts: WithValidationOptions = {},\n): TraceExporter {\n const validator = opts.validator ?? createRedactionValidator(opts);\n const wrapped: TraceExporter = {\n id: `${exporter.id}+validated`,\n [VALIDATED_EXPORTER_BRAND]: true,\n async export(record: SpanRecord): Promise<void> {\n // RP-18: the validator strips offending attributes (counting each drop)\n // and always returns an exportable record - a single untagged or\n // over-tier attribute no longer makes the whole span vanish from every\n // exporter.\n await exporter.export(sanitizeRecord(record, validator));\n },\n flush: () => exporter.flush(),\n shutdown: () => exporter.shutdown(),\n };\n return wrapped;\n}\n\n/**\n * @internal\n */\nexport function sanitizeRecord(\n record: SpanRecord,\n validator: RedactionValidatorInstance,\n): SpanRecord {\n const sensitivities = record.sensitivityByAttribute ?? {};\n // RP-18: attribute-granular sanitization. Offending attributes are stripped\n // (and counted by the validator); the span itself always survives so\n // framework spans carrying untagged attributes still reach every exporter.\n const sanitizedAttrs = sanitizeAttributes(\n record.attributes,\n sensitivities,\n validator,\n record.type,\n );\n const sanitizedEvents = sanitizeEvents(record.events, validator, record.type);\n return {\n ...record,\n attributes: sanitizedAttrs,\n events: sanitizedEvents,\n };\n}\n\n/**\n * @internal\n */\nexport function sanitizeAttributes(\n attrs: SpanAttributes,\n sensitivities: Readonly<Record<string, SpanAttributeValue>>,\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n const tier = readTier(sensitivities[key]);\n const result = validator.validate({\n value,\n tier,\n context: spanType === undefined ? { attribute: key } : { attribute: key, spanType },\n });\n if (result === null) {\n // RP-18: this single attribute exceeded the floor (or matched a secret\n // pattern). Strip it - the validator has already counted the drop - and\n // keep the rest of the span rather than discarding the whole record.\n continue;\n }\n out[key] = result.value as SpanAttributeValue;\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\n/**\n * @internal\n */\nexport function sanitizeEvents(\n events: SpanRecord['events'],\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanRecord['events'] {\n const out: SpanRecord['events'][number][] = [];\n for (const event of events) {\n const sanitizedAttrs = sanitizeAttributes(event.attributes, {}, validator, spanType);\n out.push({ name: event.name, timeUnixNano: event.timeUnixNano, attributes: sanitizedAttrs });\n }\n return out;\n}\n\nfunction readTier(value: SpanAttributeValue | undefined): Sensitivity {\n if (value === 'public' || value === 'internal' || value === 'secret') return value;\n return DEFAULT_ATTR_SENSITIVITY;\n}\n\n/**\n * Quickly check whether an exporter is the result of a previous\n * {@link withValidation} call. The tracer uses this to fail fast at\n * startup.\n *\n * @stable\n */\nexport function isValidatedExporter(exporter: TraceExporter): boolean {\n return exporter[VALIDATED_EXPORTER_BRAND] === true;\n}\n\n/**\n * Pull the counters out of any exporter wrapped by {@link withValidation}.\n * Returns `null` for exporters that were never wrapped.\n *\n * @stable\n */\nexport function tryGetValidatorCounters(validator: RedactionValidatorInstance): RedactionCounters {\n return validator.counters();\n}\n"],"mappings":";;;;AAmBA,MAAMA,2BAAwC;;;;;;;;AAuB9C,SAAgB,eACd,UACA,OAA8B,EAAE,EACjB;CACf,MAAM,YAAY,KAAK,aAAa,yBAAyB,KAAK;AAclE,QAb+B;EAC7B,IAAI,GAAG,SAAS,GAAG;GAClB,2BAA2B;EAC5B,MAAM,OAAO,QAAmC;AAK9C,SAAM,SAAS,OAAO,eAAe,QAAQ,UAAU,CAAC;;EAE1D,aAAa,SAAS,OAAO;EAC7B,gBAAgB,SAAS,UAAU;EACpC;;;;;AAOH,SAAgB,eACd,QACA,WACY;CACZ,MAAM,gBAAgB,OAAO,0BAA0B,EAAE;CAIzD,MAAM,iBAAiB,mBACrB,OAAO,YACP,eACA,WACA,OAAO,KACR;CACD,MAAM,kBAAkB,eAAe,OAAO,QAAQ,WAAW,OAAO,KAAK;AAC7E,QAAO;EACL,GAAG;EACH,YAAY;EACZ,QAAQ;EACT;;;;;AAMH,SAAgB,mBACd,OACA,eACA,WACA,UACgB;CAChB,MAAMC,MAA0C,EAAE;AAClD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;EAChD,MAAM,OAAO,SAAS,cAAc,KAAK;EACzC,MAAM,SAAS,UAAU,SAAS;GAChC;GACA;GACA,SAAS,aAAa,SAAY,EAAE,WAAW,KAAK,GAAG;IAAE,WAAW;IAAK;IAAU;GACpF,CAAC;AACF,MAAI,WAAW,KAIb;AAEF,MAAI,OAAO,OAAO;;AAEpB,QAAO,OAAO,OAAO,IAAI;;;;;AAM3B,SAAgB,eACd,QACA,WACA,UACsB;CACtB,MAAMC,MAAsC,EAAE;AAC9C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,iBAAiB,mBAAmB,MAAM,YAAY,EAAE,EAAE,WAAW,SAAS;AACpF,MAAI,KAAK;GAAE,MAAM,MAAM;GAAM,cAAc,MAAM;GAAc,YAAY;GAAgB,CAAC;;AAE9F,QAAO;;AAGT,SAAS,SAAS,OAAoD;AACpE,KAAI,UAAU,YAAY,UAAU,cAAc,UAAU,SAAU,QAAO;AAC7E,QAAO;;;;;;;;;AAUT,SAAgB,oBAAoB,UAAkC;AACpE,QAAO,SAAS,8BAA8B;;;;;;;;AAShD,SAAgB,wBAAwB,WAA0D;AAChG,QAAO,UAAU,UAAU"}
1
+ {"version":3,"file":"with-validation.js","names":["DEFAULT_ATTR_SENSITIVITY: Sensitivity","out: Record<string, SpanAttributeValue>","out: SpanRecord['events'][number][]"],"sources":["../../src/exporters/with-validation.ts"],"sourcesContent":["/**\n * `withValidation(exporter, opts)` - the **mandatory wrapper** applied\n * to every exporter. Forwards each span record through a\n * `RedactionValidator` first; drops + counts when a value exceeds the\n * configured tier floor or matches a secret / PII pattern.\n *\n * @packageDocumentation\n */\n\nimport type { Sensitivity, SpanAttributes, SpanAttributeValue } from '@graphorin/core';\nimport type {\n RedactionCounters,\n RedactionValidatorInstance,\n RedactionValidatorOptions,\n} from '../redaction/types.js';\nimport { createRedactionValidator } from '../redaction/validator.js';\n\nimport { type SpanRecord, type TraceExporter, VALIDATED_EXPORTER_BRAND } from './types.js';\n\nconst DEFAULT_ATTR_SENSITIVITY: Sensitivity = 'internal';\n\n/**\n * Options for {@link withValidation}.\n *\n * @stable\n */\nexport interface WithValidationOptions extends RedactionValidatorOptions {\n /**\n * Optional pre-built validator. When supplied, the rest of the\n * options on this object are ignored and the supplied validator is\n * reused - useful for sharing one validator across multiple exporters.\n */\n readonly validator?: RedactionValidatorInstance;\n}\n\n/**\n * Wrap an exporter so every span flows through a {@link RedactionValidator}\n * before reaching the sink. Exporters that are not wrapped are rejected\n * by the tracer at startup.\n *\n * @stable\n */\nexport function withValidation<E extends TraceExporter>(\n exporter: E,\n opts: WithValidationOptions = {},\n): TraceExporter {\n const validator = opts.validator ?? createRedactionValidator(opts);\n const wrapped: TraceExporter = {\n id: `${exporter.id}+validated`,\n [VALIDATED_EXPORTER_BRAND]: true,\n async export(record: SpanRecord): Promise<void> {\n // RP-18: the validator strips offending attributes (counting each drop)\n // and always returns an exportable record - a single untagged or\n // over-tier attribute no longer makes the whole span vanish from every\n // exporter.\n await exporter.export(sanitizeRecord(record, validator));\n },\n flush: () => exporter.flush(),\n shutdown: () => exporter.shutdown(),\n };\n return wrapped;\n}\n\n/**\n * @internal\n */\nexport function sanitizeRecord(\n record: SpanRecord,\n validator: RedactionValidatorInstance,\n): SpanRecord {\n const sensitivities = record.sensitivityByAttribute ?? {};\n // RP-18: attribute-granular sanitization. Offending attributes are stripped\n // (and counted by the validator); the span itself always survives so\n // framework spans carrying untagged attributes still reach every exporter.\n const sanitizedAttrs = sanitizeAttributes(\n record.attributes,\n sensitivities,\n validator,\n record.type,\n );\n const sanitizedEvents = sanitizeEvents(record.events, validator, record.type);\n return {\n ...record,\n attributes: sanitizedAttrs,\n events: sanitizedEvents,\n };\n}\n\n/**\n * @internal\n */\nexport function sanitizeAttributes(\n attrs: SpanAttributes,\n sensitivities: Readonly<Record<string, SpanAttributeValue>>,\n validator: RedactionValidatorInstance,\n spanType?: string,\n origin?: string,\n): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n const tier = readTier(sensitivities[key]);\n const result = validator.validate({\n value,\n tier,\n context: {\n attribute: key,\n ...(spanType !== undefined ? { spanType } : {}),\n // W-094: lets onViolation / droppedByReason tell an EVENT\n // attribute from a span attribute.\n ...(origin !== undefined ? { origin } : {}),\n },\n });\n if (result === null) {\n // RP-18: this single attribute exceeded the floor (or matched a secret\n // pattern). Strip it - the validator has already counted the drop - and\n // keep the rest of the span rather than discarding the whole record.\n continue;\n }\n out[key] = result.value as SpanAttributeValue;\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\n/**\n * @internal\n */\nexport function sanitizeEvents(\n events: SpanRecord['events'],\n validator: RedactionValidatorInstance,\n spanType?: string,\n): SpanRecord['events'] {\n const out: SpanRecord['events'][number][] = [];\n for (const event of events) {\n // W-094: honour the per-event sensitivity map recorded by\n // `addEvent(..., opts)` (pre-fix an empty map was passed here, so\n // EVERY event attribute - including `exception.type` - was dropped\n // under the default 'public' floor). Untagged attributes still\n // default-deny; the `event:<name>` origin distinguishes event drops\n // from span-attribute drops in onViolation.\n const sanitizedAttrs = sanitizeAttributes(\n event.attributes,\n event.sensitivityByAttribute ?? {},\n validator,\n spanType,\n `event:${event.name}`,\n );\n out.push({ name: event.name, timeUnixNano: event.timeUnixNano, attributes: sanitizedAttrs });\n }\n return out;\n}\n\nfunction readTier(value: SpanAttributeValue | undefined): Sensitivity {\n if (value === 'public' || value === 'internal' || value === 'secret') return value;\n return DEFAULT_ATTR_SENSITIVITY;\n}\n\n/**\n * Quickly check whether an exporter is the result of a previous\n * {@link withValidation} call. The tracer uses this to fail fast at\n * startup.\n *\n * @stable\n */\nexport function isValidatedExporter(exporter: TraceExporter): boolean {\n return exporter[VALIDATED_EXPORTER_BRAND] === true;\n}\n\n/**\n * Pull the counters out of any exporter wrapped by {@link withValidation}.\n * Returns `null` for exporters that were never wrapped.\n *\n * @stable\n */\nexport function tryGetValidatorCounters(validator: RedactionValidatorInstance): RedactionCounters {\n return validator.counters();\n}\n"],"mappings":";;;;AAmBA,MAAMA,2BAAwC;;;;;;;;AAuB9C,SAAgB,eACd,UACA,OAA8B,EAAE,EACjB;CACf,MAAM,YAAY,KAAK,aAAa,yBAAyB,KAAK;AAclE,QAb+B;EAC7B,IAAI,GAAG,SAAS,GAAG;GAClB,2BAA2B;EAC5B,MAAM,OAAO,QAAmC;AAK9C,SAAM,SAAS,OAAO,eAAe,QAAQ,UAAU,CAAC;;EAE1D,aAAa,SAAS,OAAO;EAC7B,gBAAgB,SAAS,UAAU;EACpC;;;;;AAOH,SAAgB,eACd,QACA,WACY;CACZ,MAAM,gBAAgB,OAAO,0BAA0B,EAAE;CAIzD,MAAM,iBAAiB,mBACrB,OAAO,YACP,eACA,WACA,OAAO,KACR;CACD,MAAM,kBAAkB,eAAe,OAAO,QAAQ,WAAW,OAAO,KAAK;AAC7E,QAAO;EACL,GAAG;EACH,YAAY;EACZ,QAAQ;EACT;;;;;AAMH,SAAgB,mBACd,OACA,eACA,WACA,UACA,QACgB;CAChB,MAAMC,MAA0C,EAAE;AAClD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;EAChD,MAAM,OAAO,SAAS,cAAc,KAAK;EACzC,MAAM,SAAS,UAAU,SAAS;GAChC;GACA;GACA,SAAS;IACP,WAAW;IACX,GAAI,aAAa,SAAY,EAAE,UAAU,GAAG,EAAE;IAG9C,GAAI,WAAW,SAAY,EAAE,QAAQ,GAAG,EAAE;IAC3C;GACF,CAAC;AACF,MAAI,WAAW,KAIb;AAEF,MAAI,OAAO,OAAO;;AAEpB,QAAO,OAAO,OAAO,IAAI;;;;;AAM3B,SAAgB,eACd,QACA,WACA,UACsB;CACtB,MAAMC,MAAsC,EAAE;AAC9C,MAAK,MAAM,SAAS,QAAQ;EAO1B,MAAM,iBAAiB,mBACrB,MAAM,YACN,MAAM,0BAA0B,EAAE,EAClC,WACA,UACA,SAAS,MAAM,OAChB;AACD,MAAI,KAAK;GAAE,MAAM,MAAM;GAAM,cAAc,MAAM;GAAc,YAAY;GAAgB,CAAC;;AAE9F,QAAO;;AAGT,SAAS,SAAS,OAAoD;AACpE,KAAI,UAAU,YAAY,UAAU,cAAc,UAAU,SAAU,QAAO;AAC7E,QAAO;;;;;;;;;AAUT,SAAgB,oBAAoB,UAAkC;AACpE,QAAO,SAAS,8BAA8B;;;;;;;;AAShD,SAAgB,wBAAwB,WAA0D;AAChG,QAAO,UAAU,UAAU"}
@@ -54,7 +54,15 @@ function emitGenAIAttributes(span, attrs) {
54
54
  * @stable
55
55
  */
56
56
  function emitGenAIMessageEvents(span, messages, opts = {}) {
57
- for (const message of messages) span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system));
57
+ for (const message of messages) span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system), {
58
+ sensitivity: "internal",
59
+ sensitivityByAttribute: {
60
+ "gen_ai.message.role": "public",
61
+ "gen_ai.system": "public",
62
+ "gen_ai.message.name": "public",
63
+ "gen_ai.tool.call.id": "public"
64
+ }
65
+ });
58
66
  }
59
67
  function eventNameFor(role) {
60
68
  switch (role) {
@@ -1 +1 @@
1
- {"version":3,"file":"emit.js","names":["out: Record<string, SpanAttributeValue>"],"sources":["../../src/gen-ai/emit.ts"],"sourcesContent":["/**\n * Emission helpers that attach OpenTelemetry GenAI semantic-convention\n * attributes / events to an existing span.\n *\n * The helpers are deliberately additive - they NEVER remove or rename\n * existing attributes. The new `gen_ai.*` family sits on top of the\n * Graphorin-prefixed family. Sensitivity defaults follow the catalogue\n * documented alongside the canonical mapping table:\n *\n * - `gen_ai.system` / `gen_ai.request.model` / `gen_ai.response.model` /\n * `gen_ai.response.id` / `gen_ai.usage.{input,output}_tokens` /\n * `gen_ai.response.finish_reasons` / `gen_ai.tool.{name,type,call.id,description}` /\n * `gen_ai.agent.{id,name}` / `gen_ai.session.id` /\n * `gen_ai.operation.name` → `'public'`\n * - `gen_ai.request.messages` / per-message events / `gen_ai.response.content`\n * → `'internal'` (default-deny non-public per DEC-141)\n * - `gen_ai.tool.output` → inherits per-tool `Tool.sensitivity`\n *\n * @packageDocumentation\n */\n\nimport type { AISpan, SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { GraphorinSpan } from '../tracer/span.js';\nimport { asGraphorinSpan } from '../tracer/tracer.js';\n\nimport { operationNameFor } from './operation-mapping.js';\nimport type { GenAIAttributes, GenAIMessage, GenAIMessageRole } from './types.js';\n\nconst PUBLIC_KEY_PREFIXES = [\n 'gen_ai.system',\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'gen_ai.response.id',\n 'gen_ai.response.finish_reasons',\n 'gen_ai.usage.input_tokens',\n 'gen_ai.usage.output_tokens',\n 'gen_ai.tool.name',\n 'gen_ai.tool.type',\n 'gen_ai.tool.call.id',\n 'gen_ai.tool.description',\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.session.id',\n 'gen_ai.operation.name',\n] as const;\n\n/**\n * Attach the canonical `gen_ai.*` attribute set to a span. The helper\n * is additive on the existing Graphorin-prefixed attributes and applies\n * the per-attribute sensitivity defaults catalogue.\n *\n * @stable\n */\nexport function emitGenAIAttributes<T extends SpanType>(\n span: AISpan<T>,\n attrs: GenAIAttributes,\n): void {\n const gs = asGraphorinSpan(span);\n const operation = attrs.operation ?? operationNameFor(span.type);\n if (operation !== undefined) setPublic(gs, span, 'gen_ai.operation.name', operation);\n\n if (attrs.system !== undefined) setPublic(gs, span, 'gen_ai.system', attrs.system);\n if (attrs.requestModel !== undefined)\n setPublic(gs, span, 'gen_ai.request.model', attrs.requestModel);\n if (attrs.responseModel !== undefined)\n setPublic(gs, span, 'gen_ai.response.model', attrs.responseModel);\n if (attrs.responseId !== undefined) setPublic(gs, span, 'gen_ai.response.id', attrs.responseId);\n if (attrs.inputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.input_tokens', attrs.inputTokens);\n if (attrs.outputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.output_tokens', attrs.outputTokens);\n if (attrs.finishReasons !== undefined)\n setPublic(gs, span, 'gen_ai.response.finish_reasons', [...attrs.finishReasons]);\n if (attrs.agentId !== undefined) setPublic(gs, span, 'gen_ai.agent.id', attrs.agentId);\n if (attrs.agentName !== undefined) setPublic(gs, span, 'gen_ai.agent.name', attrs.agentName);\n if (attrs.sessionId !== undefined) setPublic(gs, span, 'gen_ai.session.id', attrs.sessionId);\n if (attrs.toolName !== undefined) setPublic(gs, span, 'gen_ai.tool.name', attrs.toolName);\n if (attrs.toolType !== undefined) setPublic(gs, span, 'gen_ai.tool.type', attrs.toolType);\n if (attrs.toolCallId !== undefined) setPublic(gs, span, 'gen_ai.tool.call.id', attrs.toolCallId);\n if (attrs.toolDescription !== undefined)\n setPublic(gs, span, 'gen_ai.tool.description', attrs.toolDescription);\n}\n\n/**\n * Emit per-message OpenTelemetry GenAI span events. The helper records\n * one event per message - the per-message-event emission shape per the\n * OTel semconv discipline (size-bounded individually; safer than the\n * aggregate-attribute shape on large prompts).\n *\n * @stable\n */\nexport function emitGenAIMessageEvents<T extends SpanType>(\n span: AISpan<T>,\n messages: ReadonlyArray<GenAIMessage>,\n opts: { readonly system?: string } = {},\n): void {\n for (const message of messages) {\n span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system));\n }\n}\n\nfunction eventNameFor(role: GenAIMessageRole): string {\n switch (role) {\n case 'system':\n return 'gen_ai.system.message';\n case 'user':\n return 'gen_ai.user.message';\n case 'assistant':\n return 'gen_ai.assistant.message';\n case 'tool':\n return 'gen_ai.tool.message';\n }\n}\n\nfunction buildEventAttrs(message: GenAIMessage, system?: string): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {\n 'gen_ai.message.role': message.role,\n content: message.content,\n };\n if (system !== undefined) out['gen_ai.system'] = system;\n if (message.name !== undefined) out['gen_ai.message.name'] = message.name;\n if (message.toolCallId !== undefined) out['gen_ai.tool.call.id'] = message.toolCallId;\n if (message.toolCalls !== undefined && message.toolCalls.length > 0) {\n out['gen_ai.tool.calls'] = JSON.stringify(message.toolCalls);\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\nfunction setPublic(\n gs: GraphorinSpan | null,\n span: AISpan,\n key: string,\n value: SpanAttributeValue,\n): void {\n if (\n gs !== null &&\n PUBLIC_KEY_PREFIXES.some((prefix) => key === prefix || key.startsWith(`${prefix}.`))\n ) {\n gs.setAttribute(key, value, { sensitivity: 'public' });\n return;\n }\n span.setAttributes({ [key]: value });\n}\n"],"mappings":";;;;AA6BA,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,oBACd,MACA,OACM;CACN,MAAM,KAAK,gBAAgB,KAAK;CAChC,MAAM,YAAY,MAAM,aAAa,iBAAiB,KAAK,KAAK;AAChE,KAAI,cAAc,OAAW,WAAU,IAAI,MAAM,yBAAyB,UAAU;AAEpF,KAAI,MAAM,WAAW,OAAW,WAAU,IAAI,MAAM,iBAAiB,MAAM,OAAO;AAClF,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,wBAAwB,MAAM,aAAa;AACjE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,yBAAyB,MAAM,cAAc;AACnE,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,sBAAsB,MAAM,WAAW;AAC/F,KAAI,MAAM,gBAAgB,OACxB,WAAU,IAAI,MAAM,6BAA6B,MAAM,YAAY;AACrE,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,8BAA8B,MAAM,aAAa;AACvE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,kCAAkC,CAAC,GAAG,MAAM,cAAc,CAAC;AACjF,KAAI,MAAM,YAAY,OAAW,WAAU,IAAI,MAAM,mBAAmB,MAAM,QAAQ;AACtF,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,uBAAuB,MAAM,WAAW;AAChG,KAAI,MAAM,oBAAoB,OAC5B,WAAU,IAAI,MAAM,2BAA2B,MAAM,gBAAgB;;;;;;;;;;AAWzE,SAAgB,uBACd,MACA,UACA,OAAqC,EAAE,EACjC;AACN,MAAK,MAAM,WAAW,SACpB,MAAK,SAAS,aAAa,QAAQ,KAAK,EAAE,gBAAgB,SAAS,KAAK,OAAO,CAAC;;AAIpF,SAAS,aAAa,MAAgC;AACpD,SAAQ,MAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,OACH,QAAO;;;AAIb,SAAS,gBAAgB,SAAuB,QAAiC;CAC/E,MAAMA,MAA0C;EAC9C,uBAAuB,QAAQ;EAC/B,SAAS,QAAQ;EAClB;AACD,KAAI,WAAW,OAAW,KAAI,mBAAmB;AACjD,KAAI,QAAQ,SAAS,OAAW,KAAI,yBAAyB,QAAQ;AACrE,KAAI,QAAQ,eAAe,OAAW,KAAI,yBAAyB,QAAQ;AAC3E,KAAI,QAAQ,cAAc,UAAa,QAAQ,UAAU,SAAS,EAChE,KAAI,uBAAuB,KAAK,UAAU,QAAQ,UAAU;AAE9D,QAAO,OAAO,OAAO,IAAI;;AAG3B,SAAS,UACP,IACA,MACA,KACA,OACM;AACN,KACE,OAAO,QACP,oBAAoB,MAAM,WAAW,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,GAAG,CAAC,EACpF;AACA,KAAG,aAAa,KAAK,OAAO,EAAE,aAAa,UAAU,CAAC;AACtD;;AAEF,MAAK,cAAc,GAAG,MAAM,OAAO,CAAC"}
1
+ {"version":3,"file":"emit.js","names":["out: Record<string, SpanAttributeValue>"],"sources":["../../src/gen-ai/emit.ts"],"sourcesContent":["/**\n * Emission helpers that attach OpenTelemetry GenAI semantic-convention\n * attributes / events to an existing span.\n *\n * The helpers are deliberately additive - they NEVER remove or rename\n * existing attributes. The new `gen_ai.*` family sits on top of the\n * Graphorin-prefixed family. Sensitivity defaults follow the catalogue\n * documented alongside the canonical mapping table:\n *\n * - `gen_ai.system` / `gen_ai.request.model` / `gen_ai.response.model` /\n * `gen_ai.response.id` / `gen_ai.usage.{input,output}_tokens` /\n * `gen_ai.response.finish_reasons` / `gen_ai.tool.{name,type,call.id,description}` /\n * `gen_ai.agent.{id,name}` / `gen_ai.session.id` /\n * `gen_ai.operation.name` → `'public'`\n * - `gen_ai.request.messages` / per-message events / `gen_ai.response.content`\n * → `'internal'` (default-deny non-public per DEC-141)\n * - `gen_ai.tool.output` → inherits per-tool `Tool.sensitivity`\n *\n * @packageDocumentation\n */\n\nimport type { AISpan, SpanAttributes, SpanAttributeValue, SpanType } from '@graphorin/core';\n\nimport type { GraphorinSpan } from '../tracer/span.js';\nimport { asGraphorinSpan } from '../tracer/tracer.js';\n\nimport { operationNameFor } from './operation-mapping.js';\nimport type { GenAIAttributes, GenAIMessage, GenAIMessageRole } from './types.js';\n\nconst PUBLIC_KEY_PREFIXES = [\n 'gen_ai.system',\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'gen_ai.response.id',\n 'gen_ai.response.finish_reasons',\n 'gen_ai.usage.input_tokens',\n 'gen_ai.usage.output_tokens',\n 'gen_ai.tool.name',\n 'gen_ai.tool.type',\n 'gen_ai.tool.call.id',\n 'gen_ai.tool.description',\n 'gen_ai.agent.id',\n 'gen_ai.agent.name',\n 'gen_ai.session.id',\n 'gen_ai.operation.name',\n] as const;\n\n/**\n * Attach the canonical `gen_ai.*` attribute set to a span. The helper\n * is additive on the existing Graphorin-prefixed attributes and applies\n * the per-attribute sensitivity defaults catalogue.\n *\n * @stable\n */\nexport function emitGenAIAttributes<T extends SpanType>(\n span: AISpan<T>,\n attrs: GenAIAttributes,\n): void {\n const gs = asGraphorinSpan(span);\n const operation = attrs.operation ?? operationNameFor(span.type);\n if (operation !== undefined) setPublic(gs, span, 'gen_ai.operation.name', operation);\n\n if (attrs.system !== undefined) setPublic(gs, span, 'gen_ai.system', attrs.system);\n if (attrs.requestModel !== undefined)\n setPublic(gs, span, 'gen_ai.request.model', attrs.requestModel);\n if (attrs.responseModel !== undefined)\n setPublic(gs, span, 'gen_ai.response.model', attrs.responseModel);\n if (attrs.responseId !== undefined) setPublic(gs, span, 'gen_ai.response.id', attrs.responseId);\n if (attrs.inputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.input_tokens', attrs.inputTokens);\n if (attrs.outputTokens !== undefined)\n setPublic(gs, span, 'gen_ai.usage.output_tokens', attrs.outputTokens);\n if (attrs.finishReasons !== undefined)\n setPublic(gs, span, 'gen_ai.response.finish_reasons', [...attrs.finishReasons]);\n if (attrs.agentId !== undefined) setPublic(gs, span, 'gen_ai.agent.id', attrs.agentId);\n if (attrs.agentName !== undefined) setPublic(gs, span, 'gen_ai.agent.name', attrs.agentName);\n if (attrs.sessionId !== undefined) setPublic(gs, span, 'gen_ai.session.id', attrs.sessionId);\n if (attrs.toolName !== undefined) setPublic(gs, span, 'gen_ai.tool.name', attrs.toolName);\n if (attrs.toolType !== undefined) setPublic(gs, span, 'gen_ai.tool.type', attrs.toolType);\n if (attrs.toolCallId !== undefined) setPublic(gs, span, 'gen_ai.tool.call.id', attrs.toolCallId);\n if (attrs.toolDescription !== undefined)\n setPublic(gs, span, 'gen_ai.tool.description', attrs.toolDescription);\n}\n\n/**\n * Emit per-message OpenTelemetry GenAI span events. The helper records\n * one event per message - the per-message-event emission shape per the\n * OTel semconv discipline (size-bounded individually; safer than the\n * aggregate-attribute shape on large prompts).\n *\n * @stable\n */\nexport function emitGenAIMessageEvents<T extends SpanType>(\n span: AISpan<T>,\n messages: ReadonlyArray<GenAIMessage>,\n opts: { readonly system?: string } = {},\n): void {\n for (const message of messages) {\n // W-094: structural metadata (role, provider name, message name,\n // tool-call id) is 'public' so a message event survives the default\n // export floor as a marker; the CONTENT (and tool-call payloads)\n // stays 'internal' - exported only when the operator opted the\n // floor up, with the PII patterns applying there.\n span.addEvent(eventNameFor(message.role), buildEventAttrs(message, opts.system), {\n sensitivity: 'internal',\n sensitivityByAttribute: {\n 'gen_ai.message.role': 'public',\n 'gen_ai.system': 'public',\n 'gen_ai.message.name': 'public',\n 'gen_ai.tool.call.id': 'public',\n },\n });\n }\n}\n\nfunction eventNameFor(role: GenAIMessageRole): string {\n switch (role) {\n case 'system':\n return 'gen_ai.system.message';\n case 'user':\n return 'gen_ai.user.message';\n case 'assistant':\n return 'gen_ai.assistant.message';\n case 'tool':\n return 'gen_ai.tool.message';\n }\n}\n\nfunction buildEventAttrs(message: GenAIMessage, system?: string): SpanAttributes {\n const out: Record<string, SpanAttributeValue> = {\n 'gen_ai.message.role': message.role,\n content: message.content,\n };\n if (system !== undefined) out['gen_ai.system'] = system;\n if (message.name !== undefined) out['gen_ai.message.name'] = message.name;\n if (message.toolCallId !== undefined) out['gen_ai.tool.call.id'] = message.toolCallId;\n if (message.toolCalls !== undefined && message.toolCalls.length > 0) {\n out['gen_ai.tool.calls'] = JSON.stringify(message.toolCalls);\n }\n return Object.freeze(out) as SpanAttributes;\n}\n\nfunction setPublic(\n gs: GraphorinSpan | null,\n span: AISpan,\n key: string,\n value: SpanAttributeValue,\n): void {\n if (\n gs !== null &&\n PUBLIC_KEY_PREFIXES.some((prefix) => key === prefix || key.startsWith(`${prefix}.`))\n ) {\n gs.setAttribute(key, value, { sensitivity: 'public' });\n return;\n }\n span.setAttributes({ [key]: value });\n}\n"],"mappings":";;;;AA6BA,MAAM,sBAAsB;CAC1B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,oBACd,MACA,OACM;CACN,MAAM,KAAK,gBAAgB,KAAK;CAChC,MAAM,YAAY,MAAM,aAAa,iBAAiB,KAAK,KAAK;AAChE,KAAI,cAAc,OAAW,WAAU,IAAI,MAAM,yBAAyB,UAAU;AAEpF,KAAI,MAAM,WAAW,OAAW,WAAU,IAAI,MAAM,iBAAiB,MAAM,OAAO;AAClF,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,wBAAwB,MAAM,aAAa;AACjE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,yBAAyB,MAAM,cAAc;AACnE,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,sBAAsB,MAAM,WAAW;AAC/F,KAAI,MAAM,gBAAgB,OACxB,WAAU,IAAI,MAAM,6BAA6B,MAAM,YAAY;AACrE,KAAI,MAAM,iBAAiB,OACzB,WAAU,IAAI,MAAM,8BAA8B,MAAM,aAAa;AACvE,KAAI,MAAM,kBAAkB,OAC1B,WAAU,IAAI,MAAM,kCAAkC,CAAC,GAAG,MAAM,cAAc,CAAC;AACjF,KAAI,MAAM,YAAY,OAAW,WAAU,IAAI,MAAM,mBAAmB,MAAM,QAAQ;AACtF,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,cAAc,OAAW,WAAU,IAAI,MAAM,qBAAqB,MAAM,UAAU;AAC5F,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,aAAa,OAAW,WAAU,IAAI,MAAM,oBAAoB,MAAM,SAAS;AACzF,KAAI,MAAM,eAAe,OAAW,WAAU,IAAI,MAAM,uBAAuB,MAAM,WAAW;AAChG,KAAI,MAAM,oBAAoB,OAC5B,WAAU,IAAI,MAAM,2BAA2B,MAAM,gBAAgB;;;;;;;;;;AAWzE,SAAgB,uBACd,MACA,UACA,OAAqC,EAAE,EACjC;AACN,MAAK,MAAM,WAAW,SAMpB,MAAK,SAAS,aAAa,QAAQ,KAAK,EAAE,gBAAgB,SAAS,KAAK,OAAO,EAAE;EAC/E,aAAa;EACb,wBAAwB;GACtB,uBAAuB;GACvB,iBAAiB;GACjB,uBAAuB;GACvB,uBAAuB;GACxB;EACF,CAAC;;AAIN,SAAS,aAAa,MAAgC;AACpD,SAAQ,MAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,OACH,QAAO;;;AAIb,SAAS,gBAAgB,SAAuB,QAAiC;CAC/E,MAAMA,MAA0C;EAC9C,uBAAuB,QAAQ;EAC/B,SAAS,QAAQ;EAClB;AACD,KAAI,WAAW,OAAW,KAAI,mBAAmB;AACjD,KAAI,QAAQ,SAAS,OAAW,KAAI,yBAAyB,QAAQ;AACrE,KAAI,QAAQ,eAAe,OAAW,KAAI,yBAAyB,QAAQ;AAC3E,KAAI,QAAQ,cAAc,UAAa,QAAQ,UAAU,SAAS,EAChE,KAAI,uBAAuB,KAAK,UAAU,QAAQ,UAAU;AAE9D,QAAO,OAAO,OAAO,IAAI;;AAG3B,SAAS,UACP,IACA,MACA,KACA,OACM;AACN,KACE,OAAO,QACP,oBAAoB,MAAM,WAAW,QAAQ,UAAU,IAAI,WAAW,GAAG,OAAO,GAAG,CAAC,EACpF;AACA,KAAG,aAAa,KAAK,OAAO,EAAE,aAAa,UAAU,CAAC;AACtD;;AAEF,MAAK,cAAc,GAAG,MAAM,OAAO,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTrackerOptions } from "./cost/types.js";
2
2
  import { CostTracker, createCostTracker } from "./cost/cost-tracker.js";
3
+ import { CostTrackerDelegateIds, ProviderUsageInfoLike, costTrackerUsageDelegate } from "./cost/delegate.js";
3
4
  import { Case, Dataset, EvalCaseResult, EvalReport, RunEvalOptions, ScoreResult, Scorer } from "./eval/types.js";
4
5
  import { runEval } from "./eval/runner.js";
5
6
  import { SpanRecord, SpanRecordEvent, TraceExporter, VALIDATED_EXPORTER_BRAND } from "./exporters/types.js";
@@ -51,8 +52,7 @@ import { GraphorinTracer, TracerOptions, asGraphorinSpan, createTracer } from ".
51
52
  *
52
53
  * @packageDocumentation
53
54
  */
54
- /** Canonical version constant. Mirrors the `package.json` version. */
55
- declare const VERSION = "0.6.0";
55
+ declare const VERSION: string;
56
56
  //#endregion
57
- export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, Case, ConsoleExporterOptions, CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTracker, CostTrackerOptions, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, Dataset, EvalCaseResult, EvalReport, GenAIAttributes, GenAIMessage, GenAIMessageRole, GenAIOperationName, GenAISystem, GenAIToolType, GraphorinSpan, GraphorinTracer, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult as ImperativeScanResult, JSONLExporterOptions, LoggerFormat, LoggerOptions, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, OTLPHttpExporterOptions, OpenInferenceSpanKind, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, PatternCategory, PruneTracesOptions, RedactionCounters, RedactionInput, RedactionOutput, RedactionPattern, RedactionValidationError, RedactionValidator, RedactionValidatorInstance, RedactionValidatorOptions, RedactionViolation, RedactionViolationCallback, Replay, ReplayAuditBridge, ReplayAuditEvent, ReplayEvent, ReplayLogConfig, ReplayMode, ReplayOptions, ReplayRunInput, RunEvalOptions, Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, ScoreResult, Scorer, SetAttributeOptions, SpanRecord, SpanRecordEvent, SpanTypeToOperationName, TelemetryStatus, TraceExporter, TracerOptions, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, ValidationConfig, WithValidationOptions, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
57
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, BuiltInPatternName, Case, ConsoleExporterOptions, CostBudgetExceededCallback, CostBudgets, CostRecordInput, CostScope, CostSnapshot, CostTracker, CostTrackerDelegateIds, CostTrackerOptions, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, Dataset, EvalCaseResult, EvalReport, GenAIAttributes, GenAIMessage, GenAIMessageRole, GenAIOperationName, GenAISystem, GenAIToolType, GraphorinSpan, GraphorinTracer, IMPERATIVE_PREFILTER_SUBSTRINGS, ImperativePattern, ImperativePatternName, ScanResult as ImperativeScanResult, JSONLExporterOptions, LoggerFormat, LoggerOptions, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, OTLPHttpExporterOptions, OpenInferenceSpanKind, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, PatternCategory, ProviderUsageInfoLike, PruneTracesOptions, RedactionCounters, RedactionInput, RedactionOutput, RedactionPattern, RedactionValidationError, RedactionValidator, RedactionValidatorInstance, RedactionValidatorOptions, RedactionViolation, RedactionViolationCallback, Replay, ReplayAuditBridge, ReplayAuditEvent, ReplayEvent, ReplayLogConfig, ReplayMode, ReplayOptions, ReplayRunInput, RunEvalOptions, Sampler, SamplingDecisionMaker, SamplingOptions, SamplingRule, ScoreResult, Scorer, SetAttributeOptions, SpanRecord, SpanRecordEvent, SpanTypeToOperationName, TelemetryStatus, TraceExporter, TracerOptions, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, ValidationConfig, WithValidationOptions, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, costTrackerUsageDelegate, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
58
58
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAsBa,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAwBa"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { version } from "./package.js";
1
2
  import { createCostTracker } from "./cost/cost-tracker.js";
2
- import "./cost/index.js";
3
+ import { costTrackerUsageDelegate } from "./cost/delegate.js";
3
4
  import { runEval } from "./eval/runner.js";
4
5
  import { createConsoleExporter } from "./exporters/console.js";
5
6
  import { createJSONLExporter } from "./exporters/jsonl.js";
@@ -9,7 +10,6 @@ import { RedactionValidationError, UnvalidatedExporterError } from "./redaction/
9
10
  import { ALL_BUILT_IN_PATTERNS, BUILT_IN_PATTERNS, OPT_IN_PATTERNS } from "./redaction/patterns.js";
10
11
  import { compareSensitivityTiers, createRedactionValidator } from "./redaction/validator.js";
11
12
  import { isValidatedExporter, tryGetValidatorCounters, withValidation } from "./exporters/with-validation.js";
12
- import "./exporters/index.js";
13
13
  import { newSpanId, newTraceId } from "./tracer/ids.js";
14
14
  import { createSampler } from "./tracer/sampling.js";
15
15
  import { spanNameFor } from "./tracer/span-names.js";
@@ -17,19 +17,14 @@ import { asGraphorinSpan, createTracer } from "./tracer/tracer.js";
17
17
  import { OPERATION_NAME_TABLE, operationNameFor } from "./gen-ai/operation-mapping.js";
18
18
  import { emitGenAIAttributes, emitGenAIMessageEvents } from "./gen-ai/emit.js";
19
19
  import { PROVIDER_CLASS_TO_GEN_AI_SYSTEM, _resetGenAISystemWarningsForTesting, deriveGenAISystem, setGenAISystemWarnSink } from "./gen-ai/system-derivation.js";
20
- import "./gen-ai/index.js";
21
20
  import { createLogger, getCurrentSpanContext, withCurrentSpan } from "./logger/logger.js";
22
- import "./logger/index.js";
23
21
  import { OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, emitOpenInferenceKind, openInferenceKindFor } from "./openinference/index.js";
24
22
  import { DEFAULT_VALIDATION_CONFIG } from "./redaction/config.js";
25
23
  import { BUILT_IN_IMPERATIVE_PATTERNS, IMPERATIVE_PREFILTER_SUBSTRINGS, scanImperativePatterns, stripImperativePatterns } from "./redaction/imperative-patterns.js";
26
- import "./redaction/index.js";
27
24
  import { DEFAULT_REPLAY_LOG_CONFIG } from "./replay/config.js";
28
25
  import { getTraceLog, pruneTraces } from "./replay/log.js";
29
26
  import { createReplay } from "./replay/replay.js";
30
- import "./replay/index.js";
31
27
  import { announceTelemetryPosture, enableTelemetry, getTelemetryStatus } from "./telemetry/index.js";
32
- import "./tracer/index.js";
33
28
 
34
29
  //#region src/index.ts
35
30
  /**
@@ -52,9 +47,9 @@ import "./tracer/index.js";
52
47
  *
53
48
  * @packageDocumentation
54
49
  */
55
- /** Canonical version constant. Mirrors the `package.json` version. */
56
- const VERSION = "0.6.0";
50
+ /** Canonical version constant, derived from `package.json` at build time. */
51
+ const VERSION = version;
57
52
 
58
53
  //#endregion
59
- export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, RedactionValidationError, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
54
+ export { ALL_BUILT_IN_PATTERNS, BUILT_IN_IMPERATIVE_PATTERNS, BUILT_IN_PATTERNS, DEFAULT_REPLAY_LOG_CONFIG, DEFAULT_VALIDATION_CONFIG, IMPERATIVE_PREFILTER_SUBSTRINGS, OPEN_INFERENCE_EXCLUDED_TYPES, OPEN_INFERENCE_KIND_TABLE, OPERATION_NAME_TABLE, OPT_IN_PATTERNS, PROVIDER_CLASS_TO_GEN_AI_SYSTEM, RedactionValidationError, UnvalidatedExporterError, VALIDATED_EXPORTER_BRAND, VERSION, _resetGenAISystemWarningsForTesting, announceTelemetryPosture, asGraphorinSpan, compareSensitivityTiers, costTrackerUsageDelegate, createConsoleExporter, createCostTracker, createJSONLExporter, createLogger, createOTLPHttpExporter, createRedactionValidator, createReplay, createSampler, createTracer, deriveGenAISystem, emitGenAIAttributes, emitGenAIMessageEvents, emitOpenInferenceKind, enableTelemetry, getCurrentSpanContext, getTelemetryStatus, getTraceLog, isValidatedExporter, newSpanId, newTraceId, openInferenceKindFor, operationNameFor, pruneTraces, runEval, scanImperativePatterns, setGenAISystemWarnSink, spanNameFor, stripImperativePatterns, tryGetValidatorCounters, withCurrentSpan, withValidation };
60
55
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/observability - observability primitives for the\n * Graphorin framework. Ships:\n *\n * - the typed `AISpan<T>` tracer over OpenTelemetry,\n * - the **mandatory** `withValidation()` wrapper for every exporter,\n * - the sensitivity-aware `RedactionValidator` with 14 built-in PII /\n * secret detection patterns,\n * - OpenTelemetry GenAI semantic-conventions conformance helpers,\n * - OpenInference span-kind emission,\n * - the structured `Logger` with span correlation,\n * - the append-only `JSONLExporter` for replay,\n * - sanitized-by-default `Replay` primitives,\n * - the hierarchical `CostTracker`,\n * - and a minimal inline eval runner.\n *\n * The full documentation lives in the package `README.md`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.6.0';\n\nexport * from './cost/index.js';\nexport * from './eval/index.js';\nexport * from './exporters/index.js';\nexport * from './gen-ai/index.js';\nexport * from './logger/index.js';\nexport * from './openinference/index.js';\nexport * from './redaction/index.js';\nexport * from './replay/index.js';\nexport * from './telemetry/index.js';\nexport * from './tracer/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,UAAU"}
1
+ {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/observability - observability primitives for the\n * Graphorin framework. Ships:\n *\n * - the typed `AISpan<T>` tracer over OpenTelemetry,\n * - the **mandatory** `withValidation()` wrapper for every exporter,\n * - the sensitivity-aware `RedactionValidator` with 14 built-in PII /\n * secret detection patterns,\n * - OpenTelemetry GenAI semantic-conventions conformance helpers,\n * - OpenInference span-kind emission,\n * - the structured `Logger` with span correlation,\n * - the append-only `JSONLExporter` for replay,\n * - sanitized-by-default `Replay` primitives,\n * - the hierarchical `CostTracker`,\n * - and a minimal inline eval runner.\n *\n * The full documentation lives in the package `README.md`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport * from './cost/index.js';\nexport * from './eval/index.js';\nexport * from './exporters/index.js';\nexport * from './gen-ai/index.js';\nexport * from './logger/index.js';\nexport * from './openinference/index.js';\nexport * from './redaction/index.js';\nexport * from './replay/index.js';\nexport * from './telemetry/index.js';\nexport * from './tracer/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAaA,UAAkBC"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.7.0";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/observability\",\n \"version\": \"0.7.0\",\n \"description\": \"Observability primitives for the Graphorin framework: typed AISpan tracer over OpenTelemetry, mandatory withValidation wrapper for every exporter, sensitivity-aware RedactionValidator with built-in PII / secret patterns, GenAI Semantic Conventions conformance helpers, OpenInference span-kind emission, structured logger with span correlation, append-only JSONL exporter for replay, sanitized replay primitives, hierarchical CostTracker, and a minimal inline eval runner alongside a zero-default telemetry stub.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/observability\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/observability\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"observability\",\n \"tracing\",\n \"opentelemetry\",\n \"otel\",\n \"gen-ai\",\n \"openinference\",\n \"redaction\",\n \"pii\",\n \"cost-tracking\",\n \"replay\",\n \"evals\",\n \"logger\"\n ],\n \"type\": \"module\",\n \"sideEffects\": false,\n \"engines\": {\n \"node\": \">=22.12.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"./tracer\": {\n \"types\": \"./dist/tracer/index.d.ts\",\n \"default\": \"./dist/tracer/index.js\"\n },\n \"./redaction\": {\n \"types\": \"./dist/redaction/index.d.ts\",\n \"default\": \"./dist/redaction/index.js\"\n },\n \"./redaction/patterns\": {\n \"types\": \"./dist/redaction/patterns.d.ts\",\n \"default\": \"./dist/redaction/patterns.js\"\n },\n \"./redaction/imperative-patterns\": {\n \"types\": \"./dist/redaction/imperative-patterns.d.ts\",\n \"default\": \"./dist/redaction/imperative-patterns.js\"\n },\n \"./exporters\": {\n \"types\": \"./dist/exporters/index.d.ts\",\n \"default\": \"./dist/exporters/index.js\"\n },\n \"./gen-ai\": {\n \"types\": \"./dist/gen-ai/index.d.ts\",\n \"default\": \"./dist/gen-ai/index.js\"\n },\n \"./openinference\": {\n \"types\": \"./dist/openinference/index.d.ts\",\n \"default\": \"./dist/openinference/index.js\"\n },\n \"./cost\": {\n \"types\": \"./dist/cost/index.d.ts\",\n \"default\": \"./dist/cost/index.js\"\n },\n \"./replay\": {\n \"types\": \"./dist/replay/index.d.ts\",\n \"default\": \"./dist/replay/index.js\"\n },\n \"./eval\": {\n \"types\": \"./dist/eval/index.d.ts\",\n \"default\": \"./dist/eval/index.js\"\n },\n \"./logger\": {\n \"types\": \"./dist/logger/index.d.ts\",\n \"default\": \"./dist/logger/index.js\"\n },\n \"./telemetry\": {\n \"types\": \"./dist/telemetry/index.d.ts\",\n \"default\": \"./dist/telemetry/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"src\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {\n \"fast-check\": \"^3.23.0\"\n }\n}\n"],"mappings":";cAEa"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Imperative-pattern catalogue for inbound prompt-injection defence.
4
4
  *
5
- * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) - the two
5
+ * Sibling to `BUILT_IN_PATTERNS` (PII / secrets) - the two
6
6
  * catalogues are disjoint by construction. The imperative catalogue
7
7
  * is consumed by the inbound sanitization layer in `@graphorin/tools`
8
8
  * to scan tool / MCP results before they reach the agent's message
@@ -27,10 +27,10 @@
27
27
  *
28
28
  * @stable
29
29
  */
30
- type ImperativePatternName = 'ignore-previous-instructions' | 'forget-instructions' | 'override-instructions' | 'system-prompt-leak' | 'role-reassignment' | 'developer-mode' | 'jailbreak-marker' | 'tool-call-injection' | 'role-tag-injection';
30
+ type ImperativePatternName = 'ignore-previous-instructions' | 'forget-instructions' | 'override-instructions' | 'system-prompt-leak' | 'role-reassignment' | 'developer-mode' | 'jailbreak-marker' | 'tool-call-injection' | 'role-tag-injection' | 'untrusted-content-delimiter-injection';
31
31
  /**
32
32
  * One entry in the imperative-pattern catalogue. The shape mirrors
33
- * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace
33
+ * `BUILT_IN_PATTERNS` so consumers can share scan / replace
34
34
  * machinery, but the fields are typed as imperative-only so the two
35
35
  * catalogues do not accidentally merge.
36
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"imperative-patterns.d.ts","names":[],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAmBA;;;;;AAuGA;AASA;AAYA;AAaA;;;;;AA4DA;;;;;;;;;;;;;KAxNY,qBAAA;;;;;;;;;UAmBK,iBAAA;iBACA;;;;;;;sBAOK;;;;;kBAKJ;;;;;;;;;;;;cA0FL,uCAAuC;;;;;;;;cASvC,iCAAiC;;;;;;;;;UAY7B,UAAA;iBACA;;;;;;;;;;;;;;iBAYD,sBAAA,0BAEJ,cAAc,wCAEvB;;;;;;;;;iBAwDa,uBAAA,0BAEJ,cAAc"}
1
+ {"version":3,"file":"imperative-patterns.d.ts","names":[],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":[],"mappings":";;AA6BA;AAoBA;;;;;AAmHA;AASA;AAYA;AAaA;;;;;AA4DA;;;;;;;;;;;;;KArOY,qBAAA;;;;;;;;;UAoBK,iBAAA;iBACA;;;;;;;sBAOK;;;;;kBAKJ;;;;;;;;;;;;cAsGL,uCAAuC;;;;;;;;cASvC,iCAAiC;;;;;;;;;UAY7B,UAAA;iBACA;;;;;;;;;;;;;;iBAYD,sBAAA,0BAEJ,cAAc,wCAEvB;;;;;;;;;iBAwDa,uBAAA,0BAEJ,cAAc"}
@@ -99,6 +99,13 @@ const PATTERNS = [
99
99
  ],
100
100
  regex: /<\|(?:im_start|system|assistant|user|im_end)\|>/gi,
101
101
  mask: "[REDACTED:imperative-pattern]"
102
+ },
103
+ {
104
+ name: "untrusted-content-delimiter-injection",
105
+ description: "Fabricated `<<<untrusted_content>>>` envelope delimiters inside untrusted content - an attempt to prematurely close, or spoof a nested opening of, the inbound trust envelope. The regex is scoped STRICTLY to the envelope markers (never bare `<<<` / `>>>` runs) so legitimate Python doctest / REPL `>>>` and shell heredoc fragments are untouched.",
106
+ prefilter: ["untrusted_content"],
107
+ regex: /<<<\s*\/?\s*untrusted_content/gi,
108
+ mask: "[REDACTED:imperative-pattern]"
102
109
  }
103
110
  ];
104
111
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"imperative-patterns.js","names":["PATTERNS: readonly ImperativePattern[]","BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[]","IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>","hits: { pattern: string; matchCount: number }[]","match: RegExpExecArray | null"],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":["/**\n * Imperative-pattern catalogue for inbound prompt-injection defence.\n *\n * Sibling to {@link BUILT_IN_PATTERNS} (PII / secrets) - the two\n * catalogues are disjoint by construction. The imperative catalogue\n * is consumed by the inbound sanitization layer in `@graphorin/tools`\n * to scan tool / MCP results before they reach the agent's message\n * store; the PII / secrets catalogue is consumed by the outbound\n * exporter validators to scan span attributes before they reach an\n * exporter.\n *\n * The patterns target the canonical English \"ignore previous\n * instructions\" / \"system override\" injection family - the concrete\n * surface that an untrusted-skill or MCP server result might use to\n * smuggle imperative content into the next provider call. The\n * catalogue is intentionally conservative: every entry has a fixed-\n * substring prefilter so the per-byte scan budget stays sub-millisecond\n * on typical 16 KB tool results.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable name of an imperative pattern. The catalogue is curated;\n * user-supplied patterns can use any identifier they want and will be\n * passed through the sanitization layer alongside the built-ins.\n *\n * @stable\n */\nexport type ImperativePatternName =\n | 'ignore-previous-instructions'\n | 'forget-instructions'\n | 'override-instructions'\n | 'system-prompt-leak'\n | 'role-reassignment'\n | 'developer-mode'\n | 'jailbreak-marker'\n | 'tool-call-injection'\n | 'role-tag-injection';\n\n/**\n * One entry in the imperative-pattern catalogue. The shape mirrors\n * {@link BUILT_IN_PATTERNS} so consumers can share scan / replace\n * machinery, but the fields are typed as imperative-only so the two\n * catalogues do not accidentally merge.\n *\n * @stable\n */\nexport interface ImperativePattern {\n readonly name: ImperativePatternName | (string & {});\n readonly description: string;\n /**\n * Cheap substring prefilter applied before the regex; if the body\n * does not contain any of the prefilter substrings the regex is\n * skipped entirely. The prefilter is case-insensitive.\n */\n readonly prefilter: ReadonlyArray<string>;\n /**\n * Full regex applied when the prefilter matches. Always carries the\n * `g` and `i` flags; the catalogue construction validates this.\n */\n readonly regex: RegExp;\n /** Replacement string applied by the `'detect-and-strip*'` policies. */\n readonly mask: string;\n}\n\nconst PATTERNS: readonly ImperativePattern[] = [\n {\n name: 'ignore-previous-instructions',\n description:\n 'The canonical \"ignore previous instructions\" injection - the most common form of prompt injection seen across MCP results.',\n prefilter: ['ignore', 'disregard', 'forget'],\n regex:\n /\\b(?:ignore|disregard|forget)\\s+(?:all\\s+)?(?:the\\s+)?(?:previous|prior|above|earlier|preceding|original)\\s+(?:instructions?|prompts?|messages?|directives?|rules?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'forget-instructions',\n description: 'Imperative \"forget what I told you\" / memory-wipe family.',\n prefilter: ['forget', 'erase', 'clear'],\n regex:\n /\\b(?:forget|erase|clear|wipe)\\s+(?:everything|all)\\s+(?:above|before|previous|i\\s+(?:told|said))\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'override-instructions',\n description: '\"Override the system prompt\" / \"new instructions\" injection family.',\n prefilter: ['override', 'new instructions', 'updated instructions', 'replace'],\n regex:\n /\\b(?:override|replace)\\s+(?:the\\s+)?(?:system\\s+)?(?:prompt|instructions?|directives?)\\b|\\bnew\\s+(?:instructions?|prompts?|directives?)\\s*(?::|follow|are)\\b|\\bupdated\\s+(?:instructions?|prompts?|directives?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'system-prompt-leak',\n description:\n 'Requests asking the model to reveal its system prompt / hidden instructions / configuration.',\n prefilter: ['system prompt', 'system message', 'reveal', 'print', 'show'],\n regex:\n /\\b(?:reveal|print|show|output|repeat|expose|disclose)\\s+(?:your|the)\\s+(?:system\\s+(?:prompt|message|instructions?)|hidden\\s+instructions?|initial\\s+(?:prompt|instructions?)|configuration)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-reassignment',\n description: '\"You are now ...\" / role-reassignment imperative family.',\n prefilter: ['you are now', 'you are no longer', 'pretend', 'act as'],\n regex:\n /\\byou\\s+are\\s+(?:now|no\\s+longer)\\s+(?:a\\b|an\\b|the\\b)|\\bpretend\\s+(?:to\\s+be|you\\s+are)\\b|\\bact\\s+as\\s+(?:a|an|the|if\\s+you)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'developer-mode',\n description:\n 'Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes - a long-running jailbreak family.',\n prefilter: ['developer mode', 'admin mode', 'unrestricted'],\n regex:\n /\\b(?:enter|enable|activate)\\s+(?:developer|admin|debug|unrestricted|god|root)\\s+mode\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'jailbreak-marker',\n description: 'Direct \"jailbreak\" markers in tool result content.',\n prefilter: ['jailbreak', 'jailbroken'],\n regex: /\\bjailbroken?\\s+(?:mode|response|model|assistant)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'tool-call-injection',\n description:\n 'Tool-call markers that try to coerce the model into invoking a tool from the result content.',\n prefilter: ['<tool_use', '<function_call', '<invoke'],\n regex: /<(?:tool_use|function_call|invoke|tool_call)\\b[^>]*>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-tag-injection',\n description:\n 'Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.',\n prefilter: ['<|im_start|', '<|system|', '<|assistant|', '<|user|'],\n regex: /<\\|(?:im_start|system|assistant|user|im_end)\\|>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n];\n\n/**\n * The default-on imperative-pattern catalogue. Stable across patches;\n * additions during the pre-1.0 window are minor-bumps because new\n * patterns may produce additional `tool.inbound.sanitization.hit{...}`\n * counter increments on existing deployments.\n *\n * @stable\n */\nexport const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[] = PATTERNS;\n\n/**\n * Combined Aho-Corasick-style prefilter set across every pattern.\n * Lower-cased substrings; consumers test the body once with the\n * combined filter before iterating regexes.\n *\n * @stable\n */\nexport const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string> = Object.freeze([\n ...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase()))),\n]);\n\n/**\n * Compiled scan helper. Returns the list of pattern names that fired\n * AND the number of bytes the strip would remove if applied. Bounded\n * by the budget hint - when exceeded, returns `null` to let the caller\n * apply the best-effort `'detect-failed'` annotation.\n *\n * @stable\n */\nexport interface ScanResult {\n readonly hits: ReadonlyArray<{ readonly pattern: string; readonly matchCount: number }>;\n readonly bytesMatched: number;\n readonly scanDurationUs: number;\n}\n\n/**\n * Run the imperative-pattern scan against `body`. Patterns are\n * iterated in catalogue order; the prefilter shortcut returns early\n * for bodies that do not contain any imperative-family substring.\n *\n * @stable\n */\nexport function scanImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n budgetMs = 5,\n): ScanResult | null {\n const start = performance.now();\n if (body.length === 0) {\n return { hits: [], bytesMatched: 0, scanDurationUs: 0 };\n }\n const lower = body.toLowerCase();\n let prefilterHit = false;\n for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) {\n if (lower.includes(sub)) {\n prefilterHit = true;\n break;\n }\n }\n if (!prefilterHit) {\n return {\n hits: [],\n bytesMatched: 0,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n }\n const hits: { pattern: string; matchCount: number }[] = [];\n let bytesMatched = 0;\n for (const pattern of patterns) {\n if (performance.now() - start > budgetMs) return null;\n const localPrefilterMatch = pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()));\n if (!localPrefilterMatch) continue;\n let matchCount = 0;\n pattern.regex.lastIndex = 0;\n let match: RegExpExecArray | null = pattern.regex.exec(body);\n while (match !== null) {\n matchCount++;\n bytesMatched += match[0].length;\n if (pattern.regex.lastIndex === match.index) {\n pattern.regex.lastIndex = match.index + 1;\n }\n match = pattern.regex.exec(body);\n }\n if (matchCount > 0) {\n hits.push({ pattern: pattern.name as string, matchCount });\n }\n }\n return {\n hits,\n bytesMatched,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n}\n\n/**\n * Apply `pattern.mask` to every match of every pattern in `body`. Used\n * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT\n * match any imperative pattern itself, so post-strip bodies do not\n * trigger another scan hit on round trips.\n *\n * @stable\n */\nexport function stripImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n): string {\n let out = body;\n for (const pattern of patterns) {\n out = out.replace(pattern.regex, pattern.mask);\n }\n return out;\n}\n"],"mappings":";AAkEA,MAAMA,WAAyC;CAC7C;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAU;GAAa;GAAS;EAC5C,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAU;GAAS;GAAQ;EACvC,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAY;GAAoB;GAAwB;GAAU;EAC9E,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAiB;GAAkB;GAAU;GAAS;GAAO;EACzE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAe;GAAqB;GAAW;GAAS;EACpE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAkB;GAAc;GAAe;EAC3D,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW,CAAC,aAAa,aAAa;EACtC,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAa;GAAkB;GAAU;EACrD,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAe;GAAa;GAAgB;GAAU;EAClE,OAAO;EACP,MAAM;EACP;CACF;;;;;;;;;AAUD,MAAaC,+BAA6D;;;;;;;;AAS1E,MAAaC,kCAAyD,OAAO,OAAO,CAClF,GAAG,IAAI,IAAI,6BAA6B,SAAS,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CACjG,CAAC;;;;;;;;AAuBF,SAAgB,uBACd,MACA,WAA6C,8BAC7C,WAAW,GACQ;CACnB,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAI,KAAK,WAAW,EAClB,QAAO;EAAE,MAAM,EAAE;EAAE,cAAc;EAAG,gBAAgB;EAAG;CAEzD,MAAM,QAAQ,KAAK,aAAa;CAChC,IAAI,eAAe;AACnB,MAAK,MAAM,OAAO,gCAChB,KAAI,MAAM,SAAS,IAAI,EAAE;AACvB,iBAAe;AACf;;AAGJ,KAAI,CAAC,aACH,QAAO;EACL,MAAM,EAAE;EACR,cAAc;EACd,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;CAEH,MAAMC,OAAkD,EAAE;CAC1D,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,YAAY,KAAK,GAAG,QAAQ,SAAU,QAAO;AAEjD,MAAI,CADwB,QAAQ,UAAU,MAAM,QAAQ,MAAM,SAAS,IAAI,aAAa,CAAC,CAAC,CACpE;EAC1B,IAAI,aAAa;AACjB,UAAQ,MAAM,YAAY;EAC1B,IAAIC,QAAgC,QAAQ,MAAM,KAAK,KAAK;AAC5D,SAAO,UAAU,MAAM;AACrB;AACA,mBAAgB,MAAM,GAAG;AACzB,OAAI,QAAQ,MAAM,cAAc,MAAM,MACpC,SAAQ,MAAM,YAAY,MAAM,QAAQ;AAE1C,WAAQ,QAAQ,MAAM,KAAK,KAAK;;AAElC,MAAI,aAAa,EACf,MAAK,KAAK;GAAE,SAAS,QAAQ;GAAgB;GAAY,CAAC;;AAG9D,QAAO;EACL;EACA;EACA,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;;;;;;;;;;AAWH,SAAgB,wBACd,MACA,WAA6C,8BACrC;CACR,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,SACpB,OAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAEhD,QAAO"}
1
+ {"version":3,"file":"imperative-patterns.js","names":["PATTERNS: readonly ImperativePattern[]","BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[]","IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string>","hits: { pattern: string; matchCount: number }[]","match: RegExpExecArray | null"],"sources":["../../src/redaction/imperative-patterns.ts"],"sourcesContent":["/**\n * Imperative-pattern catalogue for inbound prompt-injection defence.\n *\n * Sibling to `BUILT_IN_PATTERNS` (PII / secrets) - the two\n * catalogues are disjoint by construction. The imperative catalogue\n * is consumed by the inbound sanitization layer in `@graphorin/tools`\n * to scan tool / MCP results before they reach the agent's message\n * store; the PII / secrets catalogue is consumed by the outbound\n * exporter validators to scan span attributes before they reach an\n * exporter.\n *\n * The patterns target the canonical English \"ignore previous\n * instructions\" / \"system override\" injection family - the concrete\n * surface that an untrusted-skill or MCP server result might use to\n * smuggle imperative content into the next provider call. The\n * catalogue is intentionally conservative: every entry has a fixed-\n * substring prefilter so the per-byte scan budget stays sub-millisecond\n * on typical 16 KB tool results.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable name of an imperative pattern. The catalogue is curated;\n * user-supplied patterns can use any identifier they want and will be\n * passed through the sanitization layer alongside the built-ins.\n *\n * @stable\n */\nexport type ImperativePatternName =\n | 'ignore-previous-instructions'\n | 'forget-instructions'\n | 'override-instructions'\n | 'system-prompt-leak'\n | 'role-reassignment'\n | 'developer-mode'\n | 'jailbreak-marker'\n | 'tool-call-injection'\n | 'role-tag-injection'\n | 'untrusted-content-delimiter-injection';\n\n/**\n * One entry in the imperative-pattern catalogue. The shape mirrors\n * `BUILT_IN_PATTERNS` so consumers can share scan / replace\n * machinery, but the fields are typed as imperative-only so the two\n * catalogues do not accidentally merge.\n *\n * @stable\n */\nexport interface ImperativePattern {\n readonly name: ImperativePatternName | (string & {});\n readonly description: string;\n /**\n * Cheap substring prefilter applied before the regex; if the body\n * does not contain any of the prefilter substrings the regex is\n * skipped entirely. The prefilter is case-insensitive.\n */\n readonly prefilter: ReadonlyArray<string>;\n /**\n * Full regex applied when the prefilter matches. Always carries the\n * `g` and `i` flags; the catalogue construction validates this.\n */\n readonly regex: RegExp;\n /** Replacement string applied by the `'detect-and-strip*'` policies. */\n readonly mask: string;\n}\n\nconst PATTERNS: readonly ImperativePattern[] = [\n {\n name: 'ignore-previous-instructions',\n description:\n 'The canonical \"ignore previous instructions\" injection - the most common form of prompt injection seen across MCP results.',\n prefilter: ['ignore', 'disregard', 'forget'],\n regex:\n /\\b(?:ignore|disregard|forget)\\s+(?:all\\s+)?(?:the\\s+)?(?:previous|prior|above|earlier|preceding|original)\\s+(?:instructions?|prompts?|messages?|directives?|rules?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'forget-instructions',\n description: 'Imperative \"forget what I told you\" / memory-wipe family.',\n prefilter: ['forget', 'erase', 'clear'],\n regex:\n /\\b(?:forget|erase|clear|wipe)\\s+(?:everything|all)\\s+(?:above|before|previous|i\\s+(?:told|said))\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'override-instructions',\n description: '\"Override the system prompt\" / \"new instructions\" injection family.',\n prefilter: ['override', 'new instructions', 'updated instructions', 'replace'],\n regex:\n /\\b(?:override|replace)\\s+(?:the\\s+)?(?:system\\s+)?(?:prompt|instructions?|directives?)\\b|\\bnew\\s+(?:instructions?|prompts?|directives?)\\s*(?::|follow|are)\\b|\\bupdated\\s+(?:instructions?|prompts?|directives?)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'system-prompt-leak',\n description:\n 'Requests asking the model to reveal its system prompt / hidden instructions / configuration.',\n prefilter: ['system prompt', 'system message', 'reveal', 'print', 'show'],\n regex:\n /\\b(?:reveal|print|show|output|repeat|expose|disclose)\\s+(?:your|the)\\s+(?:system\\s+(?:prompt|message|instructions?)|hidden\\s+instructions?|initial\\s+(?:prompt|instructions?)|configuration)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-reassignment',\n description: '\"You are now ...\" / role-reassignment imperative family.',\n prefilter: ['you are now', 'you are no longer', 'pretend', 'act as'],\n regex:\n /\\byou\\s+are\\s+(?:now|no\\s+longer)\\s+(?:a\\b|an\\b|the\\b)|\\bpretend\\s+(?:to\\s+be|you\\s+are)\\b|\\bact\\s+as\\s+(?:a|an|the|if\\s+you)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'developer-mode',\n description:\n 'Requests to enter \"developer mode\" / \"DAN\" / unrestricted modes - a long-running jailbreak family.',\n prefilter: ['developer mode', 'admin mode', 'unrestricted'],\n regex:\n /\\b(?:enter|enable|activate)\\s+(?:developer|admin|debug|unrestricted|god|root)\\s+mode\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'jailbreak-marker',\n description: 'Direct \"jailbreak\" markers in tool result content.',\n prefilter: ['jailbreak', 'jailbroken'],\n regex: /\\bjailbroken?\\s+(?:mode|response|model|assistant)\\b/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'tool-call-injection',\n description:\n 'Tool-call markers that try to coerce the model into invoking a tool from the result content.',\n prefilter: ['<tool_use', '<function_call', '<invoke'],\n regex: /<(?:tool_use|function_call|invoke|tool_call)\\b[^>]*>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'role-tag-injection',\n description:\n 'Chat-role tags injected into tool results to spoof a system / assistant message in the conversation.',\n prefilter: ['<|im_start|', '<|system|', '<|assistant|', '<|user|'],\n regex: /<\\|(?:im_start|system|assistant|user|im_end)\\|>/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n {\n name: 'untrusted-content-delimiter-injection',\n description:\n 'Fabricated `<<<untrusted_content>>>` envelope delimiters inside untrusted content - an attempt to prematurely close, or spoof a nested opening of, the inbound trust envelope. The regex is scoped STRICTLY to the envelope markers (never bare `<<<` / `>>>` runs) so legitimate Python doctest / REPL `>>>` and shell heredoc fragments are untouched.',\n // The word alone is the prefilter: any marker form the regex can\n // match necessarily contains it, including whitespace-padded\n // variants like `<<< untrusted_content` that a tighter\n // `<<<untrusted_content` prefilter would miss.\n prefilter: ['untrusted_content'],\n regex: /<<<\\s*\\/?\\s*untrusted_content/gi,\n mask: '[REDACTED:imperative-pattern]',\n },\n];\n\n/**\n * The default-on imperative-pattern catalogue. Stable across patches;\n * additions during the pre-1.0 window are minor-bumps because new\n * patterns may produce additional `tool.inbound.sanitization.hit{...}`\n * counter increments on existing deployments.\n *\n * @stable\n */\nexport const BUILT_IN_IMPERATIVE_PATTERNS: readonly ImperativePattern[] = PATTERNS;\n\n/**\n * Combined Aho-Corasick-style prefilter set across every pattern.\n * Lower-cased substrings; consumers test the body once with the\n * combined filter before iterating regexes.\n *\n * @stable\n */\nexport const IMPERATIVE_PREFILTER_SUBSTRINGS: ReadonlyArray<string> = Object.freeze([\n ...new Set(BUILT_IN_IMPERATIVE_PATTERNS.flatMap((p) => p.prefilter.map((s) => s.toLowerCase()))),\n]);\n\n/**\n * Compiled scan helper. Returns the list of pattern names that fired\n * AND the number of bytes the strip would remove if applied. Bounded\n * by the budget hint - when exceeded, returns `null` to let the caller\n * apply the best-effort `'detect-failed'` annotation.\n *\n * @stable\n */\nexport interface ScanResult {\n readonly hits: ReadonlyArray<{ readonly pattern: string; readonly matchCount: number }>;\n readonly bytesMatched: number;\n readonly scanDurationUs: number;\n}\n\n/**\n * Run the imperative-pattern scan against `body`. Patterns are\n * iterated in catalogue order; the prefilter shortcut returns early\n * for bodies that do not contain any imperative-family substring.\n *\n * @stable\n */\nexport function scanImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n budgetMs = 5,\n): ScanResult | null {\n const start = performance.now();\n if (body.length === 0) {\n return { hits: [], bytesMatched: 0, scanDurationUs: 0 };\n }\n const lower = body.toLowerCase();\n let prefilterHit = false;\n for (const sub of IMPERATIVE_PREFILTER_SUBSTRINGS) {\n if (lower.includes(sub)) {\n prefilterHit = true;\n break;\n }\n }\n if (!prefilterHit) {\n return {\n hits: [],\n bytesMatched: 0,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n }\n const hits: { pattern: string; matchCount: number }[] = [];\n let bytesMatched = 0;\n for (const pattern of patterns) {\n if (performance.now() - start > budgetMs) return null;\n const localPrefilterMatch = pattern.prefilter.some((sub) => lower.includes(sub.toLowerCase()));\n if (!localPrefilterMatch) continue;\n let matchCount = 0;\n pattern.regex.lastIndex = 0;\n let match: RegExpExecArray | null = pattern.regex.exec(body);\n while (match !== null) {\n matchCount++;\n bytesMatched += match[0].length;\n if (pattern.regex.lastIndex === match.index) {\n pattern.regex.lastIndex = match.index + 1;\n }\n match = pattern.regex.exec(body);\n }\n if (matchCount > 0) {\n hits.push({ pattern: pattern.name as string, matchCount });\n }\n }\n return {\n hits,\n bytesMatched,\n scanDurationUs: Math.round((performance.now() - start) * 1000),\n };\n}\n\n/**\n * Apply `pattern.mask` to every match of every pattern in `body`. Used\n * by the `'detect-and-strip*'` policies. The mask is calibrated to NOT\n * match any imperative pattern itself, so post-strip bodies do not\n * trigger another scan hit on round trips.\n *\n * @stable\n */\nexport function stripImperativePatterns(\n body: string,\n patterns: ReadonlyArray<ImperativePattern> = BUILT_IN_IMPERATIVE_PATTERNS,\n): string {\n let out = body;\n for (const pattern of patterns) {\n out = out.replace(pattern.regex, pattern.mask);\n }\n return out;\n}\n"],"mappings":";AAmEA,MAAMA,WAAyC;CAC7C;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAU;GAAa;GAAS;EAC5C,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAU;GAAS;GAAQ;EACvC,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAY;GAAoB;GAAwB;GAAU;EAC9E,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAiB;GAAkB;GAAU;GAAS;GAAO;EACzE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW;GAAC;GAAe;GAAqB;GAAW;GAAS;EACpE,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAkB;GAAc;GAAe;EAC3D,OACE;EACF,MAAM;EACP;CACD;EACE,MAAM;EACN,aAAa;EACb,WAAW,CAAC,aAAa,aAAa;EACtC,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAa;GAAkB;GAAU;EACrD,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EACF,WAAW;GAAC;GAAe;GAAa;GAAgB;GAAU;EAClE,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,aACE;EAKF,WAAW,CAAC,oBAAoB;EAChC,OAAO;EACP,MAAM;EACP;CACF;;;;;;;;;AAUD,MAAaC,+BAA6D;;;;;;;;AAS1E,MAAaC,kCAAyD,OAAO,OAAO,CAClF,GAAG,IAAI,IAAI,6BAA6B,SAAS,MAAM,EAAE,UAAU,KAAK,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CACjG,CAAC;;;;;;;;AAuBF,SAAgB,uBACd,MACA,WAA6C,8BAC7C,WAAW,GACQ;CACnB,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAI,KAAK,WAAW,EAClB,QAAO;EAAE,MAAM,EAAE;EAAE,cAAc;EAAG,gBAAgB;EAAG;CAEzD,MAAM,QAAQ,KAAK,aAAa;CAChC,IAAI,eAAe;AACnB,MAAK,MAAM,OAAO,gCAChB,KAAI,MAAM,SAAS,IAAI,EAAE;AACvB,iBAAe;AACf;;AAGJ,KAAI,CAAC,aACH,QAAO;EACL,MAAM,EAAE;EACR,cAAc;EACd,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;CAEH,MAAMC,OAAkD,EAAE;CAC1D,IAAI,eAAe;AACnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,YAAY,KAAK,GAAG,QAAQ,SAAU,QAAO;AAEjD,MAAI,CADwB,QAAQ,UAAU,MAAM,QAAQ,MAAM,SAAS,IAAI,aAAa,CAAC,CAAC,CACpE;EAC1B,IAAI,aAAa;AACjB,UAAQ,MAAM,YAAY;EAC1B,IAAIC,QAAgC,QAAQ,MAAM,KAAK,KAAK;AAC5D,SAAO,UAAU,MAAM;AACrB;AACA,mBAAgB,MAAM,GAAG;AACzB,OAAI,QAAQ,MAAM,cAAc,MAAM,MACpC,SAAQ,MAAM,YAAY,MAAM,QAAQ;AAE1C,WAAQ,QAAQ,MAAM,KAAK,KAAK;;AAElC,MAAI,aAAa,EACf,MAAK,KAAK;GAAE,SAAS,QAAQ;GAAgB;GAAY,CAAC;;AAG9D,QAAO;EACL;EACA;EACA,gBAAgB,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,IAAK;EAC/D;;;;;;;;;;AAWH,SAAgB,wBACd,MACA,WAA6C,8BACrC;CACR,IAAI,MAAM;AACV,MAAK,MAAM,WAAW,SACpB,OAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,KAAK;AAEhD,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"patterns.d.ts","names":[],"sources":["../../src/redaction/patterns.ts"],"sourcesContent":[],"mappings":";;AAyBA;AAyBA;AAOA;AA+JA;AAUA;AAUA;;;;;;;;;;;;;;;;;;KAnNY,kBAAA;;;;;;;KAyBA,eAAA;;;;;;UAOK,gBAAA;;qBAEI;;kBAEH;;;;;;;;;;;;;;;;;;;;;;;;;cA2JL,4BAA4B;;;;;;;cAU5B,0BAA0B;;;;;;;cAU1B,gCAAgC"}
1
+ {"version":3,"file":"patterns.d.ts","names":[],"sources":["../../src/redaction/patterns.ts"],"sourcesContent":[],"mappings":";;AAyBA;AAyBA;AAOA;AAmKA;AAUA;AAUA;;;;;;;;;;;;;;;;;;KAvNY,kBAAA;;;;;;;KAyBA,eAAA;;;;;;UAOK,gBAAA;;qBAEI;;kBAEH;;;;;;;;;;;;;;;;;;;;;;;;;cA+JL,4BAA4B;;;;;;;cAU5B,0BAA0B;;;;;;;cAU1B,gCAAgC"}
@@ -3,8 +3,8 @@ const PATTERNS = [
3
3
  {
4
4
  name: "graphorin-token",
5
5
  category: "secret",
6
- description: "Graphorin server token (`kru_<env>_v1_<entropy>_<crc32>`).",
7
- regex: /kru_(?:dev|test|prod)_v1_[A-Za-z0-9]{20,80}_[A-Za-z0-9]{6}/g,
6
+ description: "Graphorin server token (`gph_<env>_v1_<entropy>_<crc32>`; default prefix).",
7
+ regex: /gph_[a-z0-9]{2,12}_v1_[A-Za-z0-9]{20,80}_[A-Za-z0-9]{6}/g,
8
8
  mask: "[REDACTED graphorin-token]"
9
9
  },
10
10
  {
@@ -1 +1 @@
1
- {"version":3,"file":"patterns.js","names":["PATTERNS: readonly RedactionPattern[]","BUILT_IN_PATTERNS: readonly RedactionPattern[]","OPT_IN_PATTERNS: readonly RedactionPattern[]","ALL_BUILT_IN_PATTERNS: readonly RedactionPattern[]"],"sources":["../../src/redaction/patterns.ts"],"sourcesContent":["/**\n * Built-in PII / secret detection patterns. The catalogue is intentionally\n * conservative - every pattern has both positive and negative test\n * fixtures and is documented so operators understand exactly what is\n * matched.\n *\n * The catalogue is split into two groups:\n *\n * - **secret** - credentials, API tokens, JWTs, private keys. Matches\n * are always dropped + counted, regardless of the configured tier\n * floor.\n * - **pii** - email / phone / IBAN / credit card / SSN / IP address.\n * Subject to the configured tier floor + per-pattern enable / disable\n * knobs.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable pattern identifier. The catalogue is curated; user-supplied\n * patterns can use any identifier they want and will be passed through\n * the validator in addition to the built-ins.\n *\n * @stable\n */\nexport type BuiltInPatternName =\n | 'graphorin-token'\n | 'openai-key'\n | 'anthropic-key'\n | 'aws-access-key'\n | 'gcp-service-account'\n | 'github-pat'\n | 'jwt'\n | 'bearer-header'\n | 'basic-auth'\n | 'private-key-pem'\n | 'email'\n | 'creditcard'\n | 'us-ssn'\n | 'phone-e164'\n | 'iban'\n | 'ipv4'\n | 'ipv6';\n\n/**\n * Pattern category - `secret` matches always force a drop; `pii`\n * matches respect the configured `enabledPatterns` allow-list.\n *\n * @stable\n */\nexport type PatternCategory = 'secret' | 'pii';\n\n/**\n * One entry in the redaction catalogue.\n *\n * @stable\n */\nexport interface RedactionPattern {\n readonly name: string;\n readonly category: PatternCategory;\n readonly description: string;\n readonly regex: RegExp;\n /** Replacement string used when `mode === 'mask'`. */\n readonly mask?: string;\n /**\n * Optional per-match predicate (RP-21). When present, a regex hit is only\n * treated as a real match - and masked - when this returns `true` for the\n * matched substring. Used by the `creditcard` pattern to require a valid\n * Luhn checksum so look-alike digit runs (epoch-ms timestamps, order ids)\n * are not corrupted.\n */\n readonly verify?: (match: string) => boolean;\n /**\n * Optional opt-in flag. When `true` the pattern is **not** active by\n * default; operators must add it to `enabledPatterns` explicitly. Used\n * by the IPv4 / IPv6 patterns because raw IPs frequently appear in\n * non-PII log lines (host headers, debug traces, …).\n */\n readonly optIn?: boolean;\n}\n\nconst PATTERNS: readonly RedactionPattern[] = [\n {\n name: 'graphorin-token',\n category: 'secret',\n description: 'Graphorin server token (`kru_<env>_v1_<entropy>_<crc32>`).',\n regex: /kru_(?:dev|test|prod)_v1_[A-Za-z0-9]{20,80}_[A-Za-z0-9]{6}/g,\n mask: '[REDACTED graphorin-token]',\n },\n {\n name: 'openai-key',\n category: 'secret',\n description: 'OpenAI API key (`sk-...`).',\n regex: /\\bsk-[A-Za-z0-9_-]{20,}\\b/g,\n mask: '[REDACTED openai-key]',\n },\n {\n name: 'anthropic-key',\n category: 'secret',\n description: 'Anthropic API key (`sk-ant-...`).',\n regex: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g,\n mask: '[REDACTED anthropic-key]',\n },\n {\n name: 'aws-access-key',\n category: 'secret',\n description: 'AWS access-key id (`AKIA...`).',\n regex: /\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b/g,\n mask: '[REDACTED aws-access-key]',\n },\n {\n name: 'gcp-service-account',\n category: 'secret',\n description:\n 'GCP service-account email (`...@...iam.gserviceaccount.com`). Opt-in; the `email` pattern already redacts the address shape; this entry exists for operators wanting the explicit `gcp-service-account` counter label.',\n regex: /[A-Za-z0-9._-]+@[A-Za-z0-9-]+\\.iam\\.gserviceaccount\\.com\\b/g,\n mask: '[REDACTED gcp-service-account]',\n optIn: true,\n },\n {\n name: 'github-pat',\n category: 'secret',\n description: 'GitHub personal-access token / OAuth token / app token.',\n regex: /\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}\\b/g,\n mask: '[REDACTED github-pat]',\n },\n {\n name: 'jwt',\n category: 'secret',\n description: 'JSON Web Token (`eyJ...`).',\n regex: /\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g,\n mask: '[REDACTED jwt]',\n },\n {\n name: 'bearer-header',\n category: 'secret',\n description: '`Authorization: Bearer ...` header values.',\n regex: /\\bBearer\\s+[A-Za-z0-9._-]{16,}\\b/gi,\n mask: 'Bearer [REDACTED]',\n },\n {\n name: 'basic-auth',\n category: 'secret',\n description: '`Authorization: Basic ...` header values.',\n regex: /\\bBasic\\s+[A-Za-z0-9+/=]{16,}\\b/gi,\n mask: 'Basic [REDACTED]',\n },\n {\n name: 'private-key-pem',\n category: 'secret',\n description: 'PEM private-key block (`-----BEGIN ... PRIVATE KEY-----`).',\n regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,\n mask: '[REDACTED private-key-pem]',\n },\n {\n name: 'email',\n category: 'pii',\n description: 'RFC-5322-ish email address.',\n regex: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g,\n mask: '[REDACTED email]',\n },\n {\n name: 'creditcard',\n category: 'pii',\n description: 'Credit card number (13-19 digits, optional spaces / dashes; Luhn-checked).',\n regex: /\\b(?:\\d[\\s-]*?){13,19}\\b/g,\n mask: '[REDACTED creditcard]',\n // RP-21: require a valid Luhn checksum so a 13-19 digit run that is not a\n // real PAN (millisecond epoch timestamps, order numbers, …) is left alone.\n verify: isLuhnValid,\n },\n {\n name: 'us-ssn',\n category: 'pii',\n description: 'US Social Security Number (`123-45-6789`).',\n regex: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n mask: '[REDACTED us-ssn]',\n },\n {\n name: 'phone-e164',\n category: 'pii',\n description: 'E.164 phone number (`+11234567890`).',\n regex: /\\+\\d{7,15}\\b/g,\n mask: '[REDACTED phone-e164]',\n },\n {\n name: 'iban',\n category: 'pii',\n description: 'IBAN (2-letter country + 2 check digits + up to 30 alphanumerics).',\n regex: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/g,\n mask: '[REDACTED iban]',\n },\n {\n name: 'ipv4',\n category: 'pii',\n description: 'IPv4 dotted-quad address. Opt-in (often appears legitimately in logs).',\n regex: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n mask: '[REDACTED ipv4]',\n optIn: true,\n },\n {\n name: 'ipv6',\n category: 'pii',\n description: 'IPv6 address (full / compressed). Opt-in.',\n regex: /\\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\\b/g,\n mask: '[REDACTED ipv6]',\n optIn: true,\n },\n];\n\n/**\n * The 14 default-on built-in patterns (the IPv4 and IPv6 detectors are\n * opt-in and live in {@link OPT_IN_PATTERNS}).\n *\n * @stable\n */\nexport const BUILT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS.filter(\n (p) => p.optIn !== true,\n);\n\n/**\n * Patterns that are recognised by the validator but are NOT enabled by\n * default. Use them via `patterns: [...BUILT_IN_PATTERNS, ...OPT_IN_PATTERNS]`.\n *\n * @stable\n */\nexport const OPT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS.filter(\n (p) => p.optIn === true,\n);\n\n/**\n * Full registry - for tooling that wants to introspect every pattern\n * the framework knows about (e.g. CLI `graphorin redaction list`).\n *\n * @stable\n */\nexport const ALL_BUILT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS;\n\n/**\n * Luhn (mod-10) checksum validator used by the `creditcard` pattern (RP-21).\n * Strips spaces / dashes, bounds the length to 13-19 digits, and verifies the\n * checksum so a digit run that merely *looks* like a PAN is not redacted.\n */\nfunction isLuhnValid(value: string): boolean {\n const digits = value.replace(/\\D/g, '');\n if (digits.length < 13 || digits.length > 19) return false;\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i -= 1) {\n let d = digits.charCodeAt(i) - 48; // '0'\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n"],"mappings":";AAiFA,MAAMA,WAAwC;CAC5C;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aACE;EACF,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EAGN,QAAQ;EACT;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACF;;;;;;;AAQD,MAAaC,oBAAiD,SAAS,QACpE,MAAM,EAAE,UAAU,KACpB;;;;;;;AAQD,MAAaC,kBAA+C,SAAS,QAClE,MAAM,EAAE,UAAU,KACpB;;;;;;;AAQD,MAAaC,wBAAqD;;;;;;AAOlE,SAAS,YAAY,OAAwB;CAC3C,MAAM,SAAS,MAAM,QAAQ,OAAO,GAAG;AACvC,KAAI,OAAO,SAAS,MAAM,OAAO,SAAS,GAAI,QAAO;CACrD,IAAI,MAAM;CACV,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAC9C,IAAI,IAAI,OAAO,WAAW,EAAE,GAAG;AAC/B,MAAI,QAAQ;AACV,QAAK;AACL,OAAI,IAAI,EAAG,MAAK;;AAElB,SAAO;AACP,WAAS,CAAC;;AAEZ,QAAO,MAAM,OAAO"}
1
+ {"version":3,"file":"patterns.js","names":["PATTERNS: readonly RedactionPattern[]","BUILT_IN_PATTERNS: readonly RedactionPattern[]","OPT_IN_PATTERNS: readonly RedactionPattern[]","ALL_BUILT_IN_PATTERNS: readonly RedactionPattern[]"],"sources":["../../src/redaction/patterns.ts"],"sourcesContent":["/**\n * Built-in PII / secret detection patterns. The catalogue is intentionally\n * conservative - every pattern has both positive and negative test\n * fixtures and is documented so operators understand exactly what is\n * matched.\n *\n * The catalogue is split into two groups:\n *\n * - **secret** - credentials, API tokens, JWTs, private keys. Matches\n * are always dropped + counted, regardless of the configured tier\n * floor.\n * - **pii** - email / phone / IBAN / credit card / SSN / IP address.\n * Subject to the configured tier floor + per-pattern enable / disable\n * knobs.\n *\n * @packageDocumentation\n */\n\n/**\n * Stable pattern identifier. The catalogue is curated; user-supplied\n * patterns can use any identifier they want and will be passed through\n * the validator in addition to the built-ins.\n *\n * @stable\n */\nexport type BuiltInPatternName =\n | 'graphorin-token'\n | 'openai-key'\n | 'anthropic-key'\n | 'aws-access-key'\n | 'gcp-service-account'\n | 'github-pat'\n | 'jwt'\n | 'bearer-header'\n | 'basic-auth'\n | 'private-key-pem'\n | 'email'\n | 'creditcard'\n | 'us-ssn'\n | 'phone-e164'\n | 'iban'\n | 'ipv4'\n | 'ipv6';\n\n/**\n * Pattern category - `secret` matches always force a drop; `pii`\n * matches respect the configured `enabledPatterns` allow-list.\n *\n * @stable\n */\nexport type PatternCategory = 'secret' | 'pii';\n\n/**\n * One entry in the redaction catalogue.\n *\n * @stable\n */\nexport interface RedactionPattern {\n readonly name: string;\n readonly category: PatternCategory;\n readonly description: string;\n readonly regex: RegExp;\n /** Replacement string used when `mode === 'mask'`. */\n readonly mask?: string;\n /**\n * Optional per-match predicate (RP-21). When present, a regex hit is only\n * treated as a real match - and masked - when this returns `true` for the\n * matched substring. Used by the `creditcard` pattern to require a valid\n * Luhn checksum so look-alike digit runs (epoch-ms timestamps, order ids)\n * are not corrupted.\n */\n readonly verify?: (match: string) => boolean;\n /**\n * Optional opt-in flag. When `true` the pattern is **not** active by\n * default; operators must add it to `enabledPatterns` explicitly. Used\n * by the IPv4 / IPv6 patterns because raw IPs frequently appear in\n * non-PII log lines (host headers, debug traces, …).\n */\n readonly optIn?: boolean;\n}\n\nconst PATTERNS: readonly RedactionPattern[] = [\n {\n name: 'graphorin-token',\n category: 'secret',\n // Matches the DEFAULT token prefix from @graphorin/security\n // (DEFAULT_TOKEN_PREFIX = 'gph'); deployments that configure a\n // custom prefix must register their own pattern. The env label is\n // matched loosely because `acceptEnvironments` is operator-extensible.\n description: 'Graphorin server token (`gph_<env>_v1_<entropy>_<crc32>`; default prefix).',\n regex: /gph_[a-z0-9]{2,12}_v1_[A-Za-z0-9]{20,80}_[A-Za-z0-9]{6}/g,\n mask: '[REDACTED graphorin-token]',\n },\n {\n name: 'openai-key',\n category: 'secret',\n description: 'OpenAI API key (`sk-...`).',\n regex: /\\bsk-[A-Za-z0-9_-]{20,}\\b/g,\n mask: '[REDACTED openai-key]',\n },\n {\n name: 'anthropic-key',\n category: 'secret',\n description: 'Anthropic API key (`sk-ant-...`).',\n regex: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g,\n mask: '[REDACTED anthropic-key]',\n },\n {\n name: 'aws-access-key',\n category: 'secret',\n description: 'AWS access-key id (`AKIA...`).',\n regex: /\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b/g,\n mask: '[REDACTED aws-access-key]',\n },\n {\n name: 'gcp-service-account',\n category: 'secret',\n description:\n 'GCP service-account email (`...@...iam.gserviceaccount.com`). Opt-in; the `email` pattern already redacts the address shape; this entry exists for operators wanting the explicit `gcp-service-account` counter label.',\n regex: /[A-Za-z0-9._-]+@[A-Za-z0-9-]+\\.iam\\.gserviceaccount\\.com\\b/g,\n mask: '[REDACTED gcp-service-account]',\n optIn: true,\n },\n {\n name: 'github-pat',\n category: 'secret',\n description: 'GitHub personal-access token / OAuth token / app token.',\n regex: /\\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}\\b/g,\n mask: '[REDACTED github-pat]',\n },\n {\n name: 'jwt',\n category: 'secret',\n description: 'JSON Web Token (`eyJ...`).',\n regex: /\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\b/g,\n mask: '[REDACTED jwt]',\n },\n {\n name: 'bearer-header',\n category: 'secret',\n description: '`Authorization: Bearer ...` header values.',\n regex: /\\bBearer\\s+[A-Za-z0-9._-]{16,}\\b/gi,\n mask: 'Bearer [REDACTED]',\n },\n {\n name: 'basic-auth',\n category: 'secret',\n description: '`Authorization: Basic ...` header values.',\n regex: /\\bBasic\\s+[A-Za-z0-9+/=]{16,}\\b/gi,\n mask: 'Basic [REDACTED]',\n },\n {\n name: 'private-key-pem',\n category: 'secret',\n description: 'PEM private-key block (`-----BEGIN ... PRIVATE KEY-----`).',\n regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,\n mask: '[REDACTED private-key-pem]',\n },\n {\n name: 'email',\n category: 'pii',\n description: 'RFC-5322-ish email address.',\n regex: /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g,\n mask: '[REDACTED email]',\n },\n {\n name: 'creditcard',\n category: 'pii',\n description: 'Credit card number (13-19 digits, optional spaces / dashes; Luhn-checked).',\n regex: /\\b(?:\\d[\\s-]*?){13,19}\\b/g,\n mask: '[REDACTED creditcard]',\n // RP-21: require a valid Luhn checksum so a 13-19 digit run that is not a\n // real PAN (millisecond epoch timestamps, order numbers, …) is left alone.\n verify: isLuhnValid,\n },\n {\n name: 'us-ssn',\n category: 'pii',\n description: 'US Social Security Number (`123-45-6789`).',\n regex: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n mask: '[REDACTED us-ssn]',\n },\n {\n name: 'phone-e164',\n category: 'pii',\n description: 'E.164 phone number (`+11234567890`).',\n regex: /\\+\\d{7,15}\\b/g,\n mask: '[REDACTED phone-e164]',\n },\n {\n name: 'iban',\n category: 'pii',\n description: 'IBAN (2-letter country + 2 check digits + up to 30 alphanumerics).',\n regex: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b/g,\n mask: '[REDACTED iban]',\n },\n {\n name: 'ipv4',\n category: 'pii',\n description: 'IPv4 dotted-quad address. Opt-in (often appears legitimately in logs).',\n regex: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n mask: '[REDACTED ipv4]',\n optIn: true,\n },\n {\n name: 'ipv6',\n category: 'pii',\n description: 'IPv6 address (full / compressed). Opt-in.',\n regex: /\\b(?:[0-9a-fA-F]{1,4}:){2,7}[0-9a-fA-F]{1,4}\\b/g,\n mask: '[REDACTED ipv6]',\n optIn: true,\n },\n];\n\n/**\n * The 14 default-on built-in patterns (the IPv4 and IPv6 detectors are\n * opt-in and live in {@link OPT_IN_PATTERNS}).\n *\n * @stable\n */\nexport const BUILT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS.filter(\n (p) => p.optIn !== true,\n);\n\n/**\n * Patterns that are recognised by the validator but are NOT enabled by\n * default. Use them via `patterns: [...BUILT_IN_PATTERNS, ...OPT_IN_PATTERNS]`.\n *\n * @stable\n */\nexport const OPT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS.filter(\n (p) => p.optIn === true,\n);\n\n/**\n * Full registry - for tooling that wants to introspect every pattern\n * the framework knows about (e.g. CLI `graphorin redaction list`).\n *\n * @stable\n */\nexport const ALL_BUILT_IN_PATTERNS: readonly RedactionPattern[] = PATTERNS;\n\n/**\n * Luhn (mod-10) checksum validator used by the `creditcard` pattern (RP-21).\n * Strips spaces / dashes, bounds the length to 13-19 digits, and verifies the\n * checksum so a digit run that merely *looks* like a PAN is not redacted.\n */\nfunction isLuhnValid(value: string): boolean {\n const digits = value.replace(/\\D/g, '');\n if (digits.length < 13 || digits.length > 19) return false;\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i -= 1) {\n let d = digits.charCodeAt(i) - 48; // '0'\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n"],"mappings":";AAiFA,MAAMA,WAAwC;CAC5C;EACE,MAAM;EACN,UAAU;EAKV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aACE;EACF,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EAGN,QAAQ;EACT;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACP;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD;EACE,MAAM;EACN,UAAU;EACV,aAAa;EACb,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACF;;;;;;;AAQD,MAAaC,oBAAiD,SAAS,QACpE,MAAM,EAAE,UAAU,KACpB;;;;;;;AAQD,MAAaC,kBAA+C,SAAS,QAClE,MAAM,EAAE,UAAU,KACpB;;;;;;;AAQD,MAAaC,wBAAqD;;;;;;AAOlE,SAAS,YAAY,OAAwB;CAC3C,MAAM,SAAS,MAAM,QAAQ,OAAO,GAAG;AACvC,KAAI,OAAO,SAAS,MAAM,OAAO,SAAS,GAAI,QAAO;CACrD,IAAI,MAAM;CACV,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAC9C,IAAI,IAAI,OAAO,WAAW,EAAE,GAAG;AAC/B,MAAI,QAAQ;AACV,QAAK;AACL,OAAI,IAAI,EAAG,MAAK;;AAElB,SAAO;AACP,WAAS,CAAC;;AAEZ,QAAO,MAAM,OAAO"}
@@ -5,7 +5,12 @@ import { SpanType } from "@graphorin/core";
5
5
  /** @stable */
6
6
  type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';
7
7
  /**
8
- * Per-span-type rate override.
8
+ * Per-span-type rate override. Applies on the probabilistic root path
9
+ * AND (W-090) to children of a sampled parent under `'parent-based'` -
10
+ * `{ type: 'tool.execute', rate: 0.01 }` thins the per-call spans
11
+ * inside every sampled `agent.run` trace. A child dropped by its rule
12
+ * breaks the tree below it: its own descendants inherit
13
+ * `parentSampled=false`.
9
14
  *
10
15
  * @stable
11
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.d.ts","names":[],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":[],"mappings":";;;;;AAoEiB,KAxDL,qBAAA,GA0DiB,cAAA,GAAA,WAAA,GAAA,YAAA;AAc7B;;;;;UAjEiB,YAAA;iBACA;;;;;;;;UASA,eAAA;;;;mBAIE,cAAc;;2BAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCV,OAAA;;qBAEI;;;;;;;;;;;;;iBAcL,aAAA,QAAoB,kBAAuB"}
1
+ {"version":3,"file":"sampling.d.ts","names":[],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":[],"mappings":";;;;;KAeY,qBAAA;;;;;;;;;;;UAYK,YAAA;iBACA;;;;;;;;UASA,eAAA;;;;mBAIE,cAAc;;2BAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiCV,OAAA;;qBAEI;;;;;;;;;;;;;iBAcL,aAAA,QAAoB,kBAAuB"}
@@ -22,7 +22,13 @@ function createSampler(opts = {}) {
22
22
  shouldSample(type, parentSampled) {
23
23
  if (decisionMaker === "always-on") return true;
24
24
  if (decisionMaker === "parent-based") {
25
- if (parentSampled === true) return true;
25
+ if (parentSampled === true) {
26
+ const rule = ruleByType.get(type);
27
+ if (rule === void 0) return true;
28
+ if (rule >= 1) return true;
29
+ if (rule <= 0) return false;
30
+ return random() < rule;
31
+ }
26
32
  if (parentSampled === false) return false;
27
33
  }
28
34
  if (decisionMaker === "rate-limit") {
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.js","names":["decisionMaker: SamplingDecisionMaker","windowStart: number | undefined"],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":["/**\n * Head + tail sampling helpers. The tracer pairs the configured\n * sampler with a per-type override registry so chatty span types\n * (`memory.embed`, `tool.execute.partial`, …) can be downsampled\n * without affecting the rest of the trace stream.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/** @stable */\nexport type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';\n\n/**\n * Per-span-type rate override.\n *\n * @stable\n */\nexport interface SamplingRule {\n readonly type: SpanType | string;\n readonly rate: number;\n}\n\n/**\n * Configuration shape consumed by {@link createSampler}.\n *\n * @stable\n */\nexport interface SamplingOptions {\n /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */\n readonly rate?: number;\n /** Per-type overrides. Last write wins on duplicate `type`. */\n readonly rules?: ReadonlyArray<SamplingRule>;\n /** Decision maker. Defaults to `'parent-based'`. */\n readonly decisionMaker?: SamplingDecisionMaker;\n /**\n * Cap for the `'rate-limit'` decision maker: at most this many root spans\n * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap\n * (samples everything); `0` ⇒ sample nothing. Ignored by the other\n * decision makers.\n */\n readonly maxPerSecond?: number;\n /**\n * Clock for the `'rate-limit'` window. Defaults to `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n /**\n * Optional override for streaming-event sampling.\n * @see RB-52 - streaming event family `tool.execute.{progress,partial}`.\n */\n readonly streaming?: {\n readonly eventSamplingRate?: number;\n readonly includeChunkContent?: 'none' | 'text-only' | 'all';\n };\n /**\n * Override for the random source. Useful for deterministic tests.\n *\n * @internal\n */\n readonly random?: () => number;\n}\n\n/**\n * @stable\n */\nexport interface Sampler {\n /** Decide whether a span of the given type should be recorded. */\n shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;\n /** Decide whether a span event of the given name should be recorded. */\n shouldRecordEvent(name: string): boolean;\n /** Returns whether chunk *content* should travel through the exporter. */\n shouldIncludeChunkContent(): boolean;\n}\n\n/**\n * Build a {@link Sampler} from the supplied options. The sampler is\n * intentionally inexpensive - every decision boils down to a single\n * `random < threshold` comparison.\n *\n * @stable\n */\nexport function createSampler(opts: SamplingOptions = {}): Sampler {\n const baseRate = clampRate(opts.rate ?? 1.0);\n const decisionMaker: SamplingDecisionMaker = opts.decisionMaker ?? 'parent-based';\n const ruleByType = new Map<string, number>();\n for (const rule of opts.rules ?? []) {\n ruleByType.set(rule.type, clampRate(rule.rate));\n }\n const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1.0);\n const includeChunks = opts.streaming?.includeChunkContent ?? 'none';\n const random = opts.random ?? Math.random;\n const maxPerSecond = opts.maxPerSecond;\n const now = opts.now ?? Date.now;\n let windowStart: number | undefined;\n let windowCount = 0;\n\n return {\n shouldSample(type, parentSampled): boolean {\n if (decisionMaker === 'always-on') return true;\n if (decisionMaker === 'parent-based') {\n // RP-19: true parent-based - follow the parent's real sampling\n // decision. A child of an unsampled parent is NOT recorded (it would\n // otherwise be an orphan); a root span (no parent) falls through to\n // the rate.\n if (parentSampled === true) return true;\n if (parentSampled === false) return false;\n }\n if (decisionMaker === 'rate-limit') {\n // RP-19: a real token-window limiter, distinct from the probabilistic\n // path. Caps sampled spans to `maxPerSecond` per rolling second.\n if (maxPerSecond === undefined) return true;\n if (maxPerSecond <= 0) return false;\n const t = now();\n if (windowStart === undefined || t - windowStart >= 1000) {\n windowStart = t;\n windowCount = 0;\n }\n if (windowCount < maxPerSecond) {\n windowCount += 1;\n return true;\n }\n return false;\n }\n const rate = ruleByType.get(type) ?? baseRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldRecordEvent(name): boolean {\n const rate = ruleByType.get(name) ?? streamingRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldIncludeChunkContent(): boolean {\n return includeChunks !== 'none';\n },\n };\n}\n\nfunction clampRate(rate: number): number {\n if (Number.isNaN(rate)) return 1;\n if (rate < 0) return 0;\n if (rate > 1) return 1;\n return rate;\n}\n"],"mappings":";;;;;;;;AAoFA,SAAgB,cAAc,OAAwB,EAAE,EAAW;CACjE,MAAM,WAAW,UAAU,KAAK,QAAQ,EAAI;CAC5C,MAAMA,gBAAuC,KAAK,iBAAiB;CACnE,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,YAAW,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;CAEjD,MAAM,gBAAgB,UAAU,KAAK,WAAW,qBAAqB,EAAI;CACzE,MAAM,gBAAgB,KAAK,WAAW,uBAAuB;CAC7D,MAAM,SAAS,KAAK,UAAU,KAAK;CACnC,MAAM,eAAe,KAAK;CAC1B,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,IAAIC;CACJ,IAAI,cAAc;AAElB,QAAO;EACL,aAAa,MAAM,eAAwB;AACzC,OAAI,kBAAkB,YAAa,QAAO;AAC1C,OAAI,kBAAkB,gBAAgB;AAKpC,QAAI,kBAAkB,KAAM,QAAO;AACnC,QAAI,kBAAkB,MAAO,QAAO;;AAEtC,OAAI,kBAAkB,cAAc;AAGlC,QAAI,iBAAiB,OAAW,QAAO;AACvC,QAAI,gBAAgB,EAAG,QAAO;IAC9B,MAAM,IAAI,KAAK;AACf,QAAI,gBAAgB,UAAa,IAAI,eAAe,KAAM;AACxD,mBAAc;AACd,mBAAc;;AAEhB,QAAI,cAAc,cAAc;AAC9B,oBAAe;AACf,YAAO;;AAET,WAAO;;GAET,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,kBAAkB,MAAe;GAC/B,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,4BAAqC;AACnC,UAAO,kBAAkB;;EAE5B;;AAGH,SAAS,UAAU,MAAsB;AACvC,KAAI,OAAO,MAAM,KAAK,CAAE,QAAO;AAC/B,KAAI,OAAO,EAAG,QAAO;AACrB,KAAI,OAAO,EAAG,QAAO;AACrB,QAAO"}
1
+ {"version":3,"file":"sampling.js","names":["decisionMaker: SamplingDecisionMaker","windowStart: number | undefined"],"sources":["../../src/tracer/sampling.ts"],"sourcesContent":["/**\n * Head + tail sampling helpers. The tracer pairs the configured\n * sampler with a per-type override registry so chatty span types\n * (`memory.embed`, `tool.execute.partial`, …) can be downsampled\n * without affecting the rest of the trace stream. Under the default\n * `'parent-based'` decision maker the per-type rules apply to CHILDREN\n * of a sampled trace too (W-090) - a rule can only downsample inside a\n * sampled trace, never resurrect children of an unsampled parent.\n *\n * @packageDocumentation\n */\n\nimport type { SpanType } from '@graphorin/core';\n\n/** @stable */\nexport type SamplingDecisionMaker = 'parent-based' | 'always-on' | 'rate-limit';\n\n/**\n * Per-span-type rate override. Applies on the probabilistic root path\n * AND (W-090) to children of a sampled parent under `'parent-based'` -\n * `{ type: 'tool.execute', rate: 0.01 }` thins the per-call spans\n * inside every sampled `agent.run` trace. A child dropped by its rule\n * breaks the tree below it: its own descendants inherit\n * `parentSampled=false`.\n *\n * @stable\n */\nexport interface SamplingRule {\n readonly type: SpanType | string;\n readonly rate: number;\n}\n\n/**\n * Configuration shape consumed by {@link createSampler}.\n *\n * @stable\n */\nexport interface SamplingOptions {\n /** Default head-sampling rate. Must be in `[0, 1]`. Defaults to `1.0`. */\n readonly rate?: number;\n /** Per-type overrides. Last write wins on duplicate `type`. */\n readonly rules?: ReadonlyArray<SamplingRule>;\n /** Decision maker. Defaults to `'parent-based'`. */\n readonly decisionMaker?: SamplingDecisionMaker;\n /**\n * Cap for the `'rate-limit'` decision maker: at most this many root spans\n * are sampled per rolling 1-second window (RP-19). `undefined` ⇒ no cap\n * (samples everything); `0` ⇒ sample nothing. Ignored by the other\n * decision makers.\n */\n readonly maxPerSecond?: number;\n /**\n * Clock for the `'rate-limit'` window. Defaults to `Date.now`.\n *\n * @internal\n */\n readonly now?: () => number;\n /**\n * Optional override for streaming-event sampling.\n * @see RB-52 - streaming event family `tool.execute.{progress,partial}`.\n */\n readonly streaming?: {\n readonly eventSamplingRate?: number;\n readonly includeChunkContent?: 'none' | 'text-only' | 'all';\n };\n /**\n * Override for the random source. Useful for deterministic tests.\n *\n * @internal\n */\n readonly random?: () => number;\n}\n\n/**\n * @stable\n */\nexport interface Sampler {\n /** Decide whether a span of the given type should be recorded. */\n shouldSample(type: SpanType | string, parentSampled?: boolean): boolean;\n /** Decide whether a span event of the given name should be recorded. */\n shouldRecordEvent(name: string): boolean;\n /** Returns whether chunk *content* should travel through the exporter. */\n shouldIncludeChunkContent(): boolean;\n}\n\n/**\n * Build a {@link Sampler} from the supplied options. The sampler is\n * intentionally inexpensive - every decision boils down to a single\n * `random < threshold` comparison.\n *\n * @stable\n */\nexport function createSampler(opts: SamplingOptions = {}): Sampler {\n const baseRate = clampRate(opts.rate ?? 1.0);\n const decisionMaker: SamplingDecisionMaker = opts.decisionMaker ?? 'parent-based';\n const ruleByType = new Map<string, number>();\n for (const rule of opts.rules ?? []) {\n ruleByType.set(rule.type, clampRate(rule.rate));\n }\n const streamingRate = clampRate(opts.streaming?.eventSamplingRate ?? 1.0);\n const includeChunks = opts.streaming?.includeChunkContent ?? 'none';\n const random = opts.random ?? Math.random;\n const maxPerSecond = opts.maxPerSecond;\n const now = opts.now ?? Date.now;\n let windowStart: number | undefined;\n let windowCount = 0;\n\n return {\n shouldSample(type, parentSampled): boolean {\n if (decisionMaker === 'always-on') return true;\n if (decisionMaker === 'parent-based') {\n // RP-19: true parent-based - follow the parent's real sampling\n // decision. A child of an unsampled parent is NOT recorded (it would\n // otherwise be an orphan); a root span (no parent) falls through to\n // the rate.\n //\n // W-090: a per-type rule ACTS on children of a sampled parent -\n // that is where the volume lives (`tool.execute` inside a sampled\n // `agent.run` trace), and the docstring always promised it. A rule\n // never resurrects children of an UNSAMPLED parent (orphans); a\n // child dropped by its rule makes its own descendants inherit\n // parentSampled=false.\n if (parentSampled === true) {\n const rule = ruleByType.get(type);\n if (rule === undefined) return true;\n if (rule >= 1) return true;\n if (rule <= 0) return false;\n return random() < rule;\n }\n if (parentSampled === false) return false;\n }\n if (decisionMaker === 'rate-limit') {\n // RP-19: a real token-window limiter, distinct from the probabilistic\n // path. Caps sampled spans to `maxPerSecond` per rolling second.\n if (maxPerSecond === undefined) return true;\n if (maxPerSecond <= 0) return false;\n const t = now();\n if (windowStart === undefined || t - windowStart >= 1000) {\n windowStart = t;\n windowCount = 0;\n }\n if (windowCount < maxPerSecond) {\n windowCount += 1;\n return true;\n }\n return false;\n }\n const rate = ruleByType.get(type) ?? baseRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldRecordEvent(name): boolean {\n const rate = ruleByType.get(name) ?? streamingRate;\n if (rate >= 1) return true;\n if (rate <= 0) return false;\n return random() < rate;\n },\n shouldIncludeChunkContent(): boolean {\n return includeChunks !== 'none';\n },\n };\n}\n\nfunction clampRate(rate: number): number {\n if (Number.isNaN(rate)) return 1;\n if (rate < 0) return 0;\n if (rate > 1) return 1;\n return rate;\n}\n"],"mappings":";;;;;;;;AA4FA,SAAgB,cAAc,OAAwB,EAAE,EAAW;CACjE,MAAM,WAAW,UAAU,KAAK,QAAQ,EAAI;CAC5C,MAAMA,gBAAuC,KAAK,iBAAiB;CACnE,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,CACjC,YAAW,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,CAAC;CAEjD,MAAM,gBAAgB,UAAU,KAAK,WAAW,qBAAqB,EAAI;CACzE,MAAM,gBAAgB,KAAK,WAAW,uBAAuB;CAC7D,MAAM,SAAS,KAAK,UAAU,KAAK;CACnC,MAAM,eAAe,KAAK;CAC1B,MAAM,MAAM,KAAK,OAAO,KAAK;CAC7B,IAAIC;CACJ,IAAI,cAAc;AAElB,QAAO;EACL,aAAa,MAAM,eAAwB;AACzC,OAAI,kBAAkB,YAAa,QAAO;AAC1C,OAAI,kBAAkB,gBAAgB;AAYpC,QAAI,kBAAkB,MAAM;KAC1B,MAAM,OAAO,WAAW,IAAI,KAAK;AACjC,SAAI,SAAS,OAAW,QAAO;AAC/B,SAAI,QAAQ,EAAG,QAAO;AACtB,SAAI,QAAQ,EAAG,QAAO;AACtB,YAAO,QAAQ,GAAG;;AAEpB,QAAI,kBAAkB,MAAO,QAAO;;AAEtC,OAAI,kBAAkB,cAAc;AAGlC,QAAI,iBAAiB,OAAW,QAAO;AACvC,QAAI,gBAAgB,EAAG,QAAO;IAC9B,MAAM,IAAI,KAAK;AACf,QAAI,gBAAgB,UAAa,IAAI,eAAe,KAAM;AACxD,mBAAc;AACd,mBAAc;;AAEhB,QAAI,cAAc,cAAc;AAC9B,oBAAe;AACf,YAAO;;AAET,WAAO;;GAET,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,kBAAkB,MAAe;GAC/B,MAAM,OAAO,WAAW,IAAI,KAAK,IAAI;AACrC,OAAI,QAAQ,EAAG,QAAO;AACtB,OAAI,QAAQ,EAAG,QAAO;AACtB,UAAO,QAAQ,GAAG;;EAEpB,4BAAqC;AACnC,UAAO,kBAAkB;;EAE5B;;AAGH,SAAS,UAAU,MAAsB;AACvC,KAAI,OAAO,MAAM,KAAK,CAAE,QAAO;AAC/B,KAAI,OAAO,EAAG,QAAO;AACrB,KAAI,OAAO,EAAG,QAAO;AACrB,QAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"span.d.ts","names":[],"sources":["../../src/tracer/span.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA0BiB,mBAAA;yBACQ;;;;;;;;;UAUR,wBAAwB,WAAW,kBAAkB,OAAO;oCACzC,2BAA2B"}
1
+ {"version":3,"file":"span.d.ts","names":[],"sources":["../../src/tracer/span.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA2BiB,mBAAA;yBACQ;;;;;;;;;UAUR,wBAAwB,WAAW,kBAAkB,OAAO;oCACzC,2BAA2B"}
@@ -24,13 +24,17 @@ function createSpan(input) {
24
24
  attributes[name] = value;
25
25
  if (opts?.sensitivity !== void 0) sensitivities[name] = opts.sensitivity;
26
26
  },
27
- addEvent(name, attrs) {
27
+ addEvent(name, attrs, opts) {
28
28
  if (ended) return;
29
29
  if (!input.recordEvent(name)) return;
30
+ const eventSensitivities = {};
31
+ if (opts?.sensitivity !== void 0) for (const key of Object.keys(attrs ?? {})) eventSensitivities[key] = opts.sensitivity;
32
+ for (const [key, tier] of Object.entries(opts?.sensitivityByAttribute ?? {})) eventSensitivities[key] = tier;
30
33
  events.push({
31
34
  name,
32
35
  timeUnixNano: Math.floor(input.now() * 1e6),
33
- attributes: Object.freeze({ ...attrs ?? {} })
36
+ attributes: Object.freeze({ ...attrs ?? {} }),
37
+ ...Object.keys(eventSensitivities).length > 0 ? { sensitivityByAttribute: Object.freeze(eventSensitivities) } : {}
34
38
  });
35
39
  },
36
40
  recordException(err) {
@@ -44,7 +48,12 @@ function createSpan(input) {
44
48
  events.push({
45
49
  name: "exception",
46
50
  timeUnixNano: Math.floor(input.now() * 1e6),
47
- attributes: attrs
51
+ attributes: attrs,
52
+ sensitivityByAttribute: Object.freeze({
53
+ "exception.type": "public",
54
+ "exception.message": "internal",
55
+ "exception.stacktrace": "internal"
56
+ })
48
57
  });
49
58
  },
50
59
  setStatus(s, message) {
@@ -1 +1 @@
1
- {"version":3,"file":"span.js","names":["attributes: Record<string, SpanAttributeValue>","sensitivities: Record<string, Sensitivity>","events: SpanRecord['events'][number][]","status: SpanStatus","statusMessage: string | undefined","attrs: SpanAttributes","record: SpanRecord<T>"],"sources":["../../src/tracer/span.ts"],"sourcesContent":["/**\n * Concrete `AISpan<T>` implementation. The span is intentionally\n * decoupled from `@opentelemetry/api`: the tracer materialises a\n * {@link SpanRecord} on `end()` and lets exporters do the rest.\n *\n * @packageDocumentation\n */\n\nimport type {\n AISpan,\n Sensitivity,\n SpanAttributes,\n SpanAttributeValue,\n SpanStatus,\n SpanType,\n} from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\n\n/**\n * Optional metadata accepted by `setAttribute(...)` to declare the\n * sensitivity of a single attribute. The validator uses the declared\n * tier when deciding whether to drop the attribute.\n *\n * @stable\n */\nexport interface SetAttributeOptions {\n readonly sensitivity?: Sensitivity;\n}\n\n/**\n * The internal span carries the convenience `setAttribute(...)` method\n * exposed by the tracer surface (per-attribute sensitivity tagging) on\n * top of the standard {@link AISpan} contract.\n *\n * @stable\n */\nexport interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;\n}\n\n/**\n * @internal - sink invoked when a span ends.\n */\nexport type SpanSink = (record: SpanRecord) => void;\n\n/**\n * @internal - parameters passed by the tracer to {@link createSpan}.\n */\nexport interface SpanCreateInput<T extends SpanType = SpanType> {\n readonly type: T;\n readonly name: string;\n readonly traceId: string;\n readonly id: string;\n readonly parentId?: string;\n readonly attrs?: SpanAttributes;\n readonly attrSensitivities?: Readonly<Record<string, Sensitivity>>;\n readonly sink: SpanSink;\n readonly now: () => number;\n readonly recordEvent: (name: string) => boolean;\n}\n\n/**\n * @internal\n */\nexport function createSpan<T extends SpanType>(input: SpanCreateInput<T>): GraphorinSpan<T> {\n const startUnixNano = Math.floor(input.now() * 1_000_000);\n const attributes: Record<string, SpanAttributeValue> = { ...(input.attrs ?? {}) };\n const sensitivities: Record<string, Sensitivity> = { ...(input.attrSensitivities ?? {}) };\n const events: SpanRecord['events'][number][] = [];\n let status: SpanStatus = 'ok';\n let statusMessage: string | undefined;\n let ended = false;\n\n const span: GraphorinSpan<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n setAttributes(attrs: SpanAttributes): void {\n if (ended) return;\n for (const [k, v] of Object.entries(attrs)) {\n attributes[k] = v;\n }\n },\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void {\n if (ended) return;\n attributes[name] = value;\n if (opts?.sensitivity !== undefined) {\n sensitivities[name] = opts.sensitivity;\n }\n },\n addEvent(name: string, attrs?: SpanAttributes): void {\n if (ended) return;\n if (!input.recordEvent(name)) return;\n events.push({\n name,\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: Object.freeze({ ...(attrs ?? {}) }) as SpanAttributes,\n });\n },\n recordException(err: unknown): void {\n if (ended) return;\n const e = err as { name?: unknown; message?: unknown; stack?: unknown };\n const attrs: SpanAttributes = {\n 'exception.type': typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'Error',\n 'exception.message': typeof e?.message === 'string' ? e.message : String(err),\n ...(typeof e?.stack === 'string' ? { 'exception.stacktrace': e.stack } : {}),\n };\n events.push({\n name: 'exception',\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: attrs,\n });\n },\n setStatus(s: SpanStatus, message?: string): void {\n if (ended) return;\n status = s;\n if (message !== undefined) statusMessage = message;\n },\n end(): void {\n if (ended) return;\n ended = true;\n const endUnixNano = Math.floor(input.now() * 1_000_000);\n const record: SpanRecord<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n name: input.name,\n startUnixNano,\n endUnixNano,\n status,\n ...(statusMessage === undefined ? {} : { statusMessage }),\n attributes: Object.freeze({ ...attributes }) as SpanAttributes,\n events: Object.freeze([...events]) as SpanRecord['events'],\n ...(Object.keys(sensitivities).length === 0\n ? {}\n : {\n sensitivityByAttribute: Object.freeze({ ...sensitivities }) as Readonly<\n Record<string, SpanAttributeValue>\n >,\n }),\n };\n input.sink(record);\n },\n };\n\n return span;\n}\n"],"mappings":";;;;AAiEA,SAAgB,WAA+B,OAA6C;CAC1F,MAAM,gBAAgB,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;CACzD,MAAMA,aAAiD,EAAE,GAAI,MAAM,SAAS,EAAE,EAAG;CACjF,MAAMC,gBAA6C,EAAE,GAAI,MAAM,qBAAqB,EAAE,EAAG;CACzF,MAAMC,SAAyC,EAAE;CACjD,IAAIC,SAAqB;CACzB,IAAIC;CACJ,IAAI,QAAQ;AA4EZ,QA1E+B;EAC7B,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,SAAS,MAAM;EACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;EACpE,cAAc,OAA6B;AACzC,OAAI,MAAO;AACX,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,YAAW,KAAK;;EAGpB,aAAa,MAAc,OAA2B,MAAkC;AACtF,OAAI,MAAO;AACX,cAAW,QAAQ;AACnB,OAAI,MAAM,gBAAgB,OACxB,eAAc,QAAQ,KAAK;;EAG/B,SAAS,MAAc,OAA8B;AACnD,OAAI,MAAO;AACX,OAAI,CAAC,MAAM,YAAY,KAAK,CAAE;AAC9B,UAAO,KAAK;IACV;IACA,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY,OAAO,OAAO,EAAE,GAAI,SAAS,EAAE,EAAG,CAAC;IAChD,CAAC;;EAEJ,gBAAgB,KAAoB;AAClC,OAAI,MAAO;GACX,MAAM,IAAI;GACV,MAAMC,QAAwB;IAC5B,kBAAkB,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO;IAC9E,qBAAqB,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,OAAO,IAAI;IAC7E,GAAI,OAAO,GAAG,UAAU,WAAW,EAAE,wBAAwB,EAAE,OAAO,GAAG,EAAE;IAC5E;AACD,UAAO,KAAK;IACV,MAAM;IACN,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY;IACb,CAAC;;EAEJ,UAAU,GAAe,SAAwB;AAC/C,OAAI,MAAO;AACX,YAAS;AACT,OAAI,YAAY,OAAW,iBAAgB;;EAE7C,MAAY;AACV,OAAI,MAAO;AACX,WAAQ;GACR,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;GACvD,MAAMC,SAAwB;IAC5B,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,SAAS,MAAM;IACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;IACpE,MAAM,MAAM;IACZ;IACA;IACA;IACA,GAAI,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe;IACxD,YAAY,OAAO,OAAO,EAAE,GAAG,YAAY,CAAC;IAC5C,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC;IAClC,GAAI,OAAO,KAAK,cAAc,CAAC,WAAW,IACtC,EAAE,GACF,EACE,wBAAwB,OAAO,OAAO,EAAE,GAAG,eAAe,CAAC,EAG5D;IACN;AACD,SAAM,KAAK,OAAO;;EAErB"}
1
+ {"version":3,"file":"span.js","names":["attributes: Record<string, SpanAttributeValue>","sensitivities: Record<string, Sensitivity>","events: SpanRecord['events'][number][]","status: SpanStatus","statusMessage: string | undefined","eventSensitivities: Record<string, 'public' | 'internal' | 'secret'>","attrs: SpanAttributes","record: SpanRecord<T>"],"sources":["../../src/tracer/span.ts"],"sourcesContent":["/**\n * Concrete `AISpan<T>` implementation. The span is intentionally\n * decoupled from `@opentelemetry/api`: the tracer materialises a\n * {@link SpanRecord} on `end()` and lets exporters do the rest.\n *\n * @packageDocumentation\n */\n\nimport type {\n AddEventOptions,\n AISpan,\n Sensitivity,\n SpanAttributes,\n SpanAttributeValue,\n SpanStatus,\n SpanType,\n} from '@graphorin/core';\n\nimport type { SpanRecord } from '../exporters/types.js';\n\n/**\n * Optional metadata accepted by `setAttribute(...)` to declare the\n * sensitivity of a single attribute. The validator uses the declared\n * tier when deciding whether to drop the attribute.\n *\n * @stable\n */\nexport interface SetAttributeOptions {\n readonly sensitivity?: Sensitivity;\n}\n\n/**\n * The internal span carries the convenience `setAttribute(...)` method\n * exposed by the tracer surface (per-attribute sensitivity tagging) on\n * top of the standard {@link AISpan} contract.\n *\n * @stable\n */\nexport interface GraphorinSpan<T extends SpanType = SpanType> extends AISpan<T> {\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void;\n}\n\n/**\n * @internal - sink invoked when a span ends.\n */\nexport type SpanSink = (record: SpanRecord) => void;\n\n/**\n * @internal - parameters passed by the tracer to {@link createSpan}.\n */\nexport interface SpanCreateInput<T extends SpanType = SpanType> {\n readonly type: T;\n readonly name: string;\n readonly traceId: string;\n readonly id: string;\n readonly parentId?: string;\n readonly attrs?: SpanAttributes;\n readonly attrSensitivities?: Readonly<Record<string, Sensitivity>>;\n readonly sink: SpanSink;\n readonly now: () => number;\n readonly recordEvent: (name: string) => boolean;\n}\n\n/**\n * @internal\n */\nexport function createSpan<T extends SpanType>(input: SpanCreateInput<T>): GraphorinSpan<T> {\n const startUnixNano = Math.floor(input.now() * 1_000_000);\n const attributes: Record<string, SpanAttributeValue> = { ...(input.attrs ?? {}) };\n const sensitivities: Record<string, Sensitivity> = { ...(input.attrSensitivities ?? {}) };\n const events: SpanRecord['events'][number][] = [];\n let status: SpanStatus = 'ok';\n let statusMessage: string | undefined;\n let ended = false;\n\n const span: GraphorinSpan<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n setAttributes(attrs: SpanAttributes): void {\n if (ended) return;\n for (const [k, v] of Object.entries(attrs)) {\n attributes[k] = v;\n }\n },\n setAttribute(name: string, value: SpanAttributeValue, opts?: SetAttributeOptions): void {\n if (ended) return;\n attributes[name] = value;\n if (opts?.sensitivity !== undefined) {\n sensitivities[name] = opts.sensitivity;\n }\n },\n addEvent(name: string, attrs?: SpanAttributes, opts?: AddEventOptions): void {\n if (ended) return;\n if (!input.recordEvent(name)) return;\n // W-094: resolve the event default + per-attribute overrides into\n // one map the validation exporter consumes. Untagged attributes\n // stay untagged - default-deny below the floor is preserved.\n const eventSensitivities: Record<string, 'public' | 'internal' | 'secret'> = {};\n if (opts?.sensitivity !== undefined) {\n for (const key of Object.keys(attrs ?? {})) {\n eventSensitivities[key] = opts.sensitivity;\n }\n }\n for (const [key, tier] of Object.entries(opts?.sensitivityByAttribute ?? {})) {\n eventSensitivities[key] = tier;\n }\n events.push({\n name,\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: Object.freeze({ ...(attrs ?? {}) }) as SpanAttributes,\n ...(Object.keys(eventSensitivities).length > 0\n ? { sensitivityByAttribute: Object.freeze(eventSensitivities) }\n : {}),\n });\n },\n recordException(err: unknown): void {\n if (ended) return;\n const e = err as { name?: unknown; message?: unknown; stack?: unknown };\n const attrs: SpanAttributes = {\n 'exception.type': typeof e?.name === 'string' && e.name.length > 0 ? e.name : 'Error',\n 'exception.message': typeof e?.message === 'string' ? e.message : String(err),\n ...(typeof e?.stack === 'string' ? { 'exception.stacktrace': e.stack } : {}),\n };\n events.push({\n name: 'exception',\n timeUnixNano: Math.floor(input.now() * 1_000_000),\n attributes: attrs,\n // W-094: the CLASS NAME of an error is safe and load-bearing for\n // out-of-the-box error dashboards - tag it 'public'. Message +\n // stacktrace stay 'internal' (paritous with the un-sanitized\n // statusMessage; the PII patterns still apply at that floor).\n sensitivityByAttribute: Object.freeze({\n 'exception.type': 'public',\n 'exception.message': 'internal',\n 'exception.stacktrace': 'internal',\n }),\n });\n },\n setStatus(s: SpanStatus, message?: string): void {\n if (ended) return;\n status = s;\n if (message !== undefined) statusMessage = message;\n },\n end(): void {\n if (ended) return;\n ended = true;\n const endUnixNano = Math.floor(input.now() * 1_000_000);\n const record: SpanRecord<T> = {\n type: input.type,\n id: input.id,\n traceId: input.traceId,\n ...(input.parentId === undefined ? {} : { parentId: input.parentId }),\n name: input.name,\n startUnixNano,\n endUnixNano,\n status,\n ...(statusMessage === undefined ? {} : { statusMessage }),\n attributes: Object.freeze({ ...attributes }) as SpanAttributes,\n events: Object.freeze([...events]) as SpanRecord['events'],\n ...(Object.keys(sensitivities).length === 0\n ? {}\n : {\n sensitivityByAttribute: Object.freeze({ ...sensitivities }) as Readonly<\n Record<string, SpanAttributeValue>\n >,\n }),\n };\n input.sink(record);\n },\n };\n\n return span;\n}\n"],"mappings":";;;;AAkEA,SAAgB,WAA+B,OAA6C;CAC1F,MAAM,gBAAgB,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;CACzD,MAAMA,aAAiD,EAAE,GAAI,MAAM,SAAS,EAAE,EAAG;CACjF,MAAMC,gBAA6C,EAAE,GAAI,MAAM,qBAAqB,EAAE,EAAG;CACzF,MAAMC,SAAyC,EAAE;CACjD,IAAIC,SAAqB;CACzB,IAAIC;CACJ,IAAI,QAAQ;AAoGZ,QAlG+B;EAC7B,MAAM,MAAM;EACZ,IAAI,MAAM;EACV,SAAS,MAAM;EACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;EACpE,cAAc,OAA6B;AACzC,OAAI,MAAO;AACX,QAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,CACxC,YAAW,KAAK;;EAGpB,aAAa,MAAc,OAA2B,MAAkC;AACtF,OAAI,MAAO;AACX,cAAW,QAAQ;AACnB,OAAI,MAAM,gBAAgB,OACxB,eAAc,QAAQ,KAAK;;EAG/B,SAAS,MAAc,OAAwB,MAA8B;AAC3E,OAAI,MAAO;AACX,OAAI,CAAC,MAAM,YAAY,KAAK,CAAE;GAI9B,MAAMC,qBAAuE,EAAE;AAC/E,OAAI,MAAM,gBAAgB,OACxB,MAAK,MAAM,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC,CACxC,oBAAmB,OAAO,KAAK;AAGnC,QAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,MAAM,0BAA0B,EAAE,CAAC,CAC1E,oBAAmB,OAAO;AAE5B,UAAO,KAAK;IACV;IACA,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY,OAAO,OAAO,EAAE,GAAI,SAAS,EAAE,EAAG,CAAC;IAC/C,GAAI,OAAO,KAAK,mBAAmB,CAAC,SAAS,IACzC,EAAE,wBAAwB,OAAO,OAAO,mBAAmB,EAAE,GAC7D,EAAE;IACP,CAAC;;EAEJ,gBAAgB,KAAoB;AAClC,OAAI,MAAO;GACX,MAAM,IAAI;GACV,MAAMC,QAAwB;IAC5B,kBAAkB,OAAO,GAAG,SAAS,YAAY,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO;IAC9E,qBAAqB,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,OAAO,IAAI;IAC7E,GAAI,OAAO,GAAG,UAAU,WAAW,EAAE,wBAAwB,EAAE,OAAO,GAAG,EAAE;IAC5E;AACD,UAAO,KAAK;IACV,MAAM;IACN,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;IACjD,YAAY;IAKZ,wBAAwB,OAAO,OAAO;KACpC,kBAAkB;KAClB,qBAAqB;KACrB,wBAAwB;KACzB,CAAC;IACH,CAAC;;EAEJ,UAAU,GAAe,SAAwB;AAC/C,OAAI,MAAO;AACX,YAAS;AACT,OAAI,YAAY,OAAW,iBAAgB;;EAE7C,MAAY;AACV,OAAI,MAAO;AACX,WAAQ;GACR,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK,GAAG,IAAU;GACvD,MAAMC,SAAwB;IAC5B,MAAM,MAAM;IACZ,IAAI,MAAM;IACV,SAAS,MAAM;IACf,GAAI,MAAM,aAAa,SAAY,EAAE,GAAG,EAAE,UAAU,MAAM,UAAU;IACpE,MAAM,MAAM;IACZ;IACA;IACA;IACA,GAAI,kBAAkB,SAAY,EAAE,GAAG,EAAE,eAAe;IACxD,YAAY,OAAO,OAAO,EAAE,GAAG,YAAY,CAAC;IAC5C,QAAQ,OAAO,OAAO,CAAC,GAAG,OAAO,CAAC;IAClC,GAAI,OAAO,KAAK,cAAc,CAAC,WAAW,IACtC,EAAE,GACF,EACE,wBAAwB,OAAO,OAAO,EAAE,GAAG,eAAe,CAAC,EAG5D;IACN;AACD,SAAM,KAAK,OAAO;;EAErB"}