{
  "schema_version": "1.0",
  "baseline": "A-runtime-admin-phase-2b",
  "phase": "Phase 2b",
  "updated_at": "2026-04-19",
  "owner": "session_a",
  "honest_note": "This contract defines the SHAPE of audit events the admin service will eventually emit to the ptt.audit.trail Kafka topic and the WORM object-lock bucket. In Phase 2b NOTHING is delivered — the POST /api/policy/audit/preview endpoint renders the envelope for an (actor, action, subject) tuple so downstream runtimes can see the exact shape their audit emission will need, and returns sink_status='deferred'. Any claim that audit is live in Phase 2b is false.",

  "purpose_th": "นิยามรูปแบบ audit envelope ที่ Phase 2c จะส่งจริง · รอบนี้มีแค่ preview · ไม่มีตัวส่งจริง",
  "purpose_en": "Define the audit envelope Phase 2c will actually emit · this phase only previews · no live producer",

  "contract_refs": [
    "docs/runtime/admin-control-plane/audit_event_model.json (Phase 1 · base_shape · 20 event_types · integrity_model)",
    "docs/runtime/admin-control-plane-service/phase_2b_contract.json",
    "docs/runtime/admin-control-plane-service/approval_examples.json (rows carry audit_event_refs[] that will resolve once a real audit store exists)",
    "docs/kb/data/publish_workflow.json (B-owned · read-only · stages produce audit events)",
    "CLAUDE.md#Kafka Topic Quick Reference · ptt.audit.trail (8 parts · 7-year retention · compression=none · WORM)"
  ],

  "envelope_shape": {
    "source_of_truth": "audit_event_model.base_shape (Phase 1)",
    "description_en": "Every audit event carries the base shape PLUS event-type-specific additional_fields. This service emits per base_shape exactly — no field rename, no reshape.",
    "required_fields": [
      "event_id", "event_type", "event_category",
      "timestamp", "actor", "subject", "action"
    ],
    "optional_fields": [
      "approval_refs", "mask_level_at_event", "session_ref",
      "reasons", "trace", "producer", "ip_fingerprint"
    ],
    "event_id_pattern": "aud-YYMMDD-{random8} (same pattern as audit_event_model.base_shape)",
    "timestamp_format": "ISO-8601 with timezone (+07:00 for Asia/Bangkok · server default UTC Z acceptable in dev)"
  },

  "sink_status": "deferred",

  "delivery_modes": {
    "phase_2b_reality": {
      "id": "preview-only",
      "description_en": "POST /api/policy/audit/preview returns an envelope with all required fields populated from the request context · echoes the envelope as a stdout log line in NDJSON format · NEVER contacts Kafka or any external sink",
      "downstream_consumers": "none · this is a contract check, not a log pipeline",
      "retention": "no persistence beyond process stdout"
    },
    "phase_2c_planned": {
      "id": "kafka-producer",
      "description_en": "Python confluent-kafka producer to ptt.audit.trail · 8 partitions · compression=none · retention=7y · keys by entity_id · WORM object-lock mirror",
      "deferred_because": "Kafka SASL/SSL client config + ACL provisioning not in scope for decision service",
      "not_live": true
    },
    "phase_2c_planned_alt": {
      "id": "worm-s3-mirror",
      "description_en": "S3 object-lock bucket (governance mode · 7y retention lock) · monthly segments · signed manifest",
      "deferred_because": "bucket provisioning + IAM not wired",
      "not_live": true
    }
  },

  "integrity_plan_deferred": {
    "hash_chain": "audit_event_model.integrity_model.hash_chain specifies hash-linking per segment · Phase 2b documents it but does NOT sign · signing key management is Phase 2b+ infra",
    "replay_query_api": "planned at Phase 2c · out of scope here"
  },

  "event_type_coverage": {
    "preview_supports_any_of": "all 20 ids in audit_event_model.event_types[] · the endpoint does NOT force a subset",
    "examples_file": "audit_event_examples.json (5 grounded examples spanning 5 categories)",
    "categories_live": ["access", "approval", "transition", "assist", "incident", "governance"],
    "categories_delivered_in_phase_2b": "none — preview only"
  },

  "observability": {
    "stdout_shape": "single NDJSON line per preview call · same JSON as the returned data.envelope · keys sorted · event_id stable for the call duration",
    "log_event_name": "audit_preview",
    "log_fields_added": ["event_type", "event_category", "actor_user_id", "subject_type", "approval_refs_count"]
  },

  "access_control_plan_deferred": {
    "phase_2b_reality": "preview endpoint accepts any dev-mode context · no authorization check beyond the service's existing Bearer/header parser",
    "phase_2c_plan": "only actors with role in {superadmin, platform_governance, founder, dec_board} OR the actor themselves for own-session can request real audit trail reads · today preview is purely a shape check"
  },

  "non_goals_phase_2b": [
    "Kafka producer wiring",
    "WORM persistence",
    "Hash-chain signing / integrity proof",
    "Audit replay / query API",
    "Audit retention enforcement (7-year object-lock)",
    "Access control on audit reads (role-scoped · tenant-scoped)",
    "Event enrichment (IP lookup · UA fingerprint)"
  ],

  "phase_2c_deliverables_out_of_scope": [
    "app.py: swap preview-only render for a real confluent-kafka producer call",
    "Idempotent producer (enable.idempotence=true)",
    "Schema registry binding for ptt.audit.trail",
    "S3 object-lock mirror worker",
    "Hash-chain signer + verifier",
    "GET /api/policy/audit/events (query) · GET /api/policy/audit/events/{id} (single)",
    "Audit read access control (role-scoped · tenant-scoped)"
  ]
}
