// lib/exporters/field-allowlist.mjs
// CWE-212 (Improper Cross-boundary Removal of Sensitive Data) mitigation:
// inline static allowlist that strips PII / high-cardinality fields before
// records reach the OTel exporter.
//
// The allowlist is INLINE STATIC at module-scope (Object.freeze). NEVER read
// runtime from tests/fixtures/ — fixture file is AUTHORING reference only
// (Source: tests/fixtures/jsonl-schemas.md per Step 1 audit).
//
// Per-schema allowlists drop:
//   - command_excerpt (post-bash-stats: arbitrary Bash slice, CWE-212)
//   - session_id     (UUID, high-cardinality, log-only)
//   - task / question / project_dir / brief_path / plan / project (PII-ish prose, paths)
//   - payload (event-emit: open-ended object, allowlist payload-keys instead)
//   - phase_models (structured object, summarize via `profile` label instead)
//   - counts (review nested object, flatten to per-severity metrics elsewhere)

// ---- Per-schema allowlists (Source: tests/fixtures/jsonl-schemas.md) -------

// Source: tests/fixtures/jsonl-schemas.md row 1 (trekbrief)
const TREKBRIEF_ALLOWED = Object.freeze(new Set([
  'ts', 'slug', 'mode', 'interview_turns', 'review_iterations',
  'brief_quality', 'research_topics', 'auto_research', 'auto_result',
  'profile', 'profile_source',
]));

// Source: tests/fixtures/jsonl-schemas.md row 2 (trekresearch)
const TREKRESEARCH_ALLOWED = Object.freeze(new Set([
  'ts', 'slug', 'mode', 'scope', 'dimensions', 'agents_local',
  'agents_external', 'gemini_used', 'confidence', 'contradictions',
  'open_questions', 'profile', 'parallel_agents',
  'external_research_enabled', 'profile_source',
]));

// Source: tests/fixtures/jsonl-schemas.md row 3 (trekplan)
const TREKPLAN_ALLOWED = Object.freeze(new Set([
  'ts', 'slug', 'mode', 'codebase_size', 'codebase_files',
  'agents_deployed', 'deep_dives', 'research_briefs_used',
  'research_scout_used', 'critic_verdict', 'guardian_verdict', 'outcome',
  'profile', 'parallel_agents', 'profile_source',
]));

// Source: tests/fixtures/jsonl-schemas.md row 4 (trekexecute Phase 9 record)
const TREKEXECUTE_ALLOWED = Object.freeze(new Set([
  'ts', 'plan_type', 'mode', 'result', 'steps_total', 'steps_passed',
  'steps_failed', 'steps_skipped', 'failed_at_step',
  'profile', 'profile_source',
]));

// Source: tests/fixtures/jsonl-schemas.md row 5 (trekexecute autonomy events)
const EVENT_EMIT_ALLOWED = Object.freeze(new Set([
  'ts', 'event', 'known_event',
]));

// Source: tests/fixtures/jsonl-schemas.md row 5 (event-emit payload sub-allowlist)
const EVENT_EMIT_PAYLOAD_ALLOWED = Object.freeze(new Set([
  'profile', 'phase_models', 'parallel_agents',
  'external_research_enabled', 'profile_source',
  'brief_quality', 'plan_grade',
]));

// Source: tests/fixtures/jsonl-schemas.md row 6 (post-bash-stats — PII-flag)
// CWE-212: command_excerpt + session_id MUST be stripped.
const POST_BASH_STATS_ALLOWED = Object.freeze(new Set([
  'ts', 'duration_ms', 'success',
]));

// Source: tests/fixtures/jsonl-schemas.md row 7 (trekreview)
const TREKREVIEW_ALLOWED = Object.freeze(new Set([
  'ts', 'slug', 'verdict', 'reviewed_files_count', 'mode',
  'duration_ms', 'profile', 'profile_source',
]));

// Source: tests/fixtures/jsonl-schemas.md row 8 (trekcontinue)
const TREKCONTINUE_ALLOWED = Object.freeze(new Set([
  'ts', 'next_session_label', 'status', 'profile', 'profile_source',
]));

// Schema-id → allowlist set
const SCHEMA_ALLOWLISTS = Object.freeze({
  'trekbrief': TREKBRIEF_ALLOWED,
  'trekresearch': TREKRESEARCH_ALLOWED,
  'trekplan': TREKPLAN_ALLOWED,
  'trekexecute': TREKEXECUTE_ALLOWED,
  'event-emit': EVENT_EMIT_ALLOWED,
  'post-bash-stats': POST_BASH_STATS_ALLOWED,
  'post_bash_stats': POST_BASH_STATS_ALLOWED, // common alt-spelling
  'trekreview': TREKREVIEW_ALLOWED,
  'trekcontinue': TREKCONTINUE_ALLOWED,
});

/**
 * Apply field-allowlist to a record. Drops any field not in the schema's
 * allowlist. Returns a new object (does not mutate input).
 *
 * Always preserves `_schema_id` (caller-supplied identifier used by exporters
 * to group records by source).
 *
 * @param {object} record
 * @param {string} schemaType  Schema-id (e.g. 'trekplan', 'post-bash-stats')
 * @returns {object}            Redacted record with only allowlisted fields.
 */
export function applyFieldAllowlist(record, schemaType) {
  if (!record || typeof record !== 'object') return {};
  const allowed = SCHEMA_ALLOWLISTS[schemaType];
  if (!allowed) {
    // Unknown schema-type: be conservative — keep ts only + _schema_id.
    return record.ts ? { _schema_id: schemaType, ts: record.ts } : { _schema_id: schemaType };
  }
  const out = { _schema_id: schemaType };
  for (const k of allowed) {
    if (k in record) out[k] = record[k];
  }

  // Special: event-emit records have a `payload` sub-object that needs its
  // own allowlist applied recursively.
  if (schemaType === 'event-emit' && record.payload && typeof record.payload === 'object') {
    const subOut = {};
    for (const k of EVENT_EMIT_PAYLOAD_ALLOWED) {
      if (k in record.payload) subOut[k] = record.payload[k];
    }
    out.payload = subOut;
  }

  return out;
}

export {
  SCHEMA_ALLOWLISTS,
  EVENT_EMIT_PAYLOAD_ALLOWED,
  POST_BASH_STATS_ALLOWED,
  TREKBRIEF_ALLOWED,
  TREKRESEARCH_ALLOWED,
  TREKPLAN_ALLOWED,
  TREKEXECUTE_ALLOWED,
  TREKREVIEW_ALLOWED,
  TREKCONTINUE_ALLOWED,
};
